做网络服务的时候并发服务端程序的编写必不可少。前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定.
常见的linux并发服务器模型;
多进程并发服务器
多线程并发服务器
select多路I/O转接服务器
poll多路I/O转接服务器
epool多路I/O转接服务器.
本次主要讨论select多路I/O转接服务器模型:
使用select多路I/O转接服务器模型要考虑到以下几点:
1. select能监听的文件描述符个数受限于FD_SETSIZE, 一般为1024, 单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
2. 解决1024以下客户端请求使用select是很合适的, 但如果链接客户端过多, select采用的是轮询模型, 会大大降低服务器响应效率, 不应在select上投入更多精力.
主要API:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* execptfds, struct timeval* timmeout);
nfds: 监控的文件描述符集里最大文件描述符加1, 因为此参数会告诉内核横测前多少个文件描述符的状态
readfds: 监控有读数据到达文件描述符集合, 传入传出参数
writefds: 监控写数据到达文件描述符集合, 传入传出参数
execptfds: 监控异常发生达文件描述符集合, 如带外数据到达异常,
timeout: 设置阻塞监控时间,3种情况
1. NULL 永远等下去
2. 设置timeval, 等待固定的时间
3. 设置timeval里时间均为0, 检查描述字后立即返回, 轮询
void FD_ZERO(fd_set* set); //把文件描述符集合里初始化为0
void FD_SET(int fd, fd_set* set); //把文件描述符集合里fd位置1
void FD_CLR(int fd, fd_set* set); //把文件描述符集合里fd位置0
int FD_ISSET(int fd, fd_set* set); //测试文件描述符集合里fd是否为1 有返回1, 没有返回0
代码:
#include#include #include #include #include #include #include #include #define SERV_PORT 9096 //服务所占用端口 #define SERV_ADDR "10.10.101.105" //服务所占用ip int main(int argc, char* argv[]){ int listenfd, connfd, maxfd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; int opt, i, j, n, nready; char str[INET_ADDRSTRLEN]; //INET_ADDRSTRLEN 内置宏 16 char buf[BUFSIZ]; //BUFSIZ 内置宏 8192 int client[FD_SETSIZE]; //保存客户端描述符 int maxi; //记录客户端数组最大的下标 fd_set rset, allset; //创建监听套接字 //AF_INET: ipv4 //SOCK_STREAM tcp流类型 //IPPROTO_TCP tcp协议 listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //设置端口复用 opt = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //初始化为0 bzero(&serv_addr, sizeof(serv_addr)); //指定族: ipv4 serv_addr.sin_family = AF_INET; //指定端口号并转换成网络字节序 serv_addr.sin_port = htons(SERV_PORT); //指定ip并转换为网络字节序 inet_pton(AF_INET, SERV_ADDR, &serv_addr.sin_addr.s_addr); //绑定到监听套接字 bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //设置同时连接请求上限 listen(listenfd, SOMAXCONN); //将客户端数组全部置为-1 for(i = 0; i < FD_SETSIZE; i++){ client[i] = -1; } maxi = -1; //监听描述集初始化 FD_ZERO(&allset); //将监听套接字添加至监听集 FD_SET(listenfd, &allset); maxfd = listenfd; for(;;){ rset = allset; nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //判断是否有连接请求 if(FD_ISSET(listenfd, &rset)){ clie_addr_len = sizeof(clie_addr); //获取连接请求 connfd = accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len); //打印客户端信息 printf("%s:%d connect successfully!\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, str, sizeof(str)), ntohs(clie_addr.sin_port)); //添加至客户端集合 for(i = 0; i < FD_SETSIZE; i++){ if(0 > client[i]){ client[i] = connfd; break; } } //客户端数组已满 if(FD_SETSIZE == i){ printf("clients at full strength!\n"); continue;; } //添加至监听集 FD_SET(connfd, &allset); //判断i下标是否大于maxi if(i > maxi){ maxi = i; } //判断新的描述符是否大于maxfd if(connfd > maxfd){ maxfd = connfd; } //判断是否还有事件 if(0 == (--nready)){ continue; } } for(i = 0; i <= maxi; i++){ if(0 > client[i]) continue; if(FD_ISSET(client[i], &rset)){ bzero(buf, sizeof(buf)); n = read(client[i], buf, sizeof(buf)); //当对方关闭 if(0 == n){ clie_addr_len = sizeof(clie_addr); //获取客户端信息 getpeername(client[i], (struct sockaddr*)&clie_addr, &clie_addr_len); printf("%s:%d disconnect successfully!\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, str, sizeof(str)), ntohs(clie_addr.sin_port)); //关闭连接 close(client[i]); //从监听中清除 FD_CLR(client[i], &allset); //客户端数组位置恢复为-1 client[i] = -1; }else if(0 < n){ //全部转为大写 for(j = 0; j < n; j++){ buf[j] = toupper(buf[j]); } //回写给客户端 write(client[i], buf, n); } //判断是否还有事件 if(0 == (--nready)){ break; } } } } close(listenfd); }