多路复用并发模型 -- select
#include<sys/select.h>
#include<sys/time.h>
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)
maxfd 监控的套接字最大值 + 1
readset 集合中任意描述字准备好读,则返回
writeset 集合中任意描述字准备好写,则返回
exceptset 集合中任意描述字有异常等待处理,则返回
timeout 超时则返回(NULL 则一直等待,0 则立即返回)
返回值 =0 超时, 返回值<0 错误,返回值>0正常
多路复用并发模型 -- select
FD_ZERO(fd_set *fdset)
清空描述符集合
FD_SET(int fd, fd_set *fdset)
增加fd 到集合中, 事实上就是把某个bit位置位
FD_CLR(int fd, fd_set *fdset)
从集合中清除fd, 事实上就是把某个bit位清除置位
int FD_ISSET(int fd, fd_set *fdset)
描述字是否准备好
多路复用并发模型 -- select
优点:
通过IO复用,支持交互式输入
通过IO复用,可以同时监听 UDP 和 TCP
相比比多线程, 系统开销大大减少,
缺点:
每次调用 select 都需要把fd集合从用户态拷贝到内核态,fd很多时开销很大
调用 select 需要内核遍历 fd, fd 很多时开销很大
select 支持文件描述符监视有数量限制,默认 1024
服务器端代码:
#include<stdio.h> #include<unistd.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/select.h> #include<sys/time.h> #define SRV_PORT 0xabcd #define MAX_CONN 3 void RcvMsg(int fds[], int index, int *pnConn) { char szBuf[1024] = {0}; int iRet; iRet = read(fds[index], szBuf, 1024); if (iRet < 0) { perror("Fail to read!"); } else if (iRet == 0) { //disconnect. remove fd from fds printf("[%d]Disconnect... ", fds[index]); close(fds[index]); int j; for (j=index; j < *pnConn - 1; j++) { fds[j] = fds[j+1]; } (*pnConn)--; } else { printf("[%d]Recv:%s ", fds[index], szBuf); } return; } void TcpServer() { int fd; int iRet; struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("Fail to socket!"); return; } addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(SRV_PORT); iRet = bind(fd, (struct sockaddr*)&addr, addrlen); if (iRet) { perror("Fail to bind!"); close(fd); return; } iRet = listen(fd, 100); if (iRet) { perror("Fail to listen!"); close(fd); return; } fd_set fdset; int maxfd = fd; int fds[MAX_CONN]; //client fd; int nConn = 0; //client fd num. int i; int clientfd; struct sockaddr_in srcaddr; char szMsg[100]; while(1) { FD_ZERO(&fdset); FD_SET(fd, &fdset); for (i=0; i<nConn; i++) { FD_SET(fds[i], &fdset);//add client fd to fdset for monitor } fprintf(stderr, "Send:"); scanf("%s", szMsg); for (i=0; i<nConn; i++) { write(fds[i], szMsg, strlen(szMsg)); } iRet = select(maxfd+1, &fdset, NULL, NULL, NULL); if (iRet < 0) { perror("Fail to select!"); break; } else if (iRet == 0) { //timeout } else { if (FD_ISSET(fd, &fdset)) { clientfd = accept(fd, (struct sockaddr*)&srcaddr, &addrlen); if (clientfd < 0) { perror("Fail to accept!"); break; } if (nConn == MAX_CONN) { char szTip[] = "Over connect, please wait..."; write(clientfd, szTip, sizeof(szTip)); printf("Connect over! "); close(clientfd); } else { char szTip[] = "Welcome!"; write(clientfd, szTip, sizeof(szTip)); printf("[%d]New connection form %s:%d ", clientfd, inet_ntoa(srcaddr.sin_addr), ntohs(srcaddr.sin_port)); fds[nConn] = clientfd; nConn++; if (clientfd > maxfd) { maxfd = clientfd; } } } for (i=0; i<nConn; i++) { if (FD_ISSET(fds[i], &fdset)) { RcvMsg(fds, i, &nConn); } } } } close(fd); } int main() { TcpServer(); return 0; }