Linux下的TCP/IP编程----IO复用及IO复用服务端

http://blog.csdn.net/wqc_csdn/article/details/51583901

在之前我们实现的并发服务端时通过床将多个进程来实现的,这种并实现并发的方式简单方便,但是进程的创建和销毁是很消耗系统资源的,在访问量大时服务器很容易出现资源不够用的情况。除此之外,由于每个进程有独立的内存空间,所以进程间的通讯也相对比较复杂。因此我们可以考虑通过另一种方式来实现服务端的并发服务——IO复用。

复用:

复用在通讯领域很常见,一般常见”频分复用”,”时分复用”等名词。其实复用就是在一个通信频道内传递多个数据(信号)的技术。以频分复用为例:其实就是在一个通信信道内,发送端通过把信息加载在不同频率的波段上进行发送,而接受端在接受到波时通过滤波装置把各中频率的波进行分离,以此达到提高通信信道利用率的目的。

IO复用:

IO复用其实也是通过对IO描述符的复用来减少进程的创建,使得服务端始终只有一个进程,从而节省了系统资源,提高效率。


select()函数是最具有代表性的实现复用服务端的方法,它可以将多个文件描述符集中到一起进行统一监视,当监视到有文件描述符需要输入或者是输出时就选择该接口进行通讯,通讯完成之后就回到之前监视的状态。

监视内容:是否存在套接字接受数据?无需阻塞传输数据的套接字有哪些?哪些套接字发生了异常?

int select(int maxfd,fd_set *read_set, *write_set,fd_set *except_set, const struct timeval *timeout)选择描述符进行通讯:

  • maxfd(监视数量):监视对象文件描述符数量

  • read_set(读取文件描述符集合的地址):将所有关注”是否存在待读取数据”的文件描述符注册到fd_set集合中,并传递地址值。也就是说select()函数会监视这个集合里边的文件描述符是是否有待读取的数据,没有要监听的描述符时传0

  • write_set(写入文件描述符集合的地址):将所有关注”是否可传输无阻塞数据”的文件描述符注册到fd_set集合中,并传递地址值。也就是说select()函数会监视这个集合里边的文件描述符是否能发送无阻塞数据,没有要监听的描述符时传0

  • except_set(发生异常文件描述符集合的地址):将所有关注”是否可发生异常”的文件描述符注册到fd_set集合中,并传递地址值。也就是说select()函数会监视这个集合里边的文件描述符是否发生异常,没有要监听的描述符时传0

  • timeout(超时):位防止无限进入阻塞状态,设置一个超时信息

发生错误时返回-1,超时时返回0,当所关注的事件发生时,返回所发生事件的文件描述符数量

select()函数的使用比较复杂,大体分为三步:

  1. 参数设置:

    • 设置文件描述符:使用select()函数能同时监听多个文件描述符,首先要使用fd_set类型将这些文件描述符按照分类(接收,传输,异常)集中起来。

      fd_set是一个存有0和1的位数组。从下标0开始,一直到下标为当前文件描述符的最大序号为止,依次表示该文件描述符是否被监听,例如fd_set 变量fds[0]中的值为1时表示文件描述符0(标准的输入流)被监听。 
      针对fd_set的操作都是以位为单位的,为此专门编写了用于fd_set读写的宏定义:

      1. FD_ZERO(fd_set *fdset):将fd_set的所有位初始化为0

      2. FD_SET(int fd,fd_set *fdset):在fd_set中注册文件描述符fd的信息

      3. FD_CLR(int fd,fd_set *fdset):从fd_set中清除文件描述符fd的信息

      4. FD_ISSET(int fd,fd_set *fdset):查询fd_set中是否包含文件描述符fd的信息

    • 指定监听范围:指定监听文件描述符的范围,其实也就是fd_set中的文件描述符数量,由于每次新创建一个文件描述符时都会自动加1,所以要传入的值为最大的文件描述符+1(加一是由于文件描述符的标号从0开始)。

    • 设置超时:由于当文件描述符没有状态的改变时select()函数会始终处于阻塞状态,设置超时时间就是为了防止无限制的等待。即使文件描述符没有发生变化,只要过了指定时间,函数会返回0。这样在函数调用时能知道当前的状态。

      结构体timeval用于保存设置的超时时间,每次在调用select()函数之前都要重新设置超时时间,其结构体如下:

      struct timeval{long tv_sec;//秒数long tv_usec://毫秒数
      }
      • 1
      • 2
      • 3
      • 4
  2. 调用select()函数:监听注册的文件描述符的状态,当有状态发生变化,或者时超时时返回结果。

  3. 查看调用结果:当select()函数返回值是大于0的整数时说明是所监听的文件描述符的状态发生了变化,这时我们可以通过之前的fd_set变量来查看变化的结果。

    当select()函数调用完之后向其传入的fd_set变量将发生变化,原来为1的所有位均变为0,但是发生变化的文件描述符对应位除外,因此可以认为值仍为1的位置上的文件描述符发生了变化。


