IO多路复用并发服务器
来源:互联网 发布:80年代台湾经济 知乎 编辑:程序博客网 时间:2024/05/29 17:24
服务器调用select函数检测两种不同类型的输入事件
1、新的客户端请求到达,此时监听描述符准备好可以读了
服务器打开连接并将该客户添加到池里
2、一个已存在的客户端的已连接描述符准备好可以读了
服务器把来自每个已经准备好的已连接描述符的一个文本行回送回去
(当且仅当一个从该描述符读取一个字节的请求不会阻塞,描述符k就表示准备好可以读了)
#include <iostream>#include <unistd.h>#include <sys/socket.h>#include <netdb.h>#include <sys/select.h>//robust IOssize_t rio_writen(int fd, void *userbuf, size_t n){ size_t nleft = n; ssize_t nwrite; char *bufp = (char*)userbuf; while(nleft > 0) { if((nwrite = write(fd, bufp, nleft))<0) { if(errno == EINTR) nwrite = 0; else return -1; } nleft -= nwrite; bufp += nwrite; } return n;}//带缓存版本static const int RIO_BUFSIZE = 8192;struct rio_t{ int rio_fd; //文件描述符 int rio_cnt; //未读字节数 char *rio_bufptr; //下一个未读字节 char rio_buf[RIO_BUFSIZE];//内部缓冲};void rio_readinitb(rio_t *rp, int fd){ rp->rio_fd = fd; rp->rio_cnt = 0; rp->rio_bufptr = rp->rio_buf;}ssize_t rio_read(rio_t *rp, void *userbuf, size_t n){ while(rp->rio_cnt <= 0) { rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf)); if(rp->rio_cnt < 0) { if(errno != EINTR) return -1; } else if (rp->rio_cnt == 0) return 0; else rp->rio_bufptr = rp->rio_buf; } size_t cnt = n > rp->rio_cnt ? rp->rio_cnt : n; memcpy(userbuf, rp->rio_bufptr, cnt); rp->rio_bufptr += cnt; rp->rio_cnt -= cnt; return cnt;}//读文本行,复制到内存位置userbuf,最后在结尾添NULL(0)ssize_t rio_readlineb(rio_t *rp, void *userbuf, size_t maxlen){ ssize_t readn; char c; char *buf = (char*)userbuf; int i; for(i = 1; i < maxlen; ++i) { readn = rio_read(rp, &c, 1); if(readn < 0) return -1; else if (readn == 0) { if(i == 1) return 0; else break; } else { *buf++ = c; if(c == '\n') { ++i; break; } } } *buf = 0; return i-1;}//打开监听描述符int open_listenfd(const char *port){ addrinfo hints, *listp, *p; int listenfd, optval = 1; memset(&hints, 0, sizeof(addrinfo)); hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; hints.ai_flags |= AI_NUMERICSERV; getaddrinfo(NULL, port, &hints, &listp); for(p = listp; p; p = p->ai_next) { if((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) continue; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int)); if(bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) break; close(listenfd); } freeaddrinfo(listp); if(!p) return -1; if(listen(listenfd, 3) < 0) { close(listenfd); return -1; } return listenfd;}int MAXLINE = 100;struct Pool{ int maxfd; //最大描述符 fd_set read_set; //读集合的描述符集合 fd_set ready_set; //准备好集合 int nready; //准备好描述符个数 int maxi; //客户端描述符数组最大下标 int clientfd[FD_SETSIZE]; //客户端描述符数组 rio_t clientrio[FD_SETSIZE];//客户端对应的读缓冲};void init_pool(int listenfd, Pool *p){ int i; p->maxi = -1; for(i = 0; i < FD_SETSIZE; ++i) { p->clientfd[i] = -1;//-1代表空槽位,刚开始没有请求客户端 } //刚开始只有监听描述符 p->maxfd = listenfd; FD_ZERO(&(p->read_set)); FD_SET(listenfd,&(p->read_set));}void add_client(int connfd, Pool *p){ int i; p->nready--; //找到空位,添加新客户端 for(i = 0; i < FD_SETSIZE; ++i) { if(p->clientfd[i]<0) { p->clientfd[i] = connfd; rio_readinitb(&p->clientrio[i], connfd); FD_SET(connfd, &p->read_set); if(connfd > p->maxfd) p->maxfd = connfd; if(i > p->maxi) p->maxi = i; break; } //数组已经满了,FD_SETSIZE为1024 if(i == FD_SETSIZE) perror("add_client error: Too many clients"); }}//服务器接收的总字节数int byte_cnt = 0;void check_clients(Pool * p){ int i, connfd, n; char buf[MAXLINE]; rio_t rio; for(i = 0; (i <= p->maxi) && (p->nready > 0); ++i) { connfd = p->clientfd[i]; rio = p->clientrio[i]; if((connfd > 0) && (FD_ISSET(connfd, &p->ready_set))) { p->nready--; if((n = rio_readlineb(&rio, buf, MAXLINE)) != 0) { //回送文本行 byte_cnt += n; printf("Server received %d (%d total) bytes on fd %D\n", n, byte_cnt, connfd); rio_writen(connfd, buf, n); } else//已经从客户端读完文本行,检测到EOF,关闭连接 { close(connfd); FD_CLR(connfd, &p->read_set); p->clientfd[i] = -1; } } }}int main(int argc, const char * argv[]) { // insert code here... int listenfd, connfd; socklen_t clientlen; sockaddr_storage clientaddr; static Pool pool; if(argc!=2) //两个命令行参数,可执行文件名和端口号 { fprintf(stderr, "usage: %s <port>\n", argv[0]); exit(1); } listenfd = open_listenfd(argv[1]); //打开监听描述符,argv[1]为端口号,这里选择8000 init_pool(listenfd, &pool); while(1) { //select函数读入读集合,输出准备好集合,所以每次都要更新pool.ready_set,作为读集合输入 pool.ready_set = pool.read_set; pool.nready = select(pool.maxfd+1, &pool.ready_set, NULL, NULL, NULL); if(FD_ISSET(listenfd,&pool.ready_set)) { clientlen = sizeof(sockaddr_storage); connfd = accept(listenfd, (sockaddr*)&clientaddr, &clientlen); add_client(connfd, &pool); } check_clients(&pool); }}
阅读全文
0 0
- IO多路复用并发服务器
- Linux并发服务器编程之IO多路复用
- IO多路复用服务器模型
- 服务器IO多路复用模型
- 并发方式 之io多路复用
- 并发服务器:多路复用I/O
- 高并发服务器设计之多路复用模型
- 高并发服务器设计之多路复用模型
- 【UNIX】网络编程/多路复用IO服务器编码
- IO多路复用
- IO多路复用
- IO多路复用
- IO多路复用
- IO 多路复用
- IO多路复用
- IO多路复用
- IO多路复用
- 多路复用IO
- 常用语音编解码介绍
- rabbit-mq 常见问题
- 《用户体验要素》阅读笔记
- Oracle中查看所有表和字段
- 前端笔试题的小结(2)
- IO多路复用并发服务器
- 漂亮的SVG时钟
- 以“吃鸡”手游为例,讲解如何进行游戏交互界面设计
- Java 并发编程:volatile的使用及其原理
- 学习记录 2017_12_18 ~ 2017_12_24
- Python之路【第四篇】:模块
- 和我了解IIFE语法
- SIFT算法原理(不带公式)
- 事件委托java