基于select模型的TCP服务器
2016-07-29 20:36
615 查看
之前的一篇博文是基于TCP的服务器和客户机程序,今天在这我要实现一个基于select模型的TCP服务器(仅实现了服务器)。
socket套接字编程提供了很多模型来使服务器高效的接受客户端的请求,select就是其中之一。
了解select模型我们先来看一下的代码:
int iResult = recv(s, buffer,1024);
这 是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返 回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永 远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。Select
模型就是为了解决这个问题而出现的。
select函数:
参数nfds是需要监视的最⼤的⽂件描述符值+1;
rdset,wrset,exset分别对应于需要检测的可读⽂件描述符的集合,可写⽂件描述符的集 合及异
常⽂件描述符的集合。
struct timeval结构⽤于描述⼀段时间长度,如果在这个时间内,需要监视的描述符没有事件
发⽣则函数返回,返回值为0。
select返回fd_set中可用的套接字个数。
下⾯的宏提供了处理这三种描述词组的⽅式:
FD_CLR(inr fd,fd_set* set);⽤来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);⽤来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);⽤来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);⽤来清除描述词组set的全部位
函数返回值:
执⾏成功则返回⽂件描述词状态已改变的个数。
如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;
当有错误发⽣时则返回-1, 错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误值可能为:
EBADF ⽂件描述词为⽆效的或该⽂件已关闭。
EINTR 此调⽤被信号所中断。
EINVAL 参数n 为负值。
ENOMEM 核⼼内存不⾜。
根据以上的只是前提,我们可以得到select的特点:
select模型的特点:
(1)可监控的⽂件描述符个数取决与sizeof(fd_set)的值。我这边服务 器上sizeof(fd_set)=
512,每bit表⽰⼀个⽂件描述符,则我服务器上⽀持的最⼤⽂件描述符是512*8=4096。据说
可调,另有说虽 然可调,但调整上限受于编译内核时的变量值。本⼈对调整fd_set的⼤⼩不
http://www.cppblog.com /CppExplore/archive/2008/03/21/45061.html 太感兴趣,参考中的模
型2(1)可以有效突破select可监控的⽂件描述符上 限。
(2)将fd加⼊select监控集的同时,还要再使⽤⼀个数据结构array保存放到select监控集
中的fd,⼀是⽤于再select 返回后,array作为源数据和fd_set进⾏FD_ISSET判断。⼆是select
返回后会把以前加⼊的但并⽆事件发⽣的fd清空,则每次开始 select前都要重新从array取得fd
逐⼀加⼊(FD_ZERO最先),扫描array的同时取得fd最⼤值maxfd,⽤于select的第⼀个 参
数。
(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array
(FD_ISSET判断是否有时间发⽣)。
以下是select模型的工作过程:
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
基于select模型的TCP服务器的实现:
socket套接字编程提供了很多模型来使服务器高效的接受客户端的请求,select就是其中之一。
了解select模型我们先来看一下的代码:
int iResult = recv(s, buffer,1024);
这 是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返 回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永 远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。Select
模型就是为了解决这个问题而出现的。
select函数:
<span style="font-family:Microsoft YaHei;font-size:14px;">int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout );</span>
参数nfds是需要监视的最⼤的⽂件描述符值+1;
rdset,wrset,exset分别对应于需要检测的可读⽂件描述符的集合,可写⽂件描述符的集 合及异
常⽂件描述符的集合。
struct timeval结构⽤于描述⼀段时间长度,如果在这个时间内,需要监视的描述符没有事件
发⽣则函数返回,返回值为0。
<span style="font-family:Microsoft YaHei;font-size:14px;">timeval结构体: struct timeval { long tv_sec; //秒 long tv_usec; //毫秒 };</span>
select返回fd_set中可用的套接字个数。
下⾯的宏提供了处理这三种描述词组的⽅式:
FD_CLR(inr fd,fd_set* set);⽤来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);⽤来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);⽤来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);⽤来清除描述词组set的全部位
函数返回值:
执⾏成功则返回⽂件描述词状态已改变的个数。
如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;
当有错误发⽣时则返回-1, 错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误值可能为:
EBADF ⽂件描述词为⽆效的或该⽂件已关闭。
EINTR 此调⽤被信号所中断。
EINVAL 参数n 为负值。
ENOMEM 核⼼内存不⾜。
根据以上的只是前提,我们可以得到select的特点:
select模型的特点:
(1)可监控的⽂件描述符个数取决与sizeof(fd_set)的值。我这边服务 器上sizeof(fd_set)=
512,每bit表⽰⼀个⽂件描述符,则我服务器上⽀持的最⼤⽂件描述符是512*8=4096。据说
可调,另有说虽 然可调,但调整上限受于编译内核时的变量值。本⼈对调整fd_set的⼤⼩不
http://www.cppblog.com /CppExplore/archive/2008/03/21/45061.html 太感兴趣,参考中的模
型2(1)可以有效突破select可监控的⽂件描述符上 限。
(2)将fd加⼊select监控集的同时,还要再使⽤⼀个数据结构array保存放到select监控集
中的fd,⼀是⽤于再select 返回后,array作为源数据和fd_set进⾏FD_ISSET判断。⼆是select
返回后会把以前加⼊的但并⽆事件发⽣的fd清空,则每次开始 select前都要重新从array取得fd
逐⼀加⼊(FD_ZERO最先),扫描array的同时取得fd最⼤值maxfd,⽤于select的第⼀个 参
数。
(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array
(FD_ISSET判断是否有时间发⽣)。
以下是select模型的工作过程:
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
基于select模型的TCP服务器的实现:
<span style="font-family:Microsoft YaHei;font-size:14px;">#include<stdio.h> #include<string.h> #include<stdlib.h> #include<sys/socket.h> #include<sys/types.h> #include<sys/select.h> #include<netinet/in.h> #define _MAX_SIZE_ 10 int fd_arr[_MAX_SIZE_]; int max_fd=0; static void Useage(const char* proc) { printf("Useage:%s,[ip][port]"); exit(1); } static int add_fd_arr(int fd) { //fd add to fd_arr int i=0; for(;i<_MAX_SIZE_;++i) { if(fd_arr[i]==-1) { fd_arr[i]=fd; return 0; } } return 1; } int select_server(char* ip,char* port) { struct sockaddr_in ser; struct sockaddr_in cli; fd_set fds; int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0) { perror("socket()"); exit(2); } int yes=1; setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)); memset(&ser,'\0',sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(port); ser.sin_addr.s_addr=ip; if(bind(fd,(struct sockaddr*)&ser,sizeof(ser))<0) { perror("bind()"); exit(3); } //init fd_arr int i=0; for(;i<_MAX_SIZE_;++i) { fd_arr[i]=-1; } add_fd_arr(fd); FD_ZERO(&fds); if(listen(fd,5)<0) { perror("listen"); exit(4); } while(1) { //reset fd_arr for(i=0;i<_MAX_SIZE_;++i) { if(fd_arr[i]!=-1) { FD_SET(fd_arr[i],&fds); if(fd_arr[i]>max_fd) { max_fd=fd_arr[i]; } } } struct timeval timeout={3,0}; switch(select(max_fd+1,&fds,NULL,NULL,&timeout)) { case -1: { perror("select"); exit(5); break; } case 0: { printf("select timeout......"); break; } default: { for(i=0;i<_MAX_SIZE_;++i) { if(i==0&&fd_arr[i]!=-1&&FD_ISSET(fd_arr[i],&fds)) { socklen_t len=sizeof(cli); int new_fd=accept(fd,(struct sockaddr*)&cli,&len); if(-1!=new_fd) { printf("get a new link"); if(1==add_fd_arr(new_fd)) { perror("fd_arr is full,close new_fd\n"); close(new_fd); } } continue; } if(fd_arr[i]!=-1&&FD_ISSET(fd_arr[i],&fds)) { char buf[1024]; memset(buf,'\0',sizeof(buf)); ssize_t size=recv(fd_arr[i],buf,sizeof(buf)-1,0); if(size==0||size==-1) { printf("remote client close,size is%d\n",size); int j=0; for(;j<_MAX_SIZE_;++j) { if(fd_arr[j]==fd_arr[i]) { fd_arr[j]=-1; break; } } close(fd_arr[i]); FD_CLR(fd_arr[i],&fds); }else { printf("fd:%d,msg:%s",fd_arr[i],buf); } } } } break; } } } int main(int argc,char* argv[]) { if(argc!=3) { Useage(argv[0]); } select_server(argv[1],argv[2]); return 0; } </span>
相关文章推荐
- Lua编程示例(一):select、debug、可变参数、table操作、error
- jquery 获取select数组与name数组长度的实现代码
- SQL学习笔记三 select语句的各种形式小结
- 一条select语句引起的瓶颈问题思考
- SQL Select语句完整的执行顺序
- mysql SELECT语句去除某个字段的重复信息
- 点击按钮后 文本框变为Select下拉列表框
- javascript 模拟select下拉列表特效
- javascript select options 排序(保持option 对象完整性)
- 用javascript和css模拟select的脚本
- js select常用操作控制代码
- mysql中insert与select的嵌套使用方法
- jquery的clone方法应用于textarea和select的bug修复
- SQLServer中SELECT语句的执行顺序
- MySQL进阶SELECT语法篇
- asp中 select top 问题!~
- ASP中获得Select Count语句返回值的方法
- 探讨select in 在postgresql的效率问题
- 数据库插入数据之select into from与insert into select区别详解
- Javascript select控件操作大全(新增、修改、删除、选中、清空、判断存在等)