参照《Unix网络编程》相关章节内容,实现了一个简单的单线程IO多路复用服务器与客户端。

普通迭代服务器,由于执行recvfrom则会发生阻塞,直到客户端发送数据并正确接收后才能够返回,一个服务器进程只能服务于一个客户端,解决这种问题可采用多线程方式(参见虚拟机隐藏进程检测工具实现)和IO多路复用select和poll,select()机制的优势在于可以同时等待多个描述符就绪。

与IO复用密切相关的另一种IO模型是在多线程中使用阻塞式IO。

简要描述select机制:

fd_set rset

void FD_ZERO(fd_set *fdset)

void FD_SET(int fd, fd_set *fdset)

void FD_CLR(int fd, fd_set *fdset)

void FD_ISSET(int fd, fd_set *fdset)

其中rset类型为fd_set,可理解为一个位图,用于标识哪些描述符正在被监听。FD_ZERO用于初始化rset,FD_SET用于设置新的用于被监听的描述符,FD_CLR用于清空rset,FD_ISSET用户判断具体哪个描述符就绪。

由于tcp服务器中具有监听套接字与已连接套接字两个概念,在服务器端实现中主要过程如下:

  1. 初始化rset,转2;
  2. 添加监听套接字到rset中,转3;
  3. select阻塞等待是否存在描述符就绪,转4;
  4. 当客户端连接后,rset中设置的监听套接字就绪,添加已连接套接字到rset,转5;
  5. 判断哪个描述符就绪(一般为新的已连接套接字),进行套接字读写操作,转1;

在《Unix网络编程》中,上述步骤5结束后直接转2,但在实际测试与资料查阅中,由于内核会修改描述符内容,使得需要重新初始化rset才能有效。

代码测试:

服务端:

 #include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <string.h>
#include <arpa/inet.h> #define IP_ADDR 127.0.0.1
#define IP_PORT 8081 #define LISTEN_NUM 10 #define MAXLINE 1024 int main(void)
{
struct sockaddr_in clitaddr, servaddr;
unsigned int clilen;
int listenfd, connfd, sockfd, ret; int maxindex, i, n; int maxfd;
fd_set allset; int client[FD_SETSIZE];
int nready; char buf[MAXLINE]; int reuse = ; listenfd = socket(AF_INET,SOCK_STREAM,); if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -) {
return -;
} bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(IP_PORT); ret = bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
if(ret == -) {
printf("bind socket error\n");
exit();
} ret = listen(listenfd,LISTEN_NUM);
if(ret == -) {
printf("listen socket error\n");
exit();
} maxfd = listenfd;
maxindex = -;
for(i = ; i < FD_SETSIZE; i++) {
client[i] = -;
}
//FD_ZERO(&allset);
//FD_SET(listenfd, &allset); for( ; ; ) {
//reset = allset;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
for (i = ; i <= maxindex; i++) {
FD_SET(client[i], &allset);
} nready = select(maxfd + , &allset, NULL, NULL, NULL); if(FD_ISSET(listenfd, &allset)) {
clilen = sizeof(clitaddr);
connfd = accept(listenfd, (struct sockaddr*)&clitaddr, &clilen);
//connfd = accept(listenfd, (struct sockaddr*)&clitaddr, sizeof(clitaddr)); //printf("clitaddr ip : %d,port : %d\n", inet_ntoa(clitaddr.sin_addr), clitaddr.sin_port);
fprintf(stdout, "accept clitaddr %s:%d\n", inet_ntoa(clitaddr.sin_addr), clitaddr.sin_port);
for(i = ; i < FD_SETSIZE; i++) {
if(client[i] < ){
client[i] = connfd;
break;
}
} if(i == FD_SETSIZE) {
printf("too many clients\n");
exit();
} FD_SET(connfd, &allset);
if(connfd > maxfd) {
maxfd = connfd;
}
if(i > maxindex) {
maxindex = i;
}
if(--nready <= )
continue;
} for(i = ; i <= maxindex; i++) {
if((sockfd = client[i]) < )
continue;
if(FD_ISSET(sockfd, &allset)) {
if((n = recv(sockfd, buf, MAXLINE, )) == ) {
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -;
}
else {
printf("server recv buf is %s\n", buf);
send(sockfd, buf, strlen(buf), );
}
if(--nready <= )
break;
}
}
} return ;
}

