我们先从著名的C10K问题开始探讨,由于早期在网络还不普及的时候,互联网的用户并不是很多,一台服务器同时在线100个用户估计在当时已经算是大型应用了。但是随着互联网的发展,用户群体迅速的扩大,每一个用户都必须与服务器保持TCP连接才能进行实时的数据交互。Facebook这样的网站同一时间的并发TCP连接可能会过亿。这时候问题就来了。
解决这种问题的思路主要有两个:一个是对于每一个连接处理分配一个独立的进程或线程;另一种就是用同一进程或线程来同时处理若干个连接。
第一种解决方案,即来一个TCP链接,就需要分配一个进程/线程。而进程又是操作系统最昂贵的资源,一台机器无法创建很多进程,比如C10K问题就要创建1万个进程,操作系统是无法承受的。
第二中解决方案,即每个进程/线程同时处理多个连接(IO多路复用),我们讨论的select,poll,epoll都是基于这种思路的。
- 这种思路最简单的方法是循环挨个处理各个请求,每个连接对应一个socket,当所有的socket的有数据的时候,这种方法是可行的,但当应用读取某个socket的文件数据不ready的时候,整个应用就会阻塞在这里等待该文件句柄,即使别的文件句柄ready,也无法往下处理。
- select要解决的就是上面的阻塞问题,思路很简单,如果我在读取文件句柄之前,先查看它的状态,ready了就进行处理,不ready就不进行处理,这样就解决了这个问题了。
- select 使用fd_set结构体来告诉内核同时监控多个文件句柄,当其中有文件句柄的状态发生制定变化或超时,则调用返回。之后使用FD_ISSET来捉个查看是哪个文件句柄发生变化。
- fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。
- fd_set集合可以通过一些宏由人为的来操作,FD_ZERO(fd_set*):清空集合;FD_SET(int, fd_set*):将指定的文件描述符加入到集合;FD_CLR(int, fd_set*):将一个给定的文件描述符从集合中删除;FD_ISSET(int, fd_set*):判断指定的文件描述符是否可以读写。
- select函数原型:int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
- maxfdp:集合中所有文件描述符的范围,即所有文件描述符的最大值+1;
- readfds:指定我们要监视的读变化的文件描述符集合,即我们是否可以从这些文件中读取数据;
- writefds:指定我们要监视的写变化的文件描述符集合,即我们是否可以从这些文件中写数据;
- errorfds:用来监视文件错误异常;
- timeout:select 的超时时间,若将此参数设为NULL,表示阻塞,直到有文件描述符状态发生变化;若将此参数设为0秒0毫秒,表示非阻塞,直接返回,返回值为0表示没有文 件描述符发生变化,返回值为非0表示有文件描述符发生变化;若将此参数设为大于0,表示等待的时间,期间如果有事件发生则返回非0值,如果一直没有事件, 则等待此参数设置的时间,然后返回0。
- select例子:
- server.c:
#include <sys/time.h> #include <netinet/in.h> #include <sys/select.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(){ int socket1, socket2; struct sockaddr_in socket1addr, socket2addr; fd_set fds; struct timeval timeout = {3, 0}; char buffer[256] = {0}; socket1 = socket(PF_INET, SOCK_DGRAM, 0); //bzero(&socket1addr, sizeof(socket1addr)); socket1addr.sin_family = AF_INET; socket1addr.sin_addr.s_addr = htonl(INADDR_ANY); socket1addr.sin_port = htons(36500); bind(socket1, (struct sockaddr*)&socket1addr, sizeof(socket1addr)); socket2 = socket(PF_INET, SOCK_DGRAM, 0); //bzero(&socket2addr, sizeof(socket2addr)); socket2addr.sin_family = AF_INET; socket2addr.sin_addr.s_addr = htonl(INADDR_ANY); socket2addr.sin_port = htons(36501); bind(socket2, (struct sockaddr*)&socket2addr, sizeof(socket2addr)); while(1){ FD_ZERO(&fds); FD_SET(socket1, &fds); FD_SET(socket2, &fds); int maxfdp = (socket1 > socket2) ? (socket1+1) : (socket2 + 1); int retval = select(maxfdp, &fds, NULL, NULL, &timeout); if(retval == -1){ printf("error\n"); return -1; }else if(retval == 0){ continue; }else{ struct sockaddr_in client; int len = sizeof(client); if(FD_ISSET(socket1, &fds)){ recvfrom(socket1, buffer, 256, 0, (struct sockaddr*)&client, &len); printf("%u says:%s\n", ntohs(client.sin_port), buffer); } if(FD_ISSET(socket2, &fds)){ recvfrom(socket2, buffer, 256, 0, (struct sockaddr*)&client, &len); printf("%u says:%s\n", ntohs(client.sin_port), buffer); } } } }
- client.c
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> int main(int argc, char **argv) { if(argc < 2){ printf("usage: %s port\n", argv[0]); return -1; } int sockfd; struct sockaddr_in servaddr; sockfd = socket(PF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(atoi(argv[1])); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); char sendline[100]; sprintf(sendline, "Hello, world!"); sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); close(sockfd); return 1; }
- server.c:
时间: 2024-10-07 15:26:58