io多路复用-select()
参照《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服务器中具有监听套接字与已连接套接字两个概念,在服务器端实现中主要过程如下:
- 初始化rset,转2;
- 添加监听套接字到rset中,转3;
- select阻塞等待是否存在描述符就绪,转4;
- 当客户端连接后,rset中设置的监听套接字就绪,添加已连接套接字到rset,转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()的更多相关文章
- 第五十五节,IO多路复用select模块加socket模块,伪多线并发
IO多路复用select模块加socket模块,伪多线并发,并不是真正的多线程并发,实际通过循环等待还是一个一个处理的 IO多路复用,lo就是文件或数据的输入输出,IO多路复用就是可以多用户操作 IO ...
- Linux IO多路复用 select
Linux IO多路复用 select 之前曾经写过简单的服务器,服务器是用多线程阻塞,客户端每一帧是用非阻塞实现的 后来发现select可以用来多路IO复用,就是说可以把服务器这么多线程放在一个线程 ...
- 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】
下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...
- Python实战之IO多路复用select的详细简单练习
IO多路复用 I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. select 它通过一个select()系统调用来 ...
- I/O模型系列之五:IO多路复用 select、poll、epoll
IO多路复用之select.poll.epoll IO多路复用:通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. 应用:适用于针 ...
- IO多路复用select/poll/epoll详解以及在Python中的应用
IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...
- IO多路复用 select、poll、epoll
什么是IO多路复用 在同一个线程里面, 通过拨开关的方式,来同时传输多个(socket)I/O流. 在英文中叫I/O multiplexing.这里面的 multiplexing 指的其实是在单个线程 ...
- Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程
1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...
- 网络编程socket 结合IO多路复用select; epool机制分别实现单线程并发TCP服务器
select版-TCP服务器 1. select 原理 在多路复用的模型中,比较常用的有select模型和epoll模型.这两个都是系统接口,由操作系统提供.当然,Python的select模块进行了 ...
随机推荐
- MyBatis原理系列
原理分析之一:从JDBC到Mybatis 原理分析之二:框架整体设计 原理分析之三:初始化(配置文件读取和解析) 原理分析之四:一次SQL查询的源码分析
- asp.netMVC中实现分页方法
方法一:使用传统的sql语句实现分页, public class UserprintDao如下 /// <summary> /// 取得用户申请记录列表(按分页) /// </ ...
- Binding自动侦听
WPF的强大之一就是数据绑定,Binding是数据桥梁,它的两端是分别是源(Source)和目标(Target),一个简单的类的属性值发生变化,会自动反映在UI界面上,这个属性就是Binding的Pa ...
- Gevent-自动挡切换
Gevent: Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程. G ...
- STL使用记录
1,map 对map实在不熟...赶紧记录一下用法吧. 后来再发现新的用法再补充吧 定义: map<int, int> m; 其中的int可以为自定义的任何类型. m[key值类型的变量] ...
- BZOJ1009:[HNOI2008]GT考试——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1009 Description 阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0&l ...
- Dalvik虚拟机中DexClassLookup结构解析
http://blog.csdn.net/roland_sun/article/details/46877563 原文如下: 在Android系统中,所有的类定义以及具体的代码都是包含在DEX文件中的 ...
- JavaScript截取中英文字符串
有时在显示某段文字的时候,可能会太长,影响我们页面的显示效果.如果仅是英文,那么我们可以用String.substring(start, end)函数就已经够用了.但是通常我们都会遇到既有英文,又有汉 ...
- Indexing GROUP BY
SQL databases use two entirely different group by algorithms. The first one, the hash algorithm, agg ...
- Leetcode 138. 复制带随机指针的链表
1.题目要求 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点. 要求返回这个链表的深度拷贝. 2.解题思路 (1)笔试思路(求速度,拿分数):使用哈希表 /* ...