客户端:

 #include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <math.h> #define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 8081 #define MAXLINE 1024 void str_cli(FILE *fp, int sockfd)
{
int maxfdp, stdineof;
fd_set rset;
char buf[MAXLINE];
int n; stdineof = ;
FD_ZERO(&rset); for( ; ; ) {
if(stdineof == )
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp = (fileno(fp) > sockfd) ? (fileno(fp) + ) : (sockfd + );
//maxfdp = max(fileno(fp), sockfd) + 1;
select(maxfdp, &rset, NULL, NULL, NULL); if(FD_ISSET(sockfd, &rset)) {
if ((n = recv(sockfd, buf, MAXLINE, )) == ) {
if (stdineof = ) {
return ;
}
else {
printf("server prematurely\n");
exit();
}
}
printf("client:sockfd ready\n");
//write(fileno(stdout), buf, n);
write(fileno(stdout), buf, strlen(buf) + );
} if (FD_ISSET(fileno(fp), &rset)) {
if ((n = read(fileno(fp), buf, MAXLINE)) == ) {
stdineof = ;
shutdown(sockfd, SHUT_WR);
FD_CLR(fileno(fp), &rset);
continue;
}
//buf[n] = '\0';
printf("client buf is %s",buf);
send(sockfd, buf, strlen(buf), );
}
}
return ;
} int main(void)
{
int sockfd, ret;
struct sockaddr_in servaddr; sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < ) {
printf("create socket error\n");
exit();
} bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERVER_PORT);
servaddr.sin_addr.s_addr = inet_addr(SERVER_ADDR); ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if(ret < ) {
printf("connect error\n");
exit();
} str_cli(stdin, sockfd);
exit();
}

执行结果:

服务器启动后处于监听状态,可以启动多个客户端连入服务器请求并响应。实验结果略。

io多路复用-select()的更多相关文章

  1. 第五十五节,IO多路复用select模块加socket模块,伪多线并发

    IO多路复用select模块加socket模块,伪多线并发,并不是真正的多线程并发,实际通过循环等待还是一个一个处理的 IO多路复用,lo就是文件或数据的输入输出,IO多路复用就是可以多用户操作 IO ...

  2. Linux IO多路复用 select

    Linux IO多路复用 select 之前曾经写过简单的服务器,服务器是用多线程阻塞,客户端每一帧是用非阻塞实现的 后来发现select可以用来多路IO复用,就是说可以把服务器这么多线程放在一个线程 ...

  3. 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

    下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...

  4. Python实战之IO多路复用select的详细简单练习

    IO多路复用 I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. select   它通过一个select()系统调用来 ...

  5. I/O模型系列之五:IO多路复用 select、poll、epoll

    IO多路复用之select.poll.epoll IO多路复用:通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. 应用:适用于针 ...

  6. IO多路复用select/poll/epoll详解以及在Python中的应用

    IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...

  7. IO多路复用 select、poll、epoll

    什么是IO多路复用 在同一个线程里面, 通过拨开关的方式,来同时传输多个(socket)I/O流. 在英文中叫I/O multiplexing.这里面的 multiplexing 指的其实是在单个线程 ...

  8. Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程

    1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...

  9. 网络编程socket 结合IO多路复用select; epool机制分别实现单线程并发TCP服务器

    select版-TCP服务器 1. select 原理 在多路复用的模型中,比较常用的有select模型和epoll模型.这两个都是系统接口,由操作系统提供.当然,Python的select模块进行了 ...

随机推荐

  1. H3C SNMP配置解析

    华为交换机snmp配置 snmp-agent                                           /使能snmp服务/snmp-agent local-engineid ...

  2. imshow(A,[])和imshow(A)的区别

    imshow的用法: IMSHOW Display image. IMSHOW(I,N) displays the intensity image I with N discrete levels o ...

  3. 转:使用 python Matplotlib 库 绘图 及 相关问题

     使用 python Matplotlib 库绘图      转:http://blog.csdn.net/daniel_ustc/article/details/9714163 Matplotlib ...

  4. 【题解】Atcoder ARC#94 F-Normalization

    再次膜拜此强题!神级性质之不可能发现系列收藏++:首先,对于长度<=3的情况,我们采取爆搜答案(代码当中是打表).对于长度>=4的情况,则有如下几条玄妙的性质: 首先我们将 a, b, c ...

  5. 【题解】51nod 1686第K大区间

    成功的秘诀,在于克服自己看题解的冲动……[笑哭].自己A掉这题还是灰常开心的~ 以及爱死 two - pointer ! two - pointer 大法是真的好哇……这个题目有上一题的经验:求第\( ...

  6. 【倍增】LCM QUERY

    给一个序列,每次给一个长度l,问长度为l的区间中lcm最小的. 题解:因为ai<60,所以以某个点为左端点的区间的lcm只有最多60种的情况,而且相同的lcm区间的连续的. 所以就想到一个n*6 ...

  7. CF724E Goods transportation 最小割 DP

    照惯例CF的题不放原题链接... 题意:一个序列上有n个点,每个点有权值pi和si.表示这个点一开始有pi个物品,最多可以卖出si个物品,每个点都可以把物品向编号更大的点运输,但是对于i < j ...

  8. [CTSC2014]企鹅QQ hash

    ~~~题面~~~ 题解: 通过观察可以发现,其实题目就是要求长度相等的字符串中有且只有1位字符不同的 ”字符串对“ 有多少. 因为数据范围不大, 所以考虑一种暴力至极的方法. 我们枚举是哪一位不同,然 ...

  9. POJ3686:The Windy's——题解

    http://poj.org/problem?id=3686 题目大意: 有n个订单m个厂子,第i个订单在第j个厂子所需时间为zij,一个厂子做一个订单时不能做其他的订单. 求订单平均时间最小值. — ...

  10. BZOJ5336:[TJOI2018]游园会——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=5336 https://www.luogu.org/problemnew/show/P4590 小豆 ...