最近跟着刘远东老师的《C++百万并发网络通信引擎架构与实现(服务端、客户端、跨平台)》,Bilibili视频地址为C++百万并发网络通信引擎架构与实现(服务端、客户端、跨平台),重新复习下Windows以及Linux、MacOS下的C++网络编程。另外因为最近自己使用boost写了一个TCP服务器压力测试工具,模拟多个客户端设备连接指定的服务器,并定时向服务器推送数据,以测试服务器的并发连接数等,感觉看这个视频收货还蛮大的。

下面是Windows下使用Select模型实现的一个简易TCP服务端和客户端,客户端添加了一个命令输入线程,代码如下:

一、服务端程序代码如下:

// Server.cpp

#include <stdio.h>
#include <iostream>
#include <vector>
#include <algorithm> #define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") using namespace std; // 要考虑字节对齐问题(32位和64位,平台和系统)
enum CMDTYPE
{
CMD_LOGIN, // 登录
CMD_LOGIN_RESULT, // 登录返回结果
CMD_LOGOUT, // 登出
CMD_LOGOUT_RESULT, // 登出返回结果
CMD_NEW_USER_JOIN, // 新用户加入
CMD_ERROR // 错误
}; // 消息头
struct DataHeader
{
int cmd; // 命令类型
int dataLength; // 消息体的数据长度
}; // 消息体
// DataPackage
// 登录
struct Login : public DataHeader
{
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char passWord[32];
}; // 登录结果
struct LoginResult : public DataHeader
{
LoginResult()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN_RESULT;
result = 0;
}
int result;
}; // 登出
struct Logout : public DataHeader
{
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGOUT;
}
char userName[32];
}; // 登出结果
struct LogoutResult : public DataHeader
{
LogoutResult()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
result = 0;
}
int result;
}; // 新用户加入
// 登出
struct NewUserJoin : public DataHeader
{
NewUserJoin()
{
dataLength = sizeof(NewUserJoin);
cmd = CMD_NEW_USER_JOIN;
sock = 0;
}
int sock;
}; std::vector<SOCKET> g_clientList; // 客户端套接字列表 int processor(SOCKET sock)
{
// 缓冲区(4096字节)
char szRecv[4096] = {};
// 5、接收客户端的请求
// 先接收消息头
int recvLen = recv(sock, szRecv, sizeof(DataHeader), 0);
DataHeader *pHeader = (DataHeader*)szRecv;
if (recvLen <= 0)
{
printf("客户端<Socket=%d>已退出,任务结束...", sock);
return -1;
} // 6、处理请求
switch (pHeader->cmd)
{
case CMD_LOGIN:
{
Login *login = (Login*)szRecv; recv(sock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0);
printf("收到客户端<Socket=%d>请求:CMD_LOGIN, 数据长度:%d, userName:%s Password: %s\n",
sock, login->dataLength, login->userName, login->passWord);
// 忽略判断用户名和密码是否正确的过程
LoginResult ret;
send(sock, (char*)&ret, sizeof(LoginResult), 0);
}
break;
case CMD_LOGOUT:
{
Logout *logout = (Logout*)szRecv; recv(sock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0);
printf("收到客户端<Socket=%d>请求:CMD_LOGOUT, 数据长度:%d, userName:%s\n",
sock, logout->dataLength, logout->userName);
LogoutResult ret;
send(sock, (char*)&ret, sizeof(LogoutResult), 0);
}
break;
default:
{
DataHeader header = { 0, CMD_ERROR };
send(sock, (char*)&header, sizeof(header), 0);
break;
}
} return 0;
} int main(int argc, char *agrv[])
{
// 加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err; wVersionRequested = MAKEWORD(2, 2);
// 启动Windows Socket 2.x环境
err = WSAStartup(wVersionRequested, &wsaData); // 使用Socket API建立简易的TCP服务端
if (err != 0) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
return 1;
} /* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */ if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
WSACleanup();
return 1;
} /* The WinSock DLL is acceptable. Proceed. */
//----------------------
// Create a SOCKET for listening for
// incoming connection requests.
SOCKET ListenSocket;
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
WSACleanup();
return 1;
} //----------------------
// The sockaddr_in structure specifies the address family,
// IP address, and port for the socket that is being bound.
sockaddr_in service;
service.sin_family = AF_INET;
//service.sin_addr.s_addr = inet_addr("127.0.0.1");
service.sin_addr.s_addr = INADDR_ANY;
service.sin_port = htons(27015); if (bind(ListenSocket,
(SOCKADDR*)&service,
sizeof(service)) == SOCKET_ERROR) {
printf("bind() failed.绑定网络端口失败\n");
closesocket(ListenSocket);
WSACleanup();
return 1;
}
else
{
printf("绑定网络端口成功...\n");
} //----------------------
// Listen for incoming connection requests.
// on the created socket
if (listen(ListenSocket, 5) == SOCKET_ERROR) {
printf("错误,监听网络端口失败...\n");
closesocket(ListenSocket);
WSACleanup();
return 1;
}
else
{
printf("监听网络端口成功...\n");
} printf("等待客户端连接...\n"); while (true)
{
// Berkeley sockets
fd_set readfds; // 描述符(socket)集合
fd_set writefds;
fd_set exceptfds; // 清理集合
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&exceptfds); // 将描述符(socket)加入集合
FD_SET(ListenSocket, &readfds);
FD_SET(ListenSocket, &writefds);
FD_SET(ListenSocket, &exceptfds); for (int n = (int)g_clientList.size() - 1; n >= 0 ; n--)
{
FD_SET(g_clientList[n], &readfds);
} // 设置超时时间 select 非阻塞
timeval timeout = { 1, 0 }; // nfds是一个整数值,是指fd_set集合中所有描述符(socket)的范围,而不是数量
// 即是所有文件描述符最大值+1 在Windows中这个参数可以写0
//int ret = select(ListenSocket + 1, &readfds, &writefds, &exceptfds, NULL);
int ret = select(ListenSocket + 1, &readfds, &writefds, &exceptfds, &timeout);
if (ret < 0)
{
printf("select任务结束,called failed:%d!\n", WSAGetLastError());
break;
} // 是否有数据可读
// 判断描述符(socket)是否在集合中
if (FD_ISSET(ListenSocket, &readfds))
{
//FD_CLR(ListenSocket, &readfds); // Create a SOCKET for accepting incoming requests.
// 4. accept 等待接受客户端连接
SOCKADDR_IN clientAddr = {};
int nAddrLen = sizeof(SOCKADDR_IN);
SOCKET ClientSocket = INVALID_SOCKET;
ClientSocket = accept(ListenSocket, (SOCKADDR*)&clientAddr, &nAddrLen);
if (INVALID_SOCKET == ClientSocket) {
printf("accept() failed: %d,接收到无效客户端Socket\n", WSAGetLastError());
return 1;
}
else
{
// 有新的客户端加入,向之前的所有客户端群发消息
for (int n = (int)g_clientList.size() - 1; n >= 0; n--)
{
NewUserJoin userJoin;
send(g_clientList[n], (const char*)&userJoin, sizeof(NewUserJoin), 0);
} g_clientList.push_back(ClientSocket);
// 客户端连接成功,则显示客户端连接的IP地址和端口号
printf("新客户端<Sokcet=%d>加入,Ip地址:%s,端口号:%d\n", ClientSocket, inet_ntoa(clientAddr.sin_addr),
ntohs(clientAddr.sin_port));
}
} for (int i = 0; i < (int)readfds.fd_count - 1; ++i)
{
if (-1 == processor(readfds.fd_array[i]))
{
auto iter = std::find(g_clientList.begin(), g_clientList.end(),
readfds.fd_array[i]);
if (iter != g_clientList.end())
{
g_clientList.erase(iter);
}
}
} //printf("空闲时间处理其他业务...\n");
} for (int n = (int)g_clientList.size() - 1; n >= 0; n--)
{
closesocket(g_clientList[n]);
} // 8.关闭套接字
closesocket(ListenSocket);
// 9.清除Windows Socket环境
WSACleanup(); printf("服务端已退出,任务结束\n"); getchar(); return 0;
}

