以下内容来源于B站视频https://www.bilibili.com/video/BV1j4411S7jg?p=18学习资源
仅将学习笔记记录如下供以后查阅及大家分享,欢迎讨论与批评指正
网络通信基础
1. tcp通信的客户端和服务端
建立客户端的步骤
1. 建立通信套接字socket -- 类似读写文件中的File*
2. 连接服务端 -- fopen 打开文件
3. 向服务端发送数据 -- fwrite
4. 接收服务端数据 -- fread
5. 关闭socket -- fclose
建立服务端的步骤
1. 创建socket
2. 绑定端口
3. 监听网络端口
4. 等待客户端连接(则塞)
5. 接收客户端数据
6. 向客户端发送数据
7. 关闭socket
2. winsocket创建客户端和服务端
-
头文件 <WinSock2.h> 放在<windows.h>之前
-
宏重定义,winsock第一版和第二版的宏重定义了 #define WIN32_LEAN_AND_MEAN
-
引入库文件 ws2_32.lib
-
服务端代码(循环接收客户端连接,一旦连接上,向客户端发送一条消息)
// 启动windows socket网络环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); // 创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { std::cout << "create sock failed" << std::endl; } // 绑定端口 sockaddr_in sin = {}; sin.sin_family = AF_INET; // IPV4 sin.sin_port = htons(10086); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY auto res = bind(sock, (sockaddr*)&sin, sizeof(sin)); if (res == SOCKET_ERROR) { std::cout << "bind port error!" << std::endl; } // 监听端口 res = listen(sock, 5); if (res == SOCKET_ERROR) { std::cout << "listen port error!" << std::endl; } std::cout << "wait for client in" << std::endl; // 等待客户端连接 sockaddr_in clientAddr = {}; int nAddrLen = sizeof(clientAddr); SOCKET clientSock = INVALID_SOCKET; char msgBuf[] = "hello"; // 循环等待客户端连接 while (1) { clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen); if (clientSock == INVALID_SOCKET) { std::cout << "receive client socket failed!" << std::endl; } std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl; // 向服务端发送数据 send(clientSock, msgBuf, strlen(msgBuf) + 1, 0); } // 关闭套接字 closesocket(sock); // 清除windows socket环境 WSACleanup();
-
客户端代码(连接服务器并接收服务端发送的一条消息)
// 启动windows socket网络环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); // 创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { std::cout << "create socket failed" << std::endl; } // 连接服务器 sockaddr_in sin = {}; sin.sin_family = AF_INET; sin.sin_port = htons(10086); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in)); if (res == SOCKET_ERROR) { std::cout << "connect server failed" << std::endl; } // 接收服务器发送的消息 char buf[1024] = { 0 }; int len = recv(sock, buf, 1024, 0); if (len > 0) { std::cout << "data from server is" << buf << std::endl; } // 关闭套接字 closesocket(sock); WSACleanup(); std::cin.get();
-
服务端改造1(只等待一个客户端接入,循环等待客户端发送数据,处理数据并响应数据给客户端)
// 启动windows socket网络环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); // 创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { std::cout << "create sock failed" << std::endl; } // 绑定端口 sockaddr_in sin = {}; sin.sin_family = AF_INET; // IPV4 sin.sin_port = htons(10086); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY auto res = bind(sock, (sockaddr*)&sin, sizeof(sin)); if (res == SOCKET_ERROR) { std::cout << "bind port error!" << std::endl; } // 监听端口 res = listen(sock, 5); if (res == SOCKET_ERROR) { std::cout << "listen port error!" << std::endl; } std::cout << "wait for client in" << std::endl; // 等待客户端连接 sockaddr_in clientAddr = {}; int nAddrLen = sizeof(clientAddr); SOCKET clientSock = INVALID_SOCKET; char msgBuf[] = "hello"; // 等待客户端连接 clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen); if (clientSock == INVALID_SOCKET) { std::cout << "receive client socket failed!" << std::endl; } std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl; while (1) { int len = recv(clientSock, msgBuf, 1024, 0); if (len <= 0) { std::cout << "receive from client failed" << std::endl; break; } std::cout << "receive msg is : " << msgBuf << std::endl; std::string resStr = ""; Handle(msgBuf, resStr); // 向服务端发送数据 send(clientSock, resStr.c_str(), resStr.length() + 1, 0); } // 关闭套接字 closesocket(sock); // 清除windows socket环境 WSACleanup(); std::cout << "session end" << std::endl; std::cin.get();
-
客户端改造1(连接成功后,循环输入命令发送给服务端,并接收服务端返回的消息并处理,即打印)
// 启动windows socket网络环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); // 创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { std::cout << "create socket failed" << std::endl; } // 连接服务器 sockaddr_in sin = {}; sin.sin_family = AF_INET; sin.sin_port = htons(10086); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in)); if (res == SOCKET_ERROR) { std::cout << "connect server failed" << std::endl; } std::cout << "connect server succeed" << std::endl; // 输入命令发送给服务器并接收返回的消息 char buf[1024] = { 0 }; while (true) { std::cout << "请输入命令:" << std::endl; std::cin >> buf; if (strcmp(buf, "exit") == 0) { std::cout << "receive end command" << std::endl; break; } send(sock, buf, strlen(buf) + 1, 0); // 接收响应 int len = recv(sock, buf, 1024, 0); if (len > 0) { std::cout << "data from server is " << buf << std::endl; } } // 关闭套接字 closesocket(sock); WSACleanup(); std::cout << "session end" << std::endl; std::cin.get();
结构化消息数据
-
结构体 struct(一次性将多个信息返回给客户端)
typedef struct DataPackage_STRU { int age; char name[32]; } DataPackage; // 单纯的结构作为报文时,客户端发送异常数据,服务器也会进行处理,未进行过滤
-
网络数据报文
1. 一个报文包含包头和包体 2. 包头: 描述消息包的大小及数据的作用以及发送者,接收者等 3. 包体: 实际使用的数据buf // 服务端接收两次消息,先接收消息头,判断消息是否有效,有效的话继续接收消息体 // 服务端返回消息时,也是两次发送,先发送消息头,再发送消息体
// 定义两种有效命令,每种命令对应相应的消息体 enum CmdIndex { LOGIN_IN, LOGIN_OUT, LOGIN_ERROR }; // LOGIN_IN命令对应的消息体结构 typedef struct LoginIn_STRU { char userName[32]; char pwd[32]; } LoginIn; typedef struct LoginInRes_STRU { int res; } LoginInRes; // LOGIN_OUT命令对应的消息体结构 typedef struct LoginOut_STRU { char userName[32]; } LoginOut; typedef struct LoginOutRes_STRU { int res; } LoginOutRes; // 消息头 typedef struct DataHeader_STRU { unsigned short dataLen; unsigned short cmdId; } DataHeader;
-
服务端报文改造代码
// 启动windows socket网络环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); // 创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { std::cout << "create sock failed" << std::endl; } // 绑定端口 sockaddr_in sin = {}; sin.sin_family = AF_INET; // IPV4 sin.sin_port = htons(10086); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY auto res = bind(sock, (sockaddr*)&sin, sizeof(sin)); if (res == SOCKET_ERROR) { std::cout << "bind port error!" << std::endl; } // 监听端口 res = listen(sock, 5); if (res == SOCKET_ERROR) { std::cout << "listen port error!" << std::endl; } std::cout << "wait for client in" << std::endl; // 等待客户端连接 sockaddr_in clientAddr = {}; int nAddrLen = sizeof(clientAddr); SOCKET clientSock = INVALID_SOCKET; char msgBuf[] = "hello"; // 等待客户端连接 clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen); if (clientSock == INVALID_SOCKET) { std::cout << "receive client socket failed!" << std::endl; } std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl; DataPackage dataObj; while (1) { // 先接收消息头 DataHeader header; int len = recv(clientSock, (char*)&header, sizeof(header), 0); if (len <= 0) { std::cout << "receive from client failed" << std::endl; break; } std::cout << "receive msg header is : " << header.cmdId << std::endl; switch (header.cmdId) { case LOGIN_IN: { // 接收消息体 LoginIn data; recv(clientSock, (char*)&data, sizeof(data), 0); // 返回消息头 send(clientSock, (char*)&header, sizeof(header), 0); // 返回消息体 LoginInRes resData; resData.res = 1; send(clientSock, (char*)&resData, sizeof(resData), 0); } break; case LOGIN_OUT: { // 接收消息体 LoginOut data; recv(clientSock, (char*)&data, sizeof(data), 0); // ... handle // 返回消息头 send(clientSock, (char*)&header, sizeof(header), 0); // 返回消息体 LoginOutRes resData; resData.res = 1; send(clientSock, (char*)&resData, sizeof(resData), 0); } default: // 返回消息头 header.cmdId = LOGIN_ERROR; header.dataLen = 0; send(clientSock, (char*)&header, sizeof(header), 0); break; } } // 关闭套接字 closesocket(sock); // 清除windows socket环境 WSACleanup(); std::cout << "session end" << std::endl; std::cin.get();
-
客户端报文改造代码(使用同一套报文定义)
// 启动windows socket网络环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); // 创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { std::cout << "create socket failed" << std::endl; } // 连接服务器 sockaddr_in sin = {}; sin.sin_family = AF_INET; sin.sin_port = htons(10086); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in)); if (res == SOCKET_ERROR) { std::cout << "connect server failed" << std::endl; } std::cout << "connect server succeed" << std::endl; // 输入命令发送给服务器并接收返回的消息 char buf[1024] = { 0 }; DataPackage data; while (true) { std::cout << "请输入命令:" << std::endl; std::cin >> buf; if (strcmp(buf, "exit") == 0) { std::cout << "receive end command" << std::endl; break; } else if(strcmp(buf, "loginin") == 0) { LoginIn data = { "zhanghu", "mima" }; DataHeader header; header.cmdId = LOGIN_IN; header.dataLen = sizeof(LoginIn); send(sock, (char*)&header, sizeof(DataHeader), 0); send(sock, (char*)&data, sizeof(LoginIn), 0); // 接收数据 DataHeader resHeader = {}; LoginInRes resData = {}; recv(sock, (char*)&resHeader, sizeof(DataHeader), 0); recv(sock, (char*)&resData, sizeof(LoginInRes), 0); std::cout << resData.res << std::endl; } else if(strcmp(buf, "loginout") == 0) { LoginOut data = { "zhanghu"}; DataHeader header; header.cmdId = LOGIN_OUT; header.dataLen = sizeof(LoginOut); send(sock, (char*)&header, sizeof(DataHeader), 0); send(sock, (char*)&data, sizeof(LoginOut), 0); // 接收数据 DataHeader resHeader = {}; LoginOutRes resData = {}; recv(sock, (char*)&resHeader, sizeof(DataHeader), 0); recv(sock, (char*)&resData, sizeof(LoginOutRes), 0); std::cout << resData.res << std::endl; } else { std::cout << "not support cmd, please reentry:" << std::endl; } } // 关闭套接字 closesocket(sock); WSACleanup(); std::cout << "session end" << std::endl; std::cin.get();
-
-
合并包头和包体,一次发送和接收
// 使用继承包头的方法合并 // 客户端只发送一次,服务端需要分两次接收,先接收包头,再做偏移地址接收包体
// 服务端 // 启动windows socket网络环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); // 创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { std::cout << "create sock failed" << std::endl; } // 绑定端口 sockaddr_in sin = {}; sin.sin_family = AF_INET; // IPV4 sin.sin_port = htons(10086); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY auto res = bind(sock, (sockaddr*)&sin, sizeof(sin)); if (res == SOCKET_ERROR) { std::cout << "bind port error!" << std::endl; } // 监听端口 res = listen(sock, 5); if (res == SOCKET_ERROR) { std::cout << "listen port error!" << std::endl; } std::cout << "wait for client in" << std::endl; // 等待客户端连接 sockaddr_in clientAddr = {}; int nAddrLen = sizeof(clientAddr); SOCKET clientSock = INVALID_SOCKET; char msgBuf[] = "hello"; // 等待客户端连接 clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen); if (clientSock == INVALID_SOCKET) { std::cout << "receive client socket failed!" << std::endl; } std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl; while (1) { // 先接收消息头 DataHeader header; int len = recv(clientSock, (char*)&header, sizeof(header), 0); if (len <= 0) { std::cout << "receive from client failed" << std::endl; break; } std::cout << "receive msg header is : " << header.cmdId << std::endl; switch (header.cmdId) { case LOGIN_IN: { // 接收消息体 LoginIn data; recv(clientSock, (char*)&data + sizeof(DataHeader), sizeof(data) - sizeof(DataHeader), 0); // 返回消息 LoginInRes resData; send(clientSock, (char*)&resData, sizeof(resData), 0); } break; case LOGIN_OUT: { // 接收消息体 LoginOut data; recv(clientSock, (char*)&data + sizeof(DataHeader), sizeof(data) - sizeof(DataHeader), 0); // ... handle // 返回消息 LoginOutRes resData; send(clientSock, (char*)&resData, sizeof(resData), 0); } default: // 返回消息头 header.cmdId = LOGIN_ERROR; header.dataLen = 0; send(clientSock, (char*)&header, sizeof(header), 0); break; } } // 关闭套接字 closesocket(sock); // 清除windows socket环境 WSACleanup(); std::cout << "session end" << std::endl; std::cin.get();
// 客户端 // 启动windows socket网络环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); // 创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { std::cout << "create socket failed" << std::endl; } // 连接服务器 sockaddr_in sin = {}; sin.sin_family = AF_INET; sin.sin_port = htons(10086); sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in)); if (res == SOCKET_ERROR) { std::cout << "connect server failed" << std::endl; } std::cout << "connect server succeed" << std::endl; // 输入命令发送给服务器并接收返回的消息 char buf[1024] = { 0 }; DataPackage data; while (true) { std::cout << "请输入命令:" << std::endl; std::cin >> buf; if (strcmp(buf, "exit") == 0) { std::cout << "receive end command" << std::endl; break; } else if(strcmp(buf, "loginin") == 0) { LoginIn data; std::string userName = "zhangsan"; memcpy(data.userName, userName.c_str(), userName.length() + 1); std::string pwd = "123456"; memcpy(data.pwd, pwd.c_str(), pwd.length() + 1); send(sock, (char*)&data, sizeof(LoginIn), 0); // 接收数据 LoginInRes resData = {}; recv(sock, (char*)&resData, sizeof(LoginInRes), 0); std::cout << "res cmd is "<< resData.cmdId << " res is " << resData.res << std::endl; } else if(strcmp(buf, "loginout") == 0) { LoginOut data; std::string userName = "zhangsan"; memcpy(data.userName, userName.c_str(), userName.length() + 1); send(sock, (char*)&data, sizeof(LoginOut), 0); // 接收数据 LoginOutRes resData = {}; recv(sock, (char*)&resData, sizeof(LoginOutRes), 0); std::cout << "res cmd is " << resData.cmdId << " res is " << resData.res << std::endl; } else { std::cout << "not support cmd, please reentry:" << std::endl; } } // 关闭套接字 closesocket(sock); WSACleanup(); std::cout << "session end" << std::endl; std::cin.get(); return 0;
-
网络消息长度的问题
1. 固长数据/变长数据 2. 粘包/拆包 3. 分包/组包
3. socket的select网络模型(处理多客户端场景)
// select的原理: 将多个套接字放在一个集合里,统一检查集合中套接字的状态(可读,可写,异常),调用、select后,会更新集合中套接字的状态,如果可读,即可执行fdRead中的socket,依次避免多个客户端时阻塞,当没有数据写入或连接时,select会阻塞,可设置等待时间timeout(s)
// 伯克利 socket
// 参数1 nfds: 在windows下无作用,整数值,fd_set中所有socket描述符的范围,而不是数量
// 即描述符最大值 + 1
// 参数2 readfds: 需要查询的套接字集合
// 参数3 writefds:
// 参数4 exceptfds:
// 参数5 timeout:
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;
FD_ZERO(&fdRead); // 清空集合
FD_ZERO(&fdWrite); // 清空集合
FD_ZERO(&fdExp); // 清空集合
FD_SET(sock, &fdRead);
FD_SET(sock, &fdWrite);
FD_SET(sock, &fdExp);
select(sock+1, &fdRead, &fdWrite, &fdExp, NULL)
报文结构定义
// 定义两种有效命令,每种命令对应相应的消息体
enum CmdIndex
{
LOGIN_IN,
LOGIN_OUT,
LOGIN_IN_RES,
LOGIN_OUT_RES,
NEW_JOIN,
LOGIN_ERROR
};
// 消息头
struct DataHeader
{
unsigned short dataLen;
unsigned short cmdId;
};
struct LoginNew : public DataHeader
{
LoginNew()
{
dataLen = sizeof(LoginNew);
cmdId = NEW_JOIN;
}
int socket;
};
// LOGIN_IN命令对应的消息体结构
struct LoginIn : public DataHeader
{
LoginIn()
{
dataLen = sizeof(LoginIn);
cmdId = LOGIN_IN;
}
char userName[32];
char pwd[32];
};
struct LoginInRes : public DataHeader
{
LoginInRes()
{
dataLen = sizeof(LoginInRes);
cmdId = LOGIN_IN_RES;
}
int res{ 1 };
};
// LOGIN_OUT命令对应的消息体结构
struct LoginOut : public DataHeader
{
LoginOut()
{
dataLen = sizeof(LoginOut);
cmdId = LOGIN_OUT;
}
char userName[32];
};
struct LoginOutRes : public DataHeader
{
LoginOutRes()
{
dataLen = sizeof(LoginOutRes);
cmdId = LOGIN_OUT_RES;
}
int res{ 1 };
};
支持多客户端连接服务端改造代码(利用select模型实现多客户端连接,设置time_val,使select不阻塞,当有新客户端接入时,向已存在的客户端广播消息,消息处理过程:现获取消息头,再解析消息体,并返回消息)
// 启动windows socket网络环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
// 创建socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET)
{
std::cout << "create sock failed" << std::endl;
}
// 绑定端口
sockaddr_in sin = {};
sin.sin_family = AF_INET; // IPV4
sin.sin_port = htons(10086);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY
auto res = bind(sock, (sockaddr*)&sin, sizeof(sin));
if (res == SOCKET_ERROR)
{
std::cout << "bind port error!" << std::endl;
}
// 监听端口
res = listen(sock, 5);
if (res == SOCKET_ERROR)
{
std::cout << "listen port error!" << std::endl;
}
while (1)
{
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;
FD_ZERO(&fdRead); // 清空集合
FD_ZERO(&fdWrite); // 清空集合
FD_ZERO(&fdExp); // 清空集合
FD_SET(sock, &fdRead);
FD_SET(sock, &fdWrite);
FD_SET(sock, &fdExp);
// 把新加入的客户端加入可读的集合
for (int i = 0; i < vecSockets.size(); ++i)
{
FD_SET(vecSockets[i], &fdRead);
}
time_val
int ret = select(sock + 1, &fdRead, &fdWrite, &fdExp, NULL);
if(ret < 0)
{
break;
}
// 为了accept不发生阻塞, 现在fd_set中判断sock的状态是否可读
if (FD_ISSET(sock, &fdRead))
{
FD_CLR(sock, &fdRead);
std::cout << "wait for client in" << std::endl;
// 等待客户端连接
sockaddr_in clientAddr = {};
int nAddrLen = sizeof(clientAddr);
SOCKET clientSock = INVALID_SOCKET;
char msgBuf[] = "hello";
// 等待客户端连接
clientSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
if (clientSock == INVALID_SOCKET)
{
std::cout << "receive client socket failed!" << std::endl;
}
std::cout << "client ip: " << inet_ntoa(clientAddr.sin_addr) << std::endl;
vecSockets.push_back(clientSock);
}
for (int i = 0; i < fdRead.fd_count; i++)
{
if (-1 == HandleMessage(fdRead.fd_array[i]))
{
auto itr = std::find(vecSockets.begin(), vecSockets.end(), fdRead.fd_array[i]);
if (itr != vecSockets.end())
{
vecSockets.erase(itr);
}
}
}
}
// 关闭套接字
for (int i = 0; i < vecSockets.size(); ++i)
{
closesocket(vecSockets[i]);
}
closesocket(sock);
// 清除windows socket环境
WSACleanup();
std::cout << "session end" << std::endl;
std::cin.get();
目前的客户端只能输入命令后,发送一条消息,并接收一条响应消息,无法接收服务器主动推送的消息,
利用select模型实现客户端接收服务器推送消息
// 启动windows socket网络环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
// 创建socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
std::cout << "create socket failed" << std::endl;
}
// 连接服务器
sockaddr_in sin = {};
sin.sin_family = AF_INET;
sin.sin_port = htons(10086);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
auto res = connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
if (res == SOCKET_ERROR)
{
std::cout << "connect server failed" << std::endl;
}
std::cout << "connect server succeed" << std::endl;
while (true)
{
fd_set fdRead;
FD_ZERO(&fdRead);
FD_SET(sock, &fdRead);
timeval t = { 1, 0 };
int ret = select(sock + 1, &fdRead, 0, 0, &t);
if (ret < 0)
{
std::cout << "select 任务结束";
break;
}
if (FD_ISSET(sock, &fdRead))
{
FD_CLR(sock, &fdRead);
if (-1 == HandleMessage(sock))
{
std::cout << "ending" << std::endl;
break;
}
}
std::cout << "spare time,you can do something" << std::endl;
}
// 关闭套接字
closesocket(sock);
WSACleanup();
std::cout << "session end" << std::endl;
std::cin.get();
客户端封装
将客户端封装成库
message
#ifndef COMMON_MESSAGE_DEF_
#define COMMON_MESSAGE_DEF_
typedef struct DataPackage_STRU
{
int age;
char name[32];
} DataPackage;
// 定义两种有效命令,每种命令对应相应的消息体
enum CmdIndex
{
LOGIN_IN,
LOGIN_OUT