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
- 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);
}