二、客户端程序代码如下:

// Client.cpp

#include <stdio.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h> #include <thread> #pragma comment(lib, "ws2_32.lib") // 要考虑字节对齐问题(32位和64位,平台和系统)
enum CMDTYPE
{
CMD_LOGIN, // 登录
CMD_LOGIN_RESULT, // 登录返回结果
CMD_LOGOUT, // 登出
CMD_LOGOUT_RESULT, // 登出返回结果
CMD_NEW_USER_JOIN, // 新用户加入
CMD_ERROR // 错误
}; // 消息头
struct DataHeader
{
int cmd; // 命令类型
int dataLength; // 消息体的数据长度
}; // 消息体
// DataPackage
// 登录
struct Login : public DataHeader
{
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char passWord[32];
}; // 登录结果
struct LoginResult : public DataHeader
{
LoginResult()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN_RESULT;
result = 0;
}
int result;
}; // 登出
struct Logout : public DataHeader
{
Logout()
{
dataLength = sizeof(Logout);
cmd = CMD_LOGOUT;
}
char userName[32];
}; // 登出结果
struct LogoutResult : public DataHeader
{
LogoutResult()
{
dataLength = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
result = 0;
}
int result;
}; // 新用户加入
// 登出
struct NewUserJoin : public DataHeader
{
NewUserJoin()
{
dataLength = sizeof(NewUserJoin);
cmd = CMD_NEW_USER_JOIN;
sock = 0;
}
int sock;
}; int processor(SOCKET sock)
{
// 缓冲区(4096字节)
char szRecv[4096] = {};
// 5、接收客户端的请求
// 先接收消息头
int recvLen = recv(sock, szRecv, sizeof(DataHeader), 0);
DataHeader *pHeader = (DataHeader*)szRecv;
if (recvLen <= 0)
{
printf("与服务器断开连接,任务结束...");
return -1;
} // 6、处理请求
switch (pHeader->cmd)
{
case CMD_LOGIN_RESULT:
{
recv(sock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0); LoginResult *loginRes = (LoginResult*)szRecv; printf("收到服务器消息:CMD_LOGIN_RESULT, 数据长度:%d, result:%d\n",
loginRes->dataLength, loginRes->result);
}
break;
case CMD_LOGOUT_RESULT:
{
recv(sock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0); LogoutResult *logoutRes = (LogoutResult*)szRecv; printf("收到服务器消息:CMD_LOGOUT_RESULT, 数据长度:%d, result:%d\n",
logoutRes->dataLength, logoutRes->result);
}
case CMD_NEW_USER_JOIN:
{
recv(sock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0); NewUserJoin *userJoin = (NewUserJoin*)szRecv; printf("收到服务器消息:CMD_NEW_USER_JOIN, 数据长度:%d\n",
userJoin->dataLength);
}
break;
} return 0;
} bool g_bRun = true; // 是否退出程序 // 命令输入 线程入口函数
void cmdThread(SOCKET sock)
{
while (true)
{
// 输入请求命令
char cmdBuf[128] = { 0 };
printf("请输入命令:[exit | login | logout | other]\n");
scanf("%s", &cmdBuf); // 处理请求
if (0 == strcmp(cmdBuf, "exit"))
{
g_bRun = false;
printf("退出cmdThread线程...\n");
break;
}
else if (0 == strcmp(cmdBuf, "login"))
{
// 5.向服务器发送命令请求
Login login;
strcpy(login.userName, "ccf");
strcpy(login.passWord, "ccfPwd"); send(sock, (const char*)&login, sizeof(login), 0);
}
else if (0 == strcmp(cmdBuf, "logout"))
{
// 5.向服务器发送命令请求
Logout logout;
strcpy(logout.userName, "ccf");
send(sock, (const char*)&logout, sizeof(logout), 0);
}
else
{
printf("不支持的命令,请重新输入.\n");
}
}
} int main(int argc, char *argv[])
{
//----------------------
// Initialize Winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR)
{
printf("WSAStartup() 错误,创建套接字库失败!\n");
return -1;
} //----------------------
// Create a SOCKET for connecting to server
SOCKET ConnectSocket;
ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET) {
printf("创建套接字失败: %ld\n", WSAGetLastError());
WSACleanup();
return -1;
} //----------------------
// The sockaddr_in structure specifies the address family,
// IP address, and port of the server to be connected to.
sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
clientService.sin_port = htons(27015); //----------------------
// Connect to server.
if (connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService)) == SOCKET_ERROR) {
printf("连接服务器失败.\n");
WSACleanup();
return -1;
} printf("客户端成功连接到服务器.\n"); // 循环输入命令
// 启动线程
std::thread thread_(cmdThread, ConnectSocket);
thread_.detach(); while (g_bRun)
{
fd_set fdReads;
FD_ZERO(&fdReads); FD_SET(ConnectSocket, &fdReads); timeval timeout = { 0, 0 };
int ret = select(ConnectSocket, &fdReads, NULL, NULL, &timeout);
if (ret < 0)
{
printf("select任务结束1...\n");
break;
} if (FD_ISSET(ConnectSocket, &fdReads))
{
//FD_CLR(ConnectSocket, &fdReads); // 有数据可读,可以处理了
if (-1 == processor(ConnectSocket))
{
printf("select任务结束2\n");
break;
}
} /*Login login;
strcpy(login.userName, "ccf");
strcpy(login.passWord, "ccfPwd"); send(ConnectSocket, (const char*)&login, sizeof(Login), 0);*/
//Sleep(1000); // 发送登录数据后延时1s //printf("空闲时间处理其他业务...\n");
} WSACleanup(); printf("客户端已退出...\n"); getchar(); return 0;
}

