写了一个基于select的并发服务器
The following article is from 涛歌依旧 Author 点击关注👉👉
点击关注公众号,一周多次包邮送书
来源:经授权转自 涛歌依旧(ID:ai_taogeyijiu_2021)
作者:涛哥
有很多读者对网络编程很感兴趣,觉得挺有意思,希望我多写一些网络编程方面的实战例子。没问题,这就来了。
对于后端开发同学而言,肯定了解多线程并发服务器,那么多路复用的并发服务又怎么玩呢?看完这篇就清楚了。
今天,我们来实战一下基于select模型的并发服务器,并给出实际实验效果,希望大家能有一些新的认识和收获。
如果你还不清楚,那我来手绘一下,你就明白啦:8个男生追求一个女生,这个女生同时处理8个男生的通信请求。
服务端实现
大家对select函数的作用应该很熟悉了,接下来,我们看看服务端程序,在关键的地方,基本都有详细的注释:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int totalSockets = 0; // socket的总数
SOCKET socketArray[100]; // socket组成的数组,假设最多有100个socket吧
// 日志打印
void log(const char *pStr)
{
FILE *fp = fopen("log.txt", "a");
fprintf(fp, "log:%s\n", pStr);
fclose(fp);
}
// 创建socket
void addToSocketArr(SOCKET s)
{
socketArray[totalSockets] = s;
totalSockets++;
}
// 启动服务器
int main()
{
log("into main");
// 网络初始化
WSADATA wsaData;
WSAStartup(MAKEWORD(1, 1), &wsaData);
// 创建socket
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
addToSocketArr(listenSocket);
// 服务地信息
SOCKADDR_IN srvAddr;
srvAddr.sin_family = AF_INET;
srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
srvAddr.sin_port = htons(8888);
// 绑定
bind(listenSocket, (SOCKADDR*)&srvAddr, sizeof(srvAddr));
// 监听
listen(listenSocket, 10);
// 设置socket为非阻塞模式
unsigned long nonBlock = 1;
ioctlsocket(listenSocket, FIONBIO, &nonBlock);
while(1)
{
// 读集, 要记得清零初始化
FD_SET readSet;
FD_ZERO(&readSet);
// 将每个socket都塞入读集,便于让内核来监测这些socket
int i = 0;
for(i = 0; i < totalSockets; i++)
{
FD_SET(socketArray[i], &readSet);
}
// 应用程序通知内核来监测读集中的socket, 最后的NULL表示超时时间无限长
int total = select(0, &readSet, NULL, NULL, NULL);
// 我们不考虑select失败,那么程序到这里,说明读集中必有socket处于"就绪状态"
for(i = 0; i < totalSockets; i++)
{
char szTmp[20] = {0};
sprintf(szTmp, "%d", i);
log(szTmp);
if(socketArray[i] == listenSocket) // 对监听的socket进行判断
{
log("socketArray[i] == listenSocket");
if(FD_ISSET(listenSocket, &readSet)) // 如果该socket在可读集中,则表明有客户端来连接
{
log("listenSocket, socketArray[i] == listenSocket");
// 接收来自于客户端的connect请求
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
SOCKET acceptSocket = 0;
acceptSocket = accept(listenSocket,(SOCKADDR*)&addrClient, &len);
// 设置为非阻塞模式
nonBlock = 1;
ioctlsocket(acceptSocket, FIONBIO, &nonBlock);
// 添加到socket数组中
addToSocketArr(acceptSocket);
}
continue;
}
// 注意:上面的listenSocket是不负责通信的,下面的一些socket都是负责通信的socket
// 如果通信socket处于读就绪状态
if (FD_ISSET(socketArray[i], &readSet))
{
log("to receive");
char szRecvBuf[1024] = {0};
recv(socketArray[i], szRecvBuf, sizeof(szRecvBuf) - 1, 0);
printf("socketArray[i] is %d, %s\n", socketArray[i], szRecvBuf);
}
}
}
// 省略了关闭socket等后续操作
return 0;
}
编译并运行程序,开启服务端,等待客户端的连接。
客户端实现
接下来,我们看客户端的程序,很常规,也很简洁:
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(1, 1);
WSAStartup( wVersionRequested, &wsaData );
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 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(8888);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
while(1)
{
char szSendBuf[100] = {0};
scanf("%s", szSendBuf);
send(sockClient, szSendBuf, strlen(szSendBuf) + 1, 0);
}
closesocket(sockClient);
WSACleanup();
return 0;
}
实战的结果
网络编程就是这样,定好思路,多编程,多抓包,多调试,爽爽哒!大家如果有疑问请留言。
推荐阅读
• 本地与远程设备之间如何有效地进行文件同步?• 老板说我最近飘了,都敢用 MySQL 实现分布式锁了• 阿里终面:业务主表读写缓慢如何优化?• 函数调用时栈是如何变化的?• 9张图,32个案例带你轻松玩转Java stream• JVM 运行时数据区域,书中没有说清楚的方法区、永久代、元空间• 一个由“ YYYY-MM-dd ”引发的惨案 !
👇更多内容请点击👇