参照《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. MyBatis原理系列

    原理分析之一:从JDBC到Mybatis 原理分析之二:框架整体设计 原理分析之三:初始化(配置文件读取和解析) 原理分析之四:一次SQL查询的源码分析

  2. asp.netMVC中实现分页方法

    方法一:使用传统的sql语句实现分页,    public class UserprintDao如下 /// <summary> /// 取得用户申请记录列表(按分页) /// </ ...

  3. Binding自动侦听

    WPF的强大之一就是数据绑定,Binding是数据桥梁,它的两端是分别是源(Source)和目标(Target),一个简单的类的属性值发生变化,会自动反映在UI界面上,这个属性就是Binding的Pa ...

  4. Gevent-自动挡切换

    Gevent: Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程. G ...

  5. STL使用记录

    1,map 对map实在不熟...赶紧记录一下用法吧. 后来再发现新的用法再补充吧 定义: map<int, int> m; 其中的int可以为自定义的任何类型. m[key值类型的变量] ...

  6. BZOJ1009:[HNOI2008]GT考试——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=1009 Description 阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0&l ...

  7. Dalvik虚拟机中DexClassLookup结构解析

    http://blog.csdn.net/roland_sun/article/details/46877563 原文如下: 在Android系统中,所有的类定义以及具体的代码都是包含在DEX文件中的 ...

  8. JavaScript截取中英文字符串

    有时在显示某段文字的时候,可能会太长,影响我们页面的显示效果.如果仅是英文,那么我们可以用String.substring(start, end)函数就已经够用了.但是通常我们都会遇到既有英文,又有汉 ...

  9. Indexing GROUP BY

    SQL databases use two entirely different group by algorithms. The first one, the hash algorithm, agg ...

  10. Leetcode 138. 复制带随机指针的链表

    1.题目要求 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点. 要求返回这个链表的深度拷贝. 2.解题思路 (1)笔试思路(求速度,拿分数):使用哈希表 /* ...