前言
socket编程分为TCP和UDP两个模块,其中TCP是可靠的、安全的,常用于发送文件等,而UDP是不可靠的、不安全的,常用作视频通话等。
如下图:
头文件与库:
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
准备工作:
创建工程后,首先右键工程,选择属性
然后选择 C/C++ - 预处理器 - 预处理器定义
将字符串 _WINSOCK_DEPRECATED_NO_WARNINGS 添加到里面去,点击应用即可!
TCP
连接过程图:
创建tcp服务器和客户端都是按照上图的步骤来操作的!
1). 服务器
-
初始化套接字库 对应图中socket() WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
-
创建tcp套接字 对应图中socket()
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
-
绑定到本机 对应图中bind()
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
-
监听 对应图中listen()
listen(sockSrv, 10);
-
接收连接请求,返回针对客户端的套接字 对应图中accept() SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);
-
发送数据 对应图中write() sprintf_s(sendBuf, "hello client!\n");
int iSend = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
-
接收数据 对应图中read() recv(sockConn, recvBuf, 100, 0);
-
关闭套接字 对应图中close() closesocket(sockConn);
-
清理套接字库 WSACleanup();
具体实现代码:
#include <iostream>
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(void) {
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (retVal == SOCKET_ERROR) {
printf("Failed bind:%d\n", WSAGetLastError());
return -1;
}
if (listen(sockSrv, 10) == SOCKET_ERROR) {
printf("Listen failed:%d", WSAGetLastError());
return -1;
}
std::cout << "Server start at port: 6000" << std::endl;
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
char recvBuf[100];
char sendBuf[100];
while (1) {
SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);
if (sockConn == SOCKET_ERROR) {
std::cout << "Accept failed: " << WSAGetLastError() << std::endl;
break;
}
std::cout << "Accept client IP: " << inet_ntoa(addrCli.sin_addr) << std::endl;
sprintf_s(sendBuf, "hello client!\n");
int iSend = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
if (iSend == SOCKET_ERROR) {
std::cout << "send failed!\n";
break;
}
recv(sockConn, recvBuf, 100, 0);
std::cout << recvBuf << std::endl;
closesocket(sockConn);
}
closesocket(sockSrv);
WSACleanup();
return 0;
}
2). 客户端
-
初始化套接字库 对应图中socket() WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
-
连接服务器 对应图中connect()
int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
-
发送数据到服务器 对应图中write() char sendBuf[] = "你好,服务器,我是客户端!";
send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
-
接收服务器的数据 对应图中read() char recvBuf[100];
recv(sockCli, recvBuf, sizeof(recvBuf), 0);
-
关闭套接字并清除套接字库 对应图中close() closesocket(sockCli);
WSACleanup();
具体实现代码:
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(void) {
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_port = htons(6000);
addrSrv.sin_family = AF_INET;
int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (err_log == 0) {
printf("连接服务器成功!\n");
} else {
printf("连接服务器失败!\n");
return -1;
}
char recvBuf[100];
char sendBuf[] = "你好,服务器,我是客户端!";
send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
recv(sockCli, recvBuf, sizeof(recvBuf), 0);
std::cout << recvBuf << std::endl;
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
运行效果:
3). TCP聊天小项目
下面是根据上面的代码修改的一个聊天小项目(使用到了多线程)
只有一个服务器,服务器一直开启等待客户端连接; 客户都安可以开启多个,且可以一直连续的与服务器进行发送接收消息; 服务器给客户端发送数据,得通过1 - 9来区分到底给那个客户端发送消息,例如给第二个客户端发送消息:2你好,客户端 客户端那边接收到的数据是:你好,客户端
服务器代码:
#include <iostream>
#include <WinSock2.h>
#include <stdio.h>
#include <Windows.h>
#include <process.h>
#include <vector>
#include <conio.h>
#include <string.h>
#include <string>
#pragma comment(lib, "ws2_32.lib")
SOCKET sockSrv;
std::vector<SOCKET> vec_sockConn;
std::vector<SOCKADDR_IN> vec_sockaddr_in;
std::vector<int> vec_sockIndex;
typedef struct SERVER_CLIENT {
SOCKET server;
SOCKADDR_IN client;
int clientIndex;
}SC;
bool IsSocketClosed(SOCKET clientSocket) {
bool ret = false;
HANDLE closeEvent = WSACreateEvent();
WSAEventSelect(clientSocket, closeEvent, FD_CLOSE);
DWORD dwRet = WaitForSingleObject(closeEvent, 0);
if (dwRet == WSA_WAIT_EVENT_0)
ret = true;
else if (dwRet == WSA_WAIT_TIMEOUT)
ret = false;
WSACloseEvent(closeEvent);
return ret;
}
unsigned int WINAPI ThreadAccept(LPVOID p) {
static int i = 0;
while (1) {
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);
if (sockConn == SOCKET_ERROR) {
printf("Accept failed:%d", WSAGetLastError());
}
vec_sockIndex.emplace_back(i++);
vec_sockaddr_in.emplace_back(addrCli);
vec_sockConn.emplace_back(sockConn);
printf("\033[0;%d;40m客户端[%d]上线\033[0m\n", 31, i);
}
return 0;
}
unsigned int WINAPI _ThreadRecv(LPVOID p) {
char recvBuf[100];
memset(recvBuf, 0, 100);
SC _sc = *(SC *)p;
while (1) {
Sleep(20);
if (IsSocketClosed(_sc.server) == true) {
printf("客户端 [%d] 断开连接!\n", _sc.clientIndex + 1);
break;
}
recv(_sc.server, recvBuf, 100, 0);
if (strlen(recvBuf) == 0) {
continue;
}
printf("接收到客户端 [%d] 的消息:%s\n", _sc.clientIndex + 1, recvBuf);
memset(recvBuf, 0, 100);
}
return 0;
}
unsigned int WINAPI ThreadRecv(LPVOID p) {
static int index = 0;
while (1) {
if (vec_sockConn.size() == 0) {
continue;
}
if (vec_sockConn.size() == index) {
continue;
}
SC sc;
sc.server = vec_sockConn.at(index);
sc.client = vec_sockaddr_in.at(index);
sc.clientIndex = vec_sockIndex.at(index);
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, _ThreadRecv, (void *)&sc, 0, NULL);
index++;
Sleep(20);
}
return 0;
}
int main(void) {
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (retVal == SOCKET_ERROR) {
printf("Failed bind:%d\n", WSAGetLastError());
return -1;
}
if (listen(sockSrv, 10) == SOCKET_ERROR) {
printf("Listen failed:%d", WSAGetLastError());
return -1;
}
std::cout << "Server start at port: 6000" << std::endl;
HANDLE hThread_1 = (HANDLE)_beginthreadex(NULL, 0, ThreadAccept, NULL, 0, NULL);
HANDLE hThread_2 = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, NULL, 0, NULL);
char sendBuf[100];
while (1) {
char c = getchar();
scanf_s("%s", sendBuf, 100);
if (strlen(sendBuf) == 0) {
printf("输入内容为空或者超长!\n");
}
if (c < '1' || c > '9' || vec_sockConn.size() == 0 || c - '0' >= vec_sockConn.size() + 1) {
while ((c = getchar()) != '\n');
memset(sendBuf, 0, 100);
printf("输入内容不符合规则!\n");
continue;
}
int index = --c - '0';
int iSend = send(vec_sockConn.at(index) , sendBuf, strlen(sendBuf) + 1, 0);
if (iSend == SOCKET_ERROR) {
std::cout << "send failed!\n";
break;
}
memset(sendBuf, 0, 100);
while ((c = getchar()) != '\n');
}
std::vector<SOCKET>::iterator it = vec_sockConn.begin();
for (; it != vec_sockConn.end(); it++) {
closesocket((SOCKET)(*it));
}
WaitForSingleObject(hThread_1, INFINITE);
WaitForSingleObject(hThread_2, INFINITE);
CloseHandle(hThread_1);
CloseHandle(hThread_2);
closesocket(sockSrv);
WSACleanup();
return 0;
}
客户端:
#include <iostream>
#include <WinSock2.h>
#include <process.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
SOCKET sockCli;
bool IsSocketClosed(SOCKET clientSocket) {
bool ret = false;
HANDLE closeEvent = WSACreateEvent();
WSAEventSelect(clientSocket, closeEvent, FD_CLOSE);
DWORD dwRet = WaitForSingleObject(closeEvent, 0);
if (dwRet == WSA_WAIT_EVENT_0)
ret = true;
else if (dwRet == WSA_WAIT_TIMEOUT)
ret = false;
WSACloseEvent(closeEvent);
return ret;
}
unsigned int WINAPI ThreadRecv(LPVOID p) {
char recvBuf[100];
memset(recvBuf, 0, 100);
while (1) {
Sleep(20);
if (IsSocketClosed(sockCli) == true) {
printf("服务器 断开连接!\n");
break;
}
recv(sockCli, recvBuf, sizeof(recvBuf), 0);
if (strlen(recvBuf) == 0) continue;
std::cout << recvBuf << std::endl;
memset(recvBuf, 0, 100);
}
return 0;
}
int main(void) {
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
sockCli = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_port = htons(6000);
addrSrv.sin_family = AF_INET;
int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (err_log == 0) {
printf("连接服务器成功!\n");
} else {
printf("连接服务器失败!\n");
return -1;
}
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, NULL, 0, NULL);
char sendBuf[100];
while (1) {
scanf_s("%s", sendBuf, 100);
send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
memset(sendBuf, 0, 100);
char c;
while ((c = getchar()) != '\n');
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
运行效果:
UDP
UDP就比较简单了,步骤比tcp要少一些。
连接过程图:
1). 服务器
-
初始化套接字库 WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
-
创建套接字 SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
-
绑定
bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
-
接收数据 char recvBuf[100];
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR *)&addrCli, &len);
-
发送数据 char sendBuf[] = "hello Client,I'm Server!\n";
sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrCli, len);
-
关闭 closesocket(sockSrv);
WSACleanup();
具体实现代码:
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
int main(void) {
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6001);
bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
char sendBuf[] = "hello Client,I'm Server!\n";
char recvBuf[100];
std::cout << "start UDP server with port 6001" << std::endl;
while (1) {
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR *)&addrCli, &len);
std::cout << "Recv:" << recvBuf << std::endl;
sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrCli, len);
std::cout << "Send:" << sendBuf << std::endl;
}
closesocket(sockSrv);
WSACleanup();
return 0;
}
2). 客户端
-
初始化套接字库 WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
-
创建UDP套接字 SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
-
接收数据 char recvBuf[100];
recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR *)&addrCli, &len);
-
发送数据 char sendBuf[] = "hello Client,I'm Server!\n";
sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrSrv, len);
-
关闭 closesocket(sockSrv);
WSACleanup();
具体实现代码:
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
int main(void) {
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6001);
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
char sendBuf[] = "hello, I'm Client!\n";
char recvBuf[100];
std::cout << "send to Server: " << sendBuf << std::endl;
sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrSrv, len);
recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR *)&addrCli, &len);
std::cout << "recv from: " << recvBuf << std::endl;
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
运行效果:
总结
socket的具体细节用法我不太清楚,现阶段也只是熟悉TCP的一些简单操作,UDP的话也还不是太懂,不懂的是不知道在具体项目中该如何进行使用它们。 那个TCP的小项目也只是自己琢磨搞出来的,不知掉具体项目会不会这样去写!
|