网络编程之select模型

前言

对于原始的socket网络编程模型,由于accept与recv函数均会阻塞,使用多线程过于麻烦,于是知道了select的网络模型。但是看了不少讲述select模型的文章,总是云里雾里的,一点也不明白,于是专门看了一个视频,再把代码打出来,自己调试,终于理解了。

select模型介绍

select 模型主要有 select() 函数和 fd_set 结构体,以及 FD_SET, FD_ZERO, FD_ISSET 和 FD_CLR 这几个宏。

fd_set 顾名思义就是集合,实际上是 select() 函数待监控的 SOCKET 集合,在调用 select() 之前,应该把需要监控的 SOCKET 全部加入到 fd_set 集合中,否则无法处理该 SOCKET 上的消息(连接,断开,收到数据等)。

调用 select() 后, select() 会把有消息的 SOCKET 记录到 fd_set 集合中,没有消息的 SOCKET 会被清除标记,因此在重新调用 select() 之前应该再次把要监控的 SOCKET 重新加入到 fd_set 集合中。

代码

主要是理解调用select()之后该函数做了什么,调用之前要传入什么。

客户端用原来先的代码就可以进行测试了。处理函数 processor() 只是封装了大小写转换的 recv() 和 send() ,没什么好说的。

主体流程介绍:

  1. 主体中把 select() 放入 while 循环中以便可以处理多个客户端消息。
  2. 先将服务器的 SOCKET 加入到待监控的 fs_set 集合中,再将已经连接的客户端 SOCKET 加入 fd_set 集合中。
  3. 调用 select() 对这些 SOCKET 进行监听。
  4. 有新的客户端连接时,fdRead 集合中会保留服务器的 SOCKET ,于是在调用 FD_ISSET 宏时会检查到该服务器的 SOCKET,返回计数1,其余没有消息的 SOCKT会被计数减去。在取出该新建的连接时要调用 FD_CLR 将计数器减一。
  5. 当已连接 SOCKET 发送过来数据时由于上一步已经去除了新建的连接,于是可以循环 fd_set 的计数器,对 fd_set 集合中保留的 SOCKET 进行 recv() 和 send() 操作(此时 recv() 和 send() 实际上依旧是阻塞的)。当客户端断开时,清理断开的SOCKET。

程序主体(处理函数比较独立,就放下面了):

#include <stdio.h>
#include <winsock.h>
#include <vector>
#pragma comment(lib,"ws2_32.lib")
std::vector<SOCKET>vecClient;
int processor(SOCKET sockClient);
#define DEFAULT_PORT 12345
#define BUFFER_SIZE  512
int main() {
    
    
	WSADATA wsa;
	sockaddr_in addrServer = {
    
    }, addrClient = {
    
    };
	addrServer.sin_family = AF_INET;
	addrServer.sin_port = htons(DEFAULT_PORT);
	addrServer.sin_addr.S_un.S_addr = INADDR_ANY;
	int nAddrLen = sizeof(sockaddr_in);
	fd_set fdRead, fdWrite, fdExp;

	WSAStartup(MAKEWORD(2, 2), &wsa);
	SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	bind(sockServer, (sockaddr*)&addrServer, sizeof(addrServer));
	listen(sockServer, 5);
	while (true) {
    
    
		// 1.首先初始化待监听的socket列表
		// 2.调用select进行连接监听
		// 3.检查服务器socket是否在可读的集合中,如果在,那么就调用accept取出新的连接
		// 4.除去新建连接后,剩下为需要处理的socket,对剩余集合进行recv及send操作
		// 5.断开连接后将客户端连接去除
		FD_ZERO(&fdRead);				FD_ZERO(&fdWrite);				FD_ZERO(&fdExp);
		FD_SET(sockServer, &fdRead);	FD_SET(sockServer, &fdWrite);	FD_SET(sockServer, &fdExp);
		for (int i = 0; i < vecClient.size(); i++) {
    
    
 			FD_SET(vecClient[i], &fdRead);
 		}

		int ret = select(sockServer + 1, &fdRead, &fdWrite, &fdExp, NULL);
		if (FD_ISSET(sockServer, &fdRead)) {
    
       // 服务器有需要处理的连接
			FD_CLR(sockServer, &fdRead);		// 取出连接后计数减一 
			SOCKET sockClient = accept(sockServer, (sockaddr*)&addrClient, &nAddrLen);
			printf("accept sock %d form %s:%d\n", sockClient, inet_ntoa(addrClient.sin_addr), addrClient.sin_port);
			vecClient.push_back(sockClient);  // 保存新的连接
		}

		for (size_t n = 0; n < fdRead.fd_count; n++) {
    
    
			if (-1 == processor(fdRead.fd_array[n])) {
    
    
				auto iter = find(vecClient.begin(), vecClient.end(), fdRead.fd_array[n]);
				if (iter != vecClient.end()) {
    
    
					closesocket(*iter);
					vecClient.erase(iter);
				}
			}
		}
	}
	for (size_t n = vecClient.size() - 1; n >= 0; n--) {
    
    
		closesocket(vecClient[n]);
	}
	closesocket(sockServer);
	WSACleanup();
	do {
    
     printf("Press Ctrl + C to exit!\n"); } while (getchar());
	return 0;
}

处理函数:

int processor(SOCKET sockClient) {
    
    
	char szRecvBuff[BUFFER_SIZE] = {
    
    };
	char szSendBuff[BUFFER_SIZE] = {
    
    };
	int iRtn = recv(sockClient, szRecvBuff, BUFFER_SIZE, 0);
	if (iRtn > 0) {
    
    
		printf("recv socket %d %d bytes :%s\n", sockClient, iRtn, szRecvBuff);
	}
	else{
    
    
		printf("disconnect with sock %d!\n", sockClient);
		return -1;
	}
	// 大小写转换
	int i = 0;
	for (i = 0; szRecvBuff[i - 1] != '\0'; i++) {
    
    
		if (szRecvBuff[i] >= 'a' && szRecvBuff[i] <= 'z') {
    
    
			szSendBuff[i] = szRecvBuff[i] + ('A' - 'a');
		}
		else if (szRecvBuff[i] >= 'A' && szRecvBuff[i] <= 'Z') {
    
    
			szSendBuff[i] = szRecvBuff[i] - ('A' - 'a');
		}
		else {
    
    
			szSendBuff[i] = szRecvBuff[i];
		}
	}
	iRtn = send(sockClient, szSendBuff, i, 0);  // 最后一个参数默认为0(为5时导致数据发送不完整)
	if (iRtn > 0) {
    
    
		printf("send to socket %d %d bytes!\n", sockClient, iRtn);
	}
	else {
    
    
		printf("send to socket %d error!\n", sockClient);
		return -1;
	}
	// 	int iSleep = 30 * 1000;
	// 	printf("sleep %d s\n", iSleep / 1000);
	// 	Sleep(iSleep);  // 当前连接阻塞后依旧会影响其他连接的消息收发
	printf("\n");
}

参考资料:
【C++教程】C++百万并发网络通信引擎架构与实现(Socket、全栈、跨平台) https://www.bilibili.com/video/BV1LT4y1A7Pn

猜你喜欢

转载自blog.csdn.net/u012101384/article/details/108997945