参照《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. [计算机网络] C++模拟telnet登陆SMTP服务发送邮件过程

    在百度文库中的<使用telnet协议收发邮件>,我们可以很清楚地看到如何通过telnet来进行发送邮件,下面是一些需要用到的命令,通过以下命令可以很容易实现邮件发送功能.为了更好地理解其中 ...

  2. asp.net MVC 导出查询结果到Excel

    首先在View视图中有一表单form,导出按钮<input class="btn export" type="button" value="导出 ...

  3. 【】Python】异常处理try...except、raise

    一.try...except 有时候我们写程序的时候,会出现一些错误或异常,导致程序终止.例如,做除法时,除数为0,会引起一个ZeroDivisionError 例子: 1 2 3 4 a=10 b= ...

  4. BZOJ 1818 内部白点(离散化+树状数组)

    此题就是1227 的弱化版. 画个图或者稍微证明一下就能够知道,一定不会超过一次变换. 那么我们只需要统计有多少个白点会变黑,换句话说就是有多少个白点上下左右都有黑点. 离散化横坐标,因为没有黑点在的 ...

  5. vue-cli开发时,ajax跨域详细解决办法

    在config/index.js中进行如下配置 [即在进行ajax请求时,地址中任何以/api开头的请求地址都被解析为目标地址,target就是你想要的后台接口地址] proxyTable: { '/ ...

  6. NOIP2002 提高组

    [NOIP2002] 提高组 T1.均分纸牌 算法:贪心(模拟) [分析]: 1.简化 2.过滤 3.辩证法  详见课件的例7 还有一种类似的思路是:求出平均值后,i←1 to n-1扫描,若a[i] ...

  7. 在@Async注解下RequestContextHolder.getRequestAttributes() 获得null的情况

    我们有的时候会在service层获取request填充一些诸如用户名和IP地址等信息,这个时候如果不想从Controller层传request,可以在service直接使用 HttpServletRe ...

  8. [洛谷P4980]【模板】Polya定理

    题目大意:给一个$n$个点的环染色,有$n$中颜色,问有多少种涂色方案是的旋转后本质不同 题解:$burnside$引理:$ans=\dfrac1{|G|}\sum\limits_{g\in G}A_ ...

  9. POJ2826:An Easy Problem?!——题解(配特殊情况图)

    http://poj.org/problem?id=2826 题目大意:给两条线,让它接竖直下的雨,问其能装多少横截面积的雨. ———————————————————————————— 水题,看题目即 ...

  10. HDOJ.1228 A + B (map)

    A + B 点我挑战题目 点我一起学习STL-MAP 题意分析 讲字符串和数字用map对应即可 代码总览 /* Title:HDOJ.1228 Author:pengwill Date:2016-11 ...