windows网络编程-select模型

318 阅读4分钟

select模型

  • select是为了解决accept和recv一直等待的问题
简单c/s模型的调用顺序
1.socket(); 创建服务端socket
2.bind(); 绑定IP和端口号
while(1){
    3.listen(); 服务端开始监听客户端对服务器发起的连接
    4.accept(); 给客户端创建一个socket 用来了客户端通信
    5.recv(); 与客户端通信
}

那么服务端进行到第5步的时候 服务端一直和客户端通信(等待客户端发来数据) 如果这个时候由其他客户端发来连接服务端无法处理 因为第5步没执行完 无法进入下次循环

  • select模型的实现
    在服务端维护一个socket数组 将socket的状态交给操作系统处理 程序员调用select函数从socket数组中拿到响应的socket

捕获.PNG

捕获.PNG

  • select模型中对socket状态的分类
    可读: clientsocket和serversocket都可能出现可读状态的响应 serversocket可读响应表示有客户端建立连接 clientsocket可读响应表示客户端发来消息
    可写: 理论上只要客户端处于连接状态就处在可写状态
    异常: socket出现异常
  • fdset结构体
typedef struct fd_set {
    u_int fd_count;
    SOCKET fd_array[FD_SETSIZE];
}

select模型里面监控的socket数组就是fd_set结构体中的fd_array数组 fd_count表示数组里面socket数量

fd_set sockets;
FD_ZERO(&sockets);  // sockets初始化
FD_SET(serversocket, &sockets);  // 把serversocket放到sockets里面 让OS管理它的状态
  • select函数
    建视fd_set集合 如果某个socket发生事件 通过返回值以及参数通知调用者
int WSAAPI select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const timeval *timeout);

nfds: 忽略 该参数为了兼容berkeley socket 直接写0
*readfds: 检查这组socket里面是否有可读状态的socket 系统将fd_set里面可读状态的socket返回回来
*writefs: 检查这组socket里面是否有可写状态的socket 系统将fd_set里面可写状态的socket返回回来 只要socket连接不断 都可写
*exceptfds: 检查这组socket里面是否有异常状态的socket 系统将fd_set里面异常状态的socket返回回来
timeout: 设置等待时间 当客户端没有请求时 select可以等一会如果这段时间没有响应就继续执行select函数下面的语句

timeval {
    long tv_sec; 秒
    long tv_usec; 毫秒
}
设置 0 0 非阻塞 例客返回
设置 3 4 等待3s4ms

select模型的局限

  • 请求量的限制
    FD_SETSIZE默认的值是64(1个serversocket 63个clientsocket)
    只适用于小规模请求的场景 因为select模型需要遍历数组 如果数组太长 遍历一次的成本太大
  • select函数的执行阻塞
    select遍历socket数组时不能做其他操作 遍历到数组第2个位置的socket时 这个时候数组第一个位置的socket对应的客户端发来了数据 那么在本次遍历中服务端无法响应这个请求 只能放到下次循环中响应

代码 使用select模型完成多客户端的回送服务器

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <string.h>

#pragma comment(lib, "Ws2_32.lib")

#define BUFSIZE 1024

/*
	打印socket的IP地址 PORT端口号 协议类型
	@param sockMsg socket信息
*/
extern void printSocketaddrMsg(SOCKADDR_IN sockMsg);