之前在CSDN上看到一篇博客,是基于UDP的Linux C++简单聊天室实现,我把源代码重新整理并放在个人的GitHub上面基于UDP的Linux C++简单聊天室,同样是使用select模型实现的,有兴趣可以看一下。

基于Select模型的Windows TCP服务端和客户端程序示例的更多相关文章

  1. 网络编程 — Windows TCP服务端和客户端

    1. 服务端 #include <iostream> #include <signal.h> #include <forward_list> #include &l ...

  2. php编写TCP服务端和客户端程序

    1.修改php.ini,打开extension=php_sockets.dll 2.服务端程序SocketServer.php <?php //确保在连接客户端时不会超时 set_time_li ...

  3. swoole创建TCP服务端和客户端

    服务端: server.php <?php //创建Server对象,监听 127.0.0.1:9501端口    $serv = new swoole_server("127.0.0 ...

  4. 【转】TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端)、UDP客户端

    [转]TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端).UDP客户端 目录 说明 TCP/UDP通信主要结构 管理多个Socket的解决方案 框架中TCP部分的使用 框架中UDP ...

  5. 利用select实现IO多路复用TCP服务端

    一.相关函数 1.  int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeva ...

  6. C++封装的基于WinSock2的TCP服务端、客户端

    无聊研究Winsock套接字编程,用原生的C语言接口写出来的代码看着难受,于是自己简单用C++封装一下,把思路过程理清,方便自己后续翻看和新手学习. 只写好了TCP通信服务端,有空把客户端流程也封装一 ...

  7. TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端)、UDP客户端

    目录 说明 TCP/UDP通信主要结构 管理多个Socket的解决方案 框架中TCP部分的使用 框架中UDP部分的使用 框架源码结构 补充说明 源码地址 说明 之前有好几篇博客在讲TCP/UDP通信方 ...

  8. vertx 从Tcp服务端和客户端开始翻译

    写TCP 服务器和客户端 vert.x能够使你很容易写出非阻塞的TCP客户端和服务器 创建一个TCP服务 最简单的创建TCP服务的方法是使用默认的配置:如下 NetServer server = ve ...

  9. Java TCP服务端向客户端发送图片

    /** * 1.创建TCP服务端,TCP客户端 * 2.服务端等待客户端连接,客户端连接后,服务端向客户端写入图片 * 3.客户端收到后进行文件保存 * @author Administrator * ...

随机推荐

  1. SQLServer中ISNULL和CONVERT函数

    create view sss as(select ISNULL(operate_time, CONVERT(VARCHAR(20),create_time,120)) time from s_pro ...

  2. nginx入门(一)

    什么是nginx? nginx是一款高性能的http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器.由俄罗斯的程序设计师Igor Sysoev所开发,官方测试nginx能够支支撑5 ...

  3. vue的v-cloak 指令设置样式

    使用 v-cloak 指令设置样式,可以使样式在 Vue 实例编译结束时,从绑定的 HTML 元素上被移除. 详情请参考:https://www.jianshu.com/p/f56cde007210? ...

  4. 行人重识别(ReID) ——基于Person_reID_baseline_pytorch修改业务流程

    下载Person_reID_baseline_pytorch地址:https://github.com/layumi/Person_reID_baseline_pytorch/tree/master/ ...

  5. bzoj1430 小猴打架 prufer 序列

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=1430 题解 prufer 序列模板题. 一个由 \(n\) 个点构成的有标号无根树的个数为 \ ...

  6. 关于<label>的for属性的简单探索

    在freecodecamp上HTML教程的Create a Set of Radio Buttons这一节中,看到这样一段话, It is considered best practice to se ...

  7. Hybris Commerce下单时遇到产品库存不足的解决办法

    客户在Storefront下单试图购买一个产品时,遇到out of stock库存不足的错误,无法下单: 解决办法:登录Backoffice,Stock level菜单: 创建一个新的stock le ...

  8. Xenu Link Sleuth 简单好用的链接测试工具

    XenuLink Sleuth 名词介绍 “Xenu链接检测侦探”是被广泛使用的死链接检测工具.可以检测到网页中的普通链接.图片.框架.插件.背景.样式表.脚本和java程序中的链接. 那么神马时候出 ...

  9. Python_008(文件操作)

    一.文件操作 1.只读操作 f = open("taibai.txt",mode = "r",encoding = "utf-8" s = ...

  10. 6 October

    P1514 引水入城 题目描述 在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠.该国的行政区划十分特殊,刚好构成一个 \(N\) 行 \(\times M\) 列的矩形,如上图所示, ...