以下内容来源于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,
LOGIN_IN_RES,
LOGIN_OUT_RES,
NEW_JOIN,
LOGIN_ERROR
};
// 消息头
struct DataHeader
{
unsigned short dataLen;
unsigned short cmdId;
};
// 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 };
};
struct LoginNew : public DataHeader
{
LoginNew()
{
dataLen = sizeof(LoginNew);
cmdId = NEW_JOIN;
}
int socket;
};
#endif // !COMMON_MESSAGE_DEF_
头文件
#ifndef EASY_CLIENT_H_
#define EASY_CLIENT_H_
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#define SOCKET int
#define INVALID_SOCKET (SOCKET)(-0)
#define SOCKET_ERROR (-1)
#endif // _WIN32
#define ExportDll __declspec(dllexport)
class ExportDll EasyClient
{
public:
EasyClient();
virtual ~EasyClient();
// 初始化环境及socket
void InitSocket();
// 连接服务器
int Connect(const char* ip, const unsigned short port);
// 关闭socket
void CloseSocket();
int SendData(DataHeader* header);
int RecvData();
bool IsRunning();
// 其他
// 发送消息
// 接收消息
// 处理消息
bool HandleMessage();
private:
void ProcessMessage(DataHeader* header);
private:
SOCKET m_sock;
};
#endif // !EASY_CLIENT_H_
源文件
#include <iostream>
#include "common_message_def.h"
#include "easy_client.h"
EasyClient::EasyClient()
{
m_sock = INVALID_SOCKET;
}
EasyClient::~EasyClient()
{
CloseSocket();
}
void EasyClient::InitSocket()
{
#ifdef _WIN32
// 启动windows socket环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
#endif // _WIN32
// 若已经初始化过
if (INVALID_SOCKET != m_sock)
{
std::cout << "m_sock已有连接,先关闭" << std::endl;
CloseSocket();
}
// 建立socket
m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == m_sock)
{
std::cout << "error, create socket failed..." << std::endl;
}
else
{
std::cout << "create socket success..." << std::endl;
}
}
int EasyClient::Connect(const char* ip, const unsigned short port)
{
if (INVALID_SOCKET == m_sock)
{
std::cout << "m_sock无效,是否忘记初始化,这里重新初始化" << std::endl;
InitSocket();
}
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
#ifdef _WIN32
sin.sin_addr.S_un.S_addr = inet_addr(ip);
#else
sin.sin_addr.s_addr = inet_addr(ip);
#endif // _WIN32
int ret = connect(m_sock, (sockaddr*)&sin, sizeof(sockaddr_in));
if (SOCKET_ERROR == ret)
{
std::cout << "error, connect server failed..." << std::endl;
}
else
{
std::cout << "connect server succeed..." << std::endl;
}
return ret;
}
void EasyClient::CloseSocket()
{
if (INVALID_SOCKET != m_sock)
{
#ifdef _WIN32
closesocket(m_sock);
WSACleanup();
#else
close(m_sock);
#endif // _WIN32
}
m_sock = INVALID_SOCKET;
}
bool EasyClient::HandleMessage()
{
if (IsRunning())
{
fd_set fdReads;
FD_ZERO(&fdReads);
FD_SET(m_sock, &fdReads);
timeval t = {
1, 0 };
int ret = select(m_sock + 1, &fdReads, 0, 0, &t);// select模型 判断socket范围内是否有可读的数据
if (ret < 0)
{
std::cout << "[socket=" << ret << "]select任务结束.." << std::endl;
return false;
}
if (FD_ISSET(m_sock, &fdReads))
{
FD_CLR(m_sock, &fdReads);
if (-1 == RecvData())
{
std::cout << "[socket=" << ret << "]select任务结束.." << std::endl;
return false;
}
}
return true;
}
return false;
}
bool EasyClient::IsRunning()
{
return m_sock != INVALID_SOCKET;
}
int EasyClient::RecvData()
{
char szRecv[4096] = {
0 };
int len = recv(m_sock, szRecv, sizeof(DataHeader), 0);
if (len <= 0)
{
std::cout << "receive from client failed" << std::endl;
return -1;
}
DataHeader* header = (DataHeader*)szRecv;
std::cout << "client receive msg header is : " << header->cmdId << std::endl;
// 接收消息体
recv(m_sock, szRecv + sizeof(DataHeader), header->dataLen - sizeof(DataHeader), 0);
ProcessMessage(header);
return 0;
}
void EasyClient::ProcessMessage(DataHeader* header)
{
switch (header->cmdId)
{
case LOGIN_IN_RES:
{
// 接收消息体
LoginInRes* data = (LoginInRes*)header;
std::cout << "receive message from server : LOGIN_IN_RES, data length is: " << data->dataLen << std::endl;
}
break;
case LOGIN_OUT_RES:
{
// 接收消息体
LoginOutRes* data = (LoginOutRes*)header;
std::cout << "receive message from server : LOGIN_OUT_RES, data length is: " << data->dataLen << std::endl;
}
break;
case NEW_JOIN:
{
// 接收消息体
LoginNew* data = (LoginNew*)header;
std::cout << "receive message from server : NEW_JOIN, data length is: " << data->dataLen << std::endl;
}
break;
default:
std::cout << "invalid message" << std::endl;
break;
}
}
int EasyClient::SendData(DataHeader* header)
{
if (INVALID_SOCKET == m_sock)
{
return SOCKET_ERROR;
}
return send(m_sock, (char*)header, header->dataLen, 0);
}
客户端应用
#include <iostream>
#include <thread>
#include "../EasyClient//common_message_def.h"
#include "../EasyClient/easy_client.h"
#define _CRT_SECURE_NO_WARNINGS
void HandleSendProc(EasyClient* client)
{
while (true)
{
char cmdBuf[256] = {
0 };
std::cout << "please enter the command" << std:: endl;
std::cin >> cmdBuf;
if (0 == strcmp(cmdBuf, "exit"))
{
client->CloseSocket();
std::cout << "exit send data process thread" << std::endl;
break;
}
else if (0 == strcmp(cmdBuf, "loginin"))
{
LoginIn data;
strcpy(data.userName, "zhangsan");
strcmp(data.pwd, "123456");
client->SendData(&data);
}
else if (0 == strcmp(cmdBuf, "loginout"))
{
LoginOut data;
strcpy(data.userName, "zhangsan");
client->SendData(&data);
}
else
{
std::cout << "not support cmd" << std::endl;
}
}
}
int main()
{
EasyClient client;
client.InitSocket();
client.Connect("127.0.0.1", 10087);
// 创建发送命令线程
std::thread t(HandleSendProc, &client);
t.detach();
// 循环接收消息并处理消息
while (client.IsRunning())
{
client.HandleMessage();
}
client.CloseSocket();
std::cout << "client exit" << std::endl;
std::cin.get();
return 0;
}
服务端封装
头文件
#ifndef EASY_SERVER_H_
#define EASY_SERVER_H_
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#define SOCKET int
#define INVALID_SOCKET (SOCKET)(-0)
#define SOCKET_ERROR (-1)
#endif // _WIN32
#include <vector>
#include "common_message_def.h"
#define ExportDll_Server __declspec(dllexport)
class ExportDll_Server EasyServer
{
public:
EasyServer();
virtual ~EasyServer();
// 初始化Socket
void InitSocket();
// 绑定端口
int BindPort(const char* ip, const unsigned short port);
// 监听端口 num -- 最多可接受的连接的个数
int ListenPort(const int num);
// 接收客户端连接
SOCKET AcceptClient();
// 关闭Socket
void CloseSocket();
// 处理网络消息
bool HandleMessage();
// 是否工作
// 接收数据 丢包,粘包
int RecvData(SOCKET sock);
// 响应网络消息
virtual void RespondMessage(SOCKET sock, DataHeader* header);
// 发送数据
int SendData(SOCKET sock, DataHeader* header);
// 群发消息
void SendDataToAll(DataHeader* header);
bool IsRuning();
private:
SOCKET m_listenSocket;
std::vector<SOCKET> m_vecClientSockets;
};
#endif // !EASY_SERVER_H_
源文件
#include <iostream>
#include "easy_server.h"
EasyServer::EasyServer()
{
m_listenSocket = INVALID_SOCKET;
}
EasyServer::~EasyServer()
{
CloseSocket();
}
void EasyServer::InitSocket()
{
#ifdef _WIN32
// 启动windows socket环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
#endif // _WIN32
// 若已经初始化过
if (INVALID_SOCKET != m_listenSocket)
{
std::cout << "m_sock已有连接,先关闭" << std::endl;
CloseSocket();
}
// 建立socket
m_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == m_listenSocket)
{
std::cout << "error, create listening socket failed..." << std::endl;
}
else
{
std::cout << "create listening socket success..." << std::endl;
}
}
int EasyServer::BindPort(const char* ip, const unsigned short port)
{
if (INVALID_SOCKET == m_listenSocket)
{
InitSocket();
}
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
#ifdef _WIN32
if (ip != nullptr)
{
sin.sin_addr.S_un.S_addr = inet_addr(ip);
}
else
{
sin.sin_addr.S_un.S_addr = INADDR_ANY;
}
#else
if (ip != nullptr)
{
sin.sin_addr.s_addr = inet_addr(ip);
}
else
{
sin.sin_addr.s_addr = INADDR_ANY;
}
#endif // _WIN32
int ret = bind(m_listenSocket, (sockaddr*)&sin, sizeof(sockaddr_in));
if (SOCKET_ERROR == ret)
{
std::cout << "error, bind port"<<port<<" failed." << std::endl;
}
else
{
std::cout << "bind port" << port << " succeed." << std::endl;
}
return ret;
}
int EasyServer::ListenPort(const int num)
{
int ret = listen(m_listenSocket, num);
if (SOCKET_ERROR == ret)
{
std::cout << "error, listen port failed, scok = " << m_listenSocket << std::endl;
}
else
{
std::cout << "listen port succeed, scok = " << m_listenSocket << std::endl;
}
return ret;
}
int EasyServer::RecvData(SOCKET sock)
{
char szRecv[4096] = {
0 };
int nLen = (int)recv(sock, szRecv, sizeof(DataHeader), 0);
DataHeader* header = (DataHeader*)szRecv;
if (nLen <= 0)
{
std::cout << "client: " << (int)sock << " has exit, task ending" << std:: endl;
return -1;
}
// 接收消息体
recv(sock, szRecv + sizeof(DataHeader), header->dataLen - sizeof(DataHeader), 0);
RespondMessage(sock, header);
return 0;
}
int EasyServer::SendData(SOCKET sock, DataHeader* header)
{
if (IsRuning() && header != nullptr)
{
return send(sock, (char*)header, header->dataLen, 0);
}
return SOCKET_ERROR;
}
void EasyServer::SendDataToAll(DataHeader* header)
{
// 向已有的客户端发送加入的消息
for (int i = m_vecClientSockets.size() - 1; i >= 0; --i)
{
SendData(m_vecClientSockets[i], header);
}
}
void EasyServer::RespondMessage(SOCKET sock, DataHeader* header)
{
switch (header->cmdId)
{
case LOGIN_IN:
{
LoginIn* data = (LoginIn*)header;
std::cout << "recv client:" << (int)sock << " request: LOGIN_IN, data length is " << data->dataLen << "username is " << data->userName << "pwd is " << data->pwd << std::endl;
LoginInRes ret;
send(sock, (char*)&ret, sizeof(LoginInRes), 0);
}
break;
case LOGIN_OUT:
{
LoginOut* data = (LoginOut*)header;
std::cout << "recv client:" << (int)sock << " request: LOGIN_OUT, data length is " << data->dataLen << "username is " << data->userName << std::endl;
LoginOutRes ret;
send(sock, (char*)&ret, sizeof(LoginOutRes), 0);
}
break;
default:
{
DataHeader ret = {
0, LOGIN_ERROR };
send(sock, (char*)&ret, sizeof(DataHeader), 0);
}
break;
}
}
bool EasyServer::HandleMessage()
{
if (IsRuning())
{
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;
FD_ZERO(&fdRead);
FD_ZERO(&fdWrite);
FD_ZERO(&fdExp);
FD_SET(m_listenSocket, &fdRead);
FD_SET(m_listenSocket, &fdWrite);
FD_SET(m_listenSocket, &fdExp);
SOCKET maxSock = m_listenSocket;
for (int i = m_vecClientSockets.size() - 1; i >= 0; --i)
{
FD_SET(m_vecClientSockets[i], &fdRead);
if (maxSock < m_vecClientSockets[i])
{
maxSock = m_vecClientSockets[i];
}
}
timeval t = {
1, 0 };
int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);
if (ret < 0)
{
std::cout << "select task ending." << std::endl;
CloseSocket();
return false;
}
if (FD_ISSET(m_listenSocket, &fdRead))
{
FD_CLR(m_listenSocket, &fdRead);
AcceptClient();
}
for (int i = m_vecClientSockets.size() - 1; i >= 0; --i)
{
if (FD_ISSET(m_vecClientSockets[i], &fdRead))
{
if (-1 == RecvData(m_vecClientSockets[i]))
{
auto itr = m_vecClientSockets.begin() + i;
if (itr != m_vecClientSockets.end())
{
m_vecClientSockets.erase(itr);
}
}
}
}
return true;
}
return false;
}
SOCKET EasyServer::AcceptClient()
{
// 用于保存接收的客户端的信息
sockaddr_in clientAddr;
int nAddrLen = sizeof(sockaddr_in);
SOCKET clientSock = INVALID_SOCKET;
#ifdef _WIN32
clientSock = accept(m_listenSocket, (sockaddr*)&clientAddr, &nAddrLen);
#else
clientSock = accept(m_listenSocket, (sockaddr*)&clientAddr, (socklen_t)&nAddrLen);
#endif // _WIN32
if (INVALID_SOCKET == clientSock)
{
std::cout << "error, accept a invalid client, sock = " << m_listenSocket << std::endl;
}
else
{
// 向已有的客户端发送加入的消息
LoginNew data;
SendDataToAll(&data);
m_vecClientSockets.push_back(clientSock);
std::cout << "a new client join, socket = " << (int)clientSock << "ip = " << inet_ntoa(clientAddr.sin_addr) << std::endl;
}
return clientSock;
}
void EasyServer::CloseSocket()
{
if (INVALID_SOCKET != m_listenSocket)
{
#ifdef _WIN32
for (int i = m_vecClientSockets.size() - 1; i >= 0; --i)
{
closesocket(m_vecClientSockets[i]);
}
closesocket(m_listenSocket);
WSACleanup();
#else
for (int i = m_vecClientSockets.size() - 1; i >= 0; --i)
{
close(m_vecClientSockets[i]);
}
close(m_sock);
#endif // _WIN32
}
m_listenSocket = INVALID_SOCKET;
}
bool EasyServer::IsRuning()
{
return true;
}
服务端应用
#include <iostream>
#include "../EasyServer/easy_server.h"
#include "../EasyServer/common_message_def.h"
int main()
{
EasyServer server;
server.InitSocket();
server.BindPort(nullptr, 10087);
server.ListenPort(5);
while (server.IsRuning())
{
server.HandleMessage();
}
server.CloseSocket();
std::cout << "server exit " << std::endl;
std::cin.get();
return 0;
}