int main(void) {

	// 打开网络库
	WORD version = MAKEWORD(2, 2);
	WSADATA sockMsg;
	if (WSAStartup(version, &sockMsg) != 0) {
		errno = WSAGetLastError();
		fprintf(stderr, "|#server |#open network library failed. errno: %d", errno);
		return -1;
	}
	fprintf(stdout, "|#server |#open network library successfully.\n");

	// 创建服务端socket
	SOCKET serversocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (serversocket == INVALID_SOCKET) {
		errno = WSAGetLastError();
		fprintf(stderr, "|#server |#create server socket failed. errno: %d", errno);
		return -1;
	}
	fprintf(stdout, "|#server |#create server socket successfully.\n");

	// 给服务端socket绑定ip地址 port端口号 family协议类型
	SOCKADDR_IN serverMsg;
	serverMsg.sin_family = AF_INET;
	serverMsg.sin_port = 10000;
	inet_pton(AF_INET, "127.0.0.1", (const SOCKADDR*) & serverMsg.sin_addr.s_addr);
	if (bind(serversocket, (const SOCKADDR*)&serverMsg, sizeof(serverMsg)) == SOCKET_ERROR) {
		errno = WSAGetLastError();
		fprintf(stderr, "|#server |#bind IP and PORT failed. errno: %d", errno);
		return -1;
	}
	fprintf(stdout, "|#server |#bind IP and PORT successfully.\n");

	// 监听客户端发来的连接
	if (listen(serversocket, SOMAXCONN) == SOCKET_ERROR) {
		errno = WSAGetLastError();
		fprintf(stderr, "|#server |#listen failed. errno: %d", errno);
		return -1;
	}
	fprintf(stdout, "|#server |#start listen...\n");

	fd_set sockets;
	FD_ZERO(&sockets);	// 初始化监听socket数组
	FD_SET(serversocket, &sockets);	// 服务端socket被系统监控

	TIMEVAL timeout;	// 设置等待时间3s
	timeout.tv_sec = 3;
	timeout.tv_usec = 0;

	char recvbuffer[BUFSIZE] = { 0 };	// 读缓冲区
	char sendbuffer[BUFSIZE] = { 0 };	// 写缓冲区
	int recvLen = 0;
	int sendLen = 0;

	while (1) {
		fd_set readfds = sockets;
		fd_set exceptfds = sockets;
		if (select(0, &readfds, NULL, &exceptfds, &timeout) == SOCKET_ERROR) {
			errno = WSAGetLastError();
			fprintf(stderr, "|#server |#select failed. errno: %d", errno);
			return -1;
		}

		// 处理可读状态
		for (unsigned int i = 0; i < readfds.fd_count; i++) {
			if (readfds.fd_array[i] == serversocket) {
				// 新的客户端建立连接
				SOCKADDR_IN cliMsg;
				int cliLen = sizeof(SOCKADDR_IN);
				SOCKET clientsocket = accept(serversocket, (SOCKADDR*)&cliMsg, &cliLen);
				if (clientsocket == INVALID_SOCKET) {
					errno = WSAGetLastError();
					fprintf(stderr, "|#server |#server create new client socket failed. errno: %d", errno);
					return -1;
				}
				fprintf(stdout, "|#server |#server create new client socket successfully\n");
				printSocketaddrMsg(cliMsg); // 打印新客户端的信息
				send(clientsocket, "welcome access server.\n", strlen("welcome access server.\n"), 0);
				FD_SET(clientsocket, &sockets); // 新clientsocket添加到监控数组中
			}
			else {
				SOCKADDR_IN cliMsg;
				int cliLen = sizeof(SOCKADDR_IN);
				getpeername(readfds.fd_array[i], (SOCKADDR*) &cliMsg, &cliLen);
				// 老的客户端发来数据
				recvLen = recv(readfds.fd_array[i], recvbuffer, sizeof(recvbuffer), 0);
				if ((recvLen == 0) || (strcmp(recvbuffer, "QUIT") == 0)) {
					// 客户端下线 不在监控这个socket
					printSocketaddrMsg(cliMsg);
					fprintf(stdout, " |#已下线.\n");
					FD_CLR(readfds.fd_array[i], &sockets);
				}
				else if (recvLen > 0) {
					// 将接收到的数据从recvbuffer拷贝到sendbuffer
					strcpy_s(sendbuffer, sizeof(recvbuffer), recvbuffer);
					printSocketaddrMsg(cliMsg);
					fprintf(stdout, " |#接收到的数据: %s\n", recvbuffer);
					send(readfds.fd_array[i], sendbuffer, sizeof(sendbuffer), 0);
				}
				else {
					// 接收客户端数据失败
					errno = WSAGetLastError();
					fprintf(stderr, "|#server |#recv client msg failed. errno: %d", errno);
					return -1;
				}
			}
		}
	}

	WSACleanup(); // 关闭网络库
	return 0;
}

void printSocketaddrMsg(SOCKADDR_IN sockMsg) {
	char proto[BUFSIZE] = {0};
	char ip[BUFSIZE] = {0};
	unsigned short family = sockMsg.sin_family;
	unsigned short port = sockMsg.sin_port;
	if (family == 2) {
		strcpy_s(proto, BUFSIZE, "AF_INET");
	}
	inet_ntop(family, &sockMsg.sin_addr.s_addr, ip, BUFSIZE);
	fprintf(stdout, "|#socket message |# proto:%s, ip:%s, port:%d", proto, ip, port);
	fflush(stdout);
}

结果

捕获.PNG