至此关于select()函数的介绍就结束了,用起来比较复杂,我们梳理一遍使用过程:

  1. 准备工作:

    • 为select()设置要监视的文件描述符集合,使用函数库提供的关于fd_set的宏定义设置fd_set

    • 为select()设置监视范围,即当前最大文件描述符+1

    • 位select(0设置超时时间,把秒数填入timeval结构体的tv_sec成员中,把毫秒数填入timeval结构体的tv_usec成员中,每次在调用select()函数之前都要重新设置超时时间。

  2. 调用select()函数

  3. 查看调用结果:根据fd_set调用前后的变化来确定发生变化的文件描述符,调用之后fd_set中值为1的位所对应的文件描述符状态发生了变化

  4. 调用发生变化的文件描述符进行相应的操作


在大体了解了select()函数的使用过程之后我们就可以尝试着进行一下简单的应用:

#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/select.h>#define BUFF_SIZE 30int main(){//声明文件描述符集合fd_set read_set;fd_set temp_set;//保存函数的返回结果int select_res;//字符串长度int str_len;//字符缓冲char buff[BUFF_SIZE];//超时时间结构体struct timeval time_out;//初始化fd_set,所有位都置0FD_ZERO(&read_set);//设置fd_set,使其监视文件描述符为0的文件描述符(系统的标准输入流)FD_SET(0,&read_set);while(1){temp_set = read_set;//设置超时时间time_out.tv_sec = 5;time_out.tv_usec = 0;//调用select()函数select_res = select(1,&temp_set,0,0,&time_out);//根据返回值来判断是否变化if(select_res == -1){puts("select() error");break;}else if(select_res == 0){puts("select() timeout");}else{//检查是否含有要查询的描述符if(FD_ISSET(0,&temp_set)){//从文件描述符为0的流中读取数据str_len = read(0,buff,BUFF_SIZE);buff[str_len] = 0;printf("message from console : %s ",buff);}}}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

IO复用的服务端:

/*************************************************************************> File Name: echo_select_server.c> Author: xjhznick> Mail: xjhznick@gmail.com > Created Time: 2015年03月26日 星期四 14时03分40秒> Description:使用select函数实现I/O复用服务器端************************************************************************/#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<sys/select.h>void error_handling(char *message);#define BUFF_SIZE 32int main(int argc, char *argv[])
{int server_sock;int client_sock;struct sockaddr_in server_addr;struct sockaddr_in client_addr;socklen_t client_addr_size;char buff[BUFF_SIZE];fd_set reads, reads_init;struct timeval timeout, timeout_init;int str_len, i, fd_max, fd_num;if(argc!=2){ //命令行中启动服务程序仅限一个参数:端口号printf("Usage : %s <port>\n", argv[0]);exit(1);}//调用socket函数创建套接字server_sock = socket(PF_INET, SOCK_STREAM, 0);if(-1 == server_sock){error_handling("socket() error.");}memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(atoi(argv[1]));//调用bind函数分配IP地址和端口号if( -1 == bind( server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) ){error_handling("bind() error");}//监听端口的连接请求,连接请求等待队列size为5if( -1 == listen(server_sock, 5) ){error_handling("listen() error");}//register fd_set varFD_ZERO(&reads_init);FD_SET(server_sock, &reads_init);//monitor socket: server_sockFD_SET(0, &reads_init);// stdin also worksfd_max = server_sock;//timeout_init.tv_sec = 5;timeout_init.tv_usec= 0;while(1){//调用select之后,除发生变化的文件描述符对应的bit,其他所有位置0,所以需用保存初值,通过复制使用reads = reads_init;//调用select之后,timeval成员值被置为超时前剩余的时间,因此使用时也需要每次用初值重新初始化timeout = timeout_init;fd_num = select(fd_max+1, &reads, NULL, NULL, &timeout);if(fd_num < 0){fputs("Error select()!", stderr);break;}else if(fd_num == 0){puts("Time-out!");continue;}for(i=0; i<=fd_max; i++){if(FD_ISSET(i, &reads)){if(i == server_sock){//connection request!//接受连接请求client_addr_size = sizeof(client_addr);client_sock = accept( server_sock, (struct sockaddr*)&client_addr, &client_addr_size );//accept函数自动创建数据I/0 socketif(-1 == client_sock){error_handling("accept() error");//健壮性不佳,程序崩溃退出} else{//注册与客户端连接的套接字文件描述符FD_SET(client_sock, &reads_init);if(fd_max < client_sock) fd_max = client_sock;printf("Connected client : %d\n", client_sock);}}else{//read message!str_len = read(i, buff, BUFF_SIZE);if(str_len){//echo to clientbuff[str_len] = 0;printf("Message from client %d: %s", i, buff);write(i, buff, str_len);}else{ //close connectionFD_CLR(i, &reads_init);close(i);printf("Disconnected client %d!\n", i);}}//end of i==server_sock}//end of if(FD_ISSET)}//end of for}//end of while//断开连接,关闭套接字close(server_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(EXIT_FAILURE);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383965.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UVa120

相当于是一个模拟&#xff0c;为了得到合适的顺序&#xff0c;我们的策略是每次找到当前没有被翻的里面最大的&#xff0c;然后把他翻到最前面&#xff0c;然后再翻到合适的位置。 需要判断一下当前是否已经有序&#xff0c;有序就不用翻了。 如果在最开头找到了合适的当前应…

二叉树的相关操作

二叉树的数据结构 typedef char SearchTreeType; typedef struct SearchTreeNode { SearchTreeType key; // 关键码 struct SearchTreeNode* lchild; struct SearchTreeNode* rchild; } SearchTreeNode; 二叉树的初始化 由于我们是用一个指向根节点的指针表示一个二叉树, …

网络层:网关协议

一. 网关 所谓的网管即就是之前路由器的名字, 即路由器和网关是一个东西 二. 内部网关协议 1. RIP协议 路由信息协议 RIP 是内部网关协议 IGP中最先得到的广泛使用的协议. 同时 RIP 是一种分布式基于距离向量的路由选择协议. RIP 协议要求网络中的每一个路由都必须维护自己…

UVa1152

题意很好理解&#xff0c;就是从四个集合里面取出四个数字的和为0&#xff0c;问有多少种取法。 直接枚举肯定是会超时的&#xff0c;所以得想办法优化一下。我们可以将两个集合的所有的和都放在一个数组里面&#xff0c;这样得到两个数组&#xff0c;然后排序&#xff0c;对第…

Linux函数--inet_pton / inet_ntop

http://blog.csdn.net/lindyl/article/details/10427925 inet_pton 和 inet_ntop Linux下这2个IP地址转换函数&#xff0c;可以在将IP地址在“点分十进制”和“整数”之间转换而且&#xff0c;inet_pton和inet_ntop这2个函数能够处理ipv4和ipv6。算是比较新的函数了。 inet_pto…

网络基础: 浅析应用层一

应用层 1. http协议 在 http 中协议分为了协议方案名, 登录信息名, 服务器地址, 服务器端口号(http协议绑定的端口号), 文件类型, 查询的字符串, 片段标识位 2. http 请求协议格式 httpp 总共分为三大部分, 其中首行即就是第一部分, 分为三个区域, 第一去个区域是请方法, 第…

socket 编程篇六之IPO多路复用-select poll epoll

http://blog.csdn.net/woxiaohahaa/article/details/51498951 文章参考自&#xff1a;http://blog.csdn.net/tennysonsky/article/details/45745887&#xff08;秋叶原 — Mike VS 麦克《Linux系统编程——I/O多路复用select、poll、epoll的区别使用》&#xff09; 此外&#x…

UVa11054

挺简单的小模拟。 还是要注意思维的方向&#xff0c;要有切入点&#xff0c;不能像无头苍蝇一样东想一下西想一下&#xff0c;而应该分析问题的性质&#xff0c;然后尝试思维的方向&#xff0c;从不同的方向思考&#xff08;顺序&#xff0c;逆序&#xff0c;排序后&#xff0…

浅谈传输层

1. 传输层的作用 在传输层中有两个特别重要的协议 TCP/UDP . 以快递员送快递为例说明这个问题吧. 在进行包裹传输的过程中快递员需要根据快递上的目的地址(目的计算机)来投递包裹(IP数据报), 加入在快递单上只写了收件人的所在地, 所在单位, 而只写了收件人的姓没有写收件人的…

UVa10375

题目描述很简单,就是求两个组合数的商.可是数字范围很大,肯定不能直接计算. 因此要用到唯一分解定理,即将结果全部表示为素因子的幂的形式. #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #include<cctype> #inc…

I/O复用的 select poll和epoll的简单实现

http://www.cnblogs.com/wj9012/p/3876734.html 一个tcp的客户端服务器程序 服务器端不变&#xff0c;客户端通过I/O复用轮询键盘输入与socket输入&#xff08;接收客户端的信息&#xff09; 服务器端&#xff1a; 1 /*服务器:2 1.客户端关闭后&#xff0c;服务器再向客户端发送…

netstat 相关命令解析

1.列出所有的端口 netstat -a 列出TCP协议的端口 netstat -at UDP协议的端口 netstat -au 2.列出处于监听状态的socket netstat -l 列出监听的TCP端口 netstat -lt 列出监听的UDP端口 netstat -lu 列出监听的UNIX端口 netstat -lx 3.列出协议的统计信息 nestat …

UVa10791

我们可以先用唯一分解定理将这个数字分解成素因子幂的乘积&#xff0c;为了得到最小的和&#xff0c;我们可以发现&#xff1a;每个 素因子的幂单独分开的和是最小的。 先说明每个素因子都是以出现的最大的次数出现。因为最小公倍数一定&#xff0c;因此至少有一个数字的这个素…

TCP相关代码

TCP 基础代码 //tcp_server.c #include<stdio.h> #include<error.h> #include<sys/types.h> #include<string.h> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include <arpa/inet.h> #include<st…

UVa1635

我们很容易发现最后每一项的系数就是二项式展开&#xff0c;余数和m没有关系就意味着可以被m整除&#xff0c;因此我们就需要求出每一个二项式的系数。但是数据实在太大我们根据唯一分解定理将m和系数都进行分解&#xff0c;然后比较因子的幂。 二项式的计算我们可以根据杨辉三…

几种并发服务器模型的实现:多线程,多进程,select,poll,epoll

http://www.cnblogs.com/wj9012/p/3879605.html 客户端使用select模型&#xff1a; 1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 #include <errno.h>5 #include <sys/types.h>6 #include <sys/socket.h>7 #include …

哈希表1

1. 初始化 void HashInit(HashTable* ht, HashFunc func) {if(ht NULL || func NULL){return;}ht -> size 0;ht -> func func;int i 0;for(; i < HashMaxSize; i){ht -> data[i].state Empty;} } 2. 哈希表的销毁 void HashDestroy(HashTable* ht) {if(ht…

UVa10820

实质上就是求欧拉函数值 书上有个板子挺好&#xff0c;也不难理解。 #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #include<cctype> #include<queue> #include<set>using namespace std;typedef l…

linux socket 编程(C语言)

https://www.cnblogs.com/x_wukong/p/4541010.html 最近看了一些网络编程的书籍&#xff0c;一直以来总感觉网络编程神秘莫测&#xff0c;其实网络编程入门还是很容易学的&#xff0c;下面这些代码是我在linux下编写的&#xff0c;已经运行过了&#xff0c;编译之后就可以运行了…

哈希表2

哈希表的初始化 void HashInit(HashTable* ht, HashFunc func) {if(ht NULL){return;}ht -> size 0;ht -> func func;size_t i 0;for(; i < MaxSize; i){ht -> data[i] NULL;} } 哈希表的结点创建 HashElem* CreateHashElemNode(KeyType key, ValueType va…