Linux下套接字具体解释(九)---poll模式下的IO多路复用server
參照
poll调用深入解析-从poll的实现来讲poll多路复用模型,非常有深度
poll多路复用
poll的机制与select相似,与select在本质上没有多大差别。管理多个描写叙述符也是进行轮询,依据描写叙述符的状态进行处理,可是poll没有最大文件描写叙述符数量的限制。
poll和select相同存在一个缺点就是。包括大量文件描写叙述符的数组被总体复制于用户态和内核的地址空间之间,而不论这些文件描写叙述符是否就绪,它的开销随着文件描写叙述符数量的添加而线性增大。
poll编程模型
函数原型
函数格式例如以下所看到的:
# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
參数说明
fds
是一个struct pollfd结构类型的数组,用于存放须要检測其状态的Socket描写叙述符。每当调用这个函数之后。系统不会清空这个数组。操作起来比較方便;特别是对于socket连接比較多的情况下,在一定程度上能够提高处理的效率;这一点与select()函数不同。调用select()函数之后。select()函数会清空它所检測的socket描写叙述符集合,导致每次调用select()之前都必须把socket描写叙述符又一次加入到待检測的集合中。因此,select()函数适合于仅仅检測一个socket描写叙述符的情况,而poll()函数适合于大量socket描写叙述符的情况。
nfds
nfds_t类型的參数,用于标记数组fds中的结构体元素的总数量;
timeout
是poll函数调用堵塞的时间。单位:毫秒;
和 select 一样,最后一个參数 timeout 指定 poll() 将在超时前等待一个事件多长事件。这里有 3 种情况:
timeout 为 -1
这会造成 poll 永远等待。poll() 仅仅有在一个描写叙述符就绪时返回。或者在调用进程捕捉到信号时返回(在这里。poll 返回 -1),而且设置 errno 值为 EINTR 。-1 能够用宏定义常量 INFTIM 来取代(在 pth.h 中有定义) 。
timeout 等于0
在这样的情况下,測试全部的描写叙述符,而且 poll() 立马返回。这同意在 poll 中没有堵塞的情况下找出多个文件描写叙述符的状态。time > 0
这将以毫秒为单位指定 timeout 的超时周期。poll() 仅仅有在超时到期时返回,除非一个描写叙述符变为就绪,在这样的情况下,它立马返回。假设超时周期到齐。poll() 返回 0。这里也可能会由于某个信号而中断该等待。
和 select 一样,文件描写叙述符是否堵塞对 poll 是否堵塞没有不论什么影响。
返回值和错误代码
成功时,poll()返回结构体中revents域不为0的文件描写叙述符个数;假设在超时前没有不论什么事件发生。poll()返回0;失败时,poll()返回-1
>0
数组fds中准备好读、写或出错状态的那些socket描写叙述符的总数量;
==0
数组fds中没有不论什么socket描写叙述符准备好读、写,或出错;此时poll超时,超时时间是timeout毫秒。换句话说。假设所检測的socket描写叙述符上没有不论什么事件发生的话,那么poll()函数会堵塞timeout所指定的毫秒时间长度之后返回,假设timeout==0。那么poll() 函数马上返回而不堵塞,假设timeout==INFTIM,那么poll() 函数会一直堵塞下去。直到所检測的socket描写叙述符上的感兴趣的事件发生是才返回,假设感兴趣的事件永远不发生。那么poll()就会永远堵塞下去;
-1
poll函数调用失败,同一时候会自己主动设置全局变量errno为下列值之中的一个
errno | 描写叙述 |
---|---|
EBADF | 一个或多个结构体中指定的文件描写叙述符无效 |
EFAULTfds | 指针指向的地址超出进程的地址空间 |
EINTR | 请求的事件之前产生一个信号。调用能够又一次发起 |
EINVALnfds | 參数超出PLIMIT_NOFILE值 |
ENOMEM | 可用内存不足,无法完毕请求 |
pollfd结构体
struct pollfd
{
int fd; /* 文件描写叙述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
fd 成员表示感兴趣的,且打开了的文件描写叙述符;
events 成员是位掩码,用于指定针对这个文件描写叙述符感兴趣的事件;
revents 成员是位掩码,用于指定当 poll 返回时,在该文件描写叙述符上已经发生了哪些事情。
每个pollfd结构体指定了一个被监视的文件描写叙述符,能够传递多个结构体,指示poll()监视多个文件描写叙述符。
事件
在poll返回时,我们能够检查revents中的标志,相应于文件描写叙述符请求的events结构体。假设POLLIN事件被设置,则文件描写叙述符能够被读取而不堵塞。
假设POLLOUT被设置。则文件描写叙述符能够写入而不导致堵塞。这些标志并非相互排斥的:它们可能被同一时候设置,表示这个文件描写叙述符的读取和写入操作都会正常返回而不堵塞。
timeout參数指定等待的毫秒数,不管I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生。timeout为0指示poll调用马上返回并列出准备好I/O的文件描写叙述符,但并不等待其他的事件。
这样的情况下,poll()就像它的名字那样,一旦选举出来,马上返回。
event注冊的事件,通过revents返回
每个结构体的events域是监视该文件描写叙述符的事件掩码,由用户来设置这个域。
revents域是文件描写叙述符的操作结果事件掩码。内核在调用返回时设置这个域。
events域中请求的不论什么事件都可能在revents域中返回。
比如fds[0].events = POLLIN; /将測试条件设置成普通或优先级带数据可读/
然后 int pollresult = poll(fds,xx,xx); //这样就能够监听fds里面文件描写叙述符了。当满足特定条件就返回,并将结果保存在revents中。
事件描写叙述符概述
合法的事件例如以下:
事件 | 描写叙述 |
---|---|
POLLIN | 有数据可读 |
POLLRDNORM | 有普通数据可读 |
POLLRDBAND | 有优先数据可读。 |
POLLPRI | 有紧迫数据可读。 |
POLLOUT | 写数据不会导致堵塞 |
POLLWRNORM | 写普通数据不会导致堵塞 |
POLLWRBAND | 写优先数据不会导致堵塞 |
POLLMSGSIGPOLL | 消息可用。 |
此外,revents域中还可能返回下列事件:
事件 | 描写叙述 |
---|---|
POLLER | 指定的文件描写叙述符错误发生 |
POLLHUP | 指定的文件描写叙述符挂起事件 |
POLLNVAL | 指定的文件描写叙述符非法 |
这些事件在events域中无意义,由于它们在合适的时候总是会从revents中返回。
事件使用技巧
- POLLIN
events 中使用该宏常数,能够在折本文件的可读情况下。结束 poll() 函数。相反。revents 上使用该宏常数。在检查 poll() 函数结束后。可依此推断设备文件是否处于可读状态(即使消息长度是 0)。
- POLLPRI
在 events 域中使用该宏常数,能够在设备文件的高优先级数据读取状态下,结束 poll() 函数。相反。revents 上使用该宏常数,在检查 poll() 函数结束后。可依此推断设备文件是否处于可读高优先级数据的状态(即使消息长度是 0)。该宏常数用于处理网络信息包(packet) 的数据传递。
- POLLOUT
在 events 域中使用该宏常数,能够在设备文件的写入状态下,结束 poll() 函数。相反。revents 域上使用该宏常数。在检查 poll() 结束后,可依此推断设备文件是否处于可写状态。
- POLLERR
在 events 域中使用该宏常数,能够在设备文件上错误发生时,结束 poll() 函数。
相反,revents 域上使用该宏函数。在检查 poll() 函数结束后,可依此推断设备文件是否出错。
- POLLHUP
在 events域中使用该宏常数,能够在设备文件里发生 hungup 时,结束 poll() 函数 。相反,在检查 poll() 结束后,可依此推断设备文件是否发生 hungup 。
- POLLNVAL
在 events 域中使用该宏函数,能够在文件描写叙述符的值无效时,结束 poll() 。相反,在 revents 域上使用该宏函数时。在检查 poll() 函数后,文件描写叙述符是否有效。
可用于处理网络信息时,检查 socket handler 是否已经无效。
poll与select的差别与联系
使用poll()和select()不一样。你不须要显式地请求异常情况报告。
POLLIN | POLLPRI等价于select()的读事件,
POLLOUT |POLLWRBAND等价于select()的写事件。
POLLIN等价于POLLRDNORM |POLLRDBAND,
而POLLOUT则等价于POLLWRNORM。
比如,要同一时候监视一个文件描写叙述符是否可读和可写,我们能够设置 events为POLLIN |POLLOUT。
演示样例
poll的本质是轮训,就是监听我们全部的文件描写叙述符的所注冊的事件,当有事件请求时。poll返回。然后我们轮询全部的描写叙述符,找到有时间请求的那个就可以
服务器server
#define _GNU_SOURCE 1
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/poll.h>
#define USER_LIMIT 5
#define BUFFER_SIZE 64
#define FD_LIMIT 65535
typedef struct client_data // 客户端的数据结构
{
struct sockaddr_in address; //
char* write_buf; // 发送数据缓冲区
char buf[ BUFFER_SIZE ]; // 接收数据缓冲区
}client_data;
int setnonblocking( int fd )
{
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
int listenfd;
int ret = 0;
struct sockaddr_in address;
client_data *users = NULL;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
//
// 创建服务器的监听套接字
//
if( ( listenfd = socket( PF_INET, SOCK_STREAM, 0 ) ) < 0)
{
perror("create socket error...\n");
exit(-1);
}
else
{
printf("create socket success...\n");
}
//
// 命名服务器的监听套接字
//
if((ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address))) < 0 )
{
perror("bind socket error...\n");
exit(-1);
}
else
{
printf("bind socket success...\n");
}
if((ret = listen(listenfd, 5)) < 0)
{
perror("listen error...\n");
}
else
{
printf("start listen...\n");
}
assert( ret != -1 );
if((users = (client_data *)malloc(sizeof(client_data) * FD_LIMIT)) == NULL)
{
perror("malloc client_data error...");
}
else
{
printf("malloc client_data success...");
}
struct pollfd fds[USER_LIMIT + 1];
/* Data structure describing a polling request.
struct pollfd
{
int fd; poll 的文件描写叙述符.
short int events; fd 上感兴趣的事件(等待的事件).
short int revents; fd 上实际发生的事件.
};
*/
/// 初始化poll的
int user_counter = 0;
for( int i = 1; i <= USER_LIMIT; ++i )
{
fds[i].fd = -1;
fds[i].events = 0;
}
fds[0].fd = listenfd;
fds[0].events = POLLIN | POLLERR; // POLLIN表示有数据可读, POLLERR表示出错
fds[0].revents = 0;
while( 1 )
{
ret = poll( fds, // 准备轮训的套接字文件描写叙述符
user_counter + 1, //
-1); // poll 永远等待。poll() 仅仅有在一个描写叙述符就绪时返回,或者在调用进程捕捉到信号时返回
if ( ret < 0 )
{
printf( "poll failure\n" );
break;
}
///
/// poll模型的本质就是轮训, 在pull返回时,轮询全部的文件描写叙述符, 查找到有事情请求的那个文件
///
for( int i = 0; i < user_counter + 1; ++i )
{
if((fds[i].fd == listenfd) /* 监听的是服务器套接字, 此时假设有数据可读,说明有客户端请求链接*/
&& (fds[i].revents & POLLIN)) /* 有数据可读取 */
{
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof( client_address );
// 開始接收客户端的链接
int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
continue;
}
if( user_counter >= USER_LIMIT )
{
const char* info = "too many users\n";
printf( "%s", info );
send( connfd, info, strlen( info ), 0 );
close( connfd );
continue;
}
user_counter++;
users[connfd].address = client_address;
setnonblocking( connfd );
fds[user_counter].fd = connfd;
fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR;
fds[user_counter].revents = 0;
printf( "comes a new user, now have %d users\n", user_counter );
}
else if( fds[i].revents & POLLERR ) // 数据出错
{
printf( "get an error from %d\n", fds[i].fd );
char errors[ 100 ];
memset( errors, '\0', 100 );
socklen_t length = sizeof( errors );
if( getsockopt( fds[i].fd, SOL_SOCKET, SO_ERROR, &errors, &length ) < 0 )
{
printf( "get socket option failed\n" );
}
continue;
}
else if( fds[i].revents & POLLRDHUP ) // 被挂起---断开
{
users[fds[i].fd] = users[fds[user_counter].fd];
close( fds[i].fd );
fds[i] = fds[user_counter];
i--;
user_counter--;
printf( "a client left\n" );
}
else if( fds[i].revents & POLLIN ) // 客户端套接字有数据可写
{
int connfd = fds[i].fd;
memset( users[connfd].buf, '\0', BUFFER_SIZE );
ret = recv( connfd, users[connfd].buf, BUFFER_SIZE-1, 0 );
printf( "get %d bytes of client data %s from %d\n", ret, users[connfd].buf, connfd );
if( ret < 0 )
{
if( errno != EAGAIN )
{
close( connfd );
users[fds[i].fd] = users[fds[user_counter].fd];
fds[i] = fds[user_counter];
i--;
user_counter--;
}
}
else if( ret == 0 )
{
printf( "code should not come to here\n" );
}
else
{
for( int j = 1; j <= user_counter; ++j )
{
if( fds[j].fd == connfd )
{
continue;
}
fds[j].events |= ~POLLIN;
fds[j].events |= POLLOUT;
users[fds[j].fd].write_buf = users[connfd].buf;
}
}
}
else if( fds[i].revents & POLLOUT ) // 服务器向外发送数据
{
int connfd = fds[i].fd;
if( ! users[connfd].write_buf )
{
continue;
}
ret = send( connfd, users[connfd].write_buf, strlen( users[connfd].write_buf ), 0 );
users[connfd].write_buf = NULL;
fds[i].events |= ~POLLOUT;
fds[i].events |= POLLIN;
}
}
}
free(users);
close( listenfd );
return 0;
}
服务器client
#define _GNU_SOURCE 1
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <fcntl.h>
#define BUFFER_SIZE 64
int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
struct sockaddr_in server_address;
bzero( &server_address, sizeof( server_address ) );
server_address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &server_address.sin_addr );
server_address.sin_port = htons( port );
int sockfd = socket( PF_INET, SOCK_STREAM, 0 );
assert( sockfd >= 0 );
if ( connect( sockfd, ( struct sockaddr* )&server_address, sizeof( server_address ) ) < 0 )
{
printf( "connection failed\n" );
close( sockfd );
return 1;
}
struct pollfd fds[2];
// 加入标准输入
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[0].revents = 0;
// 加入套接字描写叙述符
fds[1].fd = sockfd;
fds[1].events = POLLIN | POLLRDHUP;
fds[1].revents = 0;
char read_buf[BUFFER_SIZE];
int pipefd[2];
int ret = pipe( pipefd );
assert( ret != -1 );
while( 1 )
{
ret = poll( fds, 2, -1 );
if( ret < 0 )
{
printf( "poll failure\n" );
break;
}
if( fds[1].revents & POLLRDHUP )
{
printf( "server close the connection\n" );
break;
}
else if( fds[1].revents & POLLIN )
{
memset( read_buf, '\0', BUFFER_SIZE );
recv( fds[1].fd, read_buf, BUFFER_SIZE-1, 0 );
printf( "%s\n", read_buf );
}
if( fds[0].revents & POLLIN )
{
ret = splice( 0, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
ret = splice( pipefd[0], NULL, sockfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
}
}
close( sockfd );
return 0;
}
Linux下套接字具体解释(九)---poll模式下的IO多路复用server的更多相关文章
- Linux下套接字具体解释(三)----几种套接字I/O模型
參考: 网络编程–IO模型演示样例 几种server端IO模型的简介及实现 背景知识 堵塞和非堵塞 对于一个套接字的 I/O通信,它会涉及到两个系统对象.一个是调用这个IO的进程或者线程,还有一个就是 ...
- 【转载】Linux下套接字学习
感觉这个系列还不错,学习一下. 先看的是第三篇: http://blog.csdn.net/gatieme/article/details/46334337 < Linux下套接字详解(三)-- ...
- Linux编程---套接字
网络相关的东西差点儿都是建立在套接字之上.所以这个内容对于程序猿来说还是蛮重要的啊. 事实上套接字也就是一个特殊的设备文件而已,我始终不能明确为什么要叫套接字.这么个奇怪的名字.只是还是就这样算了吧. ...
- linux 网络套接字
在内核分析网络分组时,底层协议的数据将传输到跟高的层.而发送数据的时候顺序是相反的.每一层都是通过加(首部+净荷)传向跟底层,直至最终发送. 这些操作决定了网络的的性能. 就如下图所示 linux因此 ...
- LINUX TCP套接字详细配置
提高服务器的负载能力,是一个永恒的话题.在一台服务器CPU和内存资源额定有限的情况下,最大的压榨服务器的性能,是最终的目的.要提高 Linux系统下的负载能力,可以先启用Apache的Worker模式 ...
- Linux TCP套接字选项 之 SO_REUSEADDR && SO_REUSEPORT
说明 前面从stackoverflow上找了一篇讲这两个选项的文章,文章内容很长,读到最后对Linux中的这两个选项还是有些迷茫,所以重新写一篇文章来做一个总结: 本文只总结TCP单播部分,并且只讨论 ...
- 关于linux 原始套接字编程
关于linux 网络编程最权威的书是<<unix网络编程>>,但是看这本书时有些内容你可能理解的不是很深刻,或者说只知其然而不知其所以然,那么如果你想搞懂的话那么我建议你可以看 ...
- Linux原始套接字实现分析---转
http://blog.chinaunix.net/uid-27074062-id-3388166.html 本文从IPV4协议栈原始套接字的分类入手,详细介绍了链路层和网络层原始套接字的特点及其内核 ...
- Linux进程间通信—套接字
六.套接字(socket) socket也是一种进程间的通信机制,不过它与其他通信方式主要的区别是:它可以实现不同主机间的进程通信.一个套接口可以看做是进程间通信的端点(endpoint),每个套接口 ...
随机推荐
- 李洪强总结KVC用法
- 设置VMware随系统开机自动启动并引导虚拟机操作系统
设置VMware随系统开机自动启动并引导虚拟机操作系统 转载 2012年03月15日 19:50:53 标签: vmware / 虚拟机 / windows / parameters / tools ...
- mongdb复制集搭建
可参考官网教程 复制集增加了数据的冗余同时也提高了mongodb的可靠性,相比传统的主从架构,mongodb具有自动容灾的特性,即主库挂掉后会自动从剩下的从库中选举出一个节点做为主库(不需要人工干预) ...
- Linux环境下连接Mssql 2008
首先,Linux环境装个驱动:Microsoft® SQL Server® ODBC Driver 1.0 for Linuxhttps://www.microsoft.com/en-us/downl ...
- (转)SQL执行顺序
SQL语句理解:http://blog.jobbole.com/55086/ 窗口函数/分析函数:http://blog.csdn.net/mfkpie/article/details/1636451 ...
- poj 2386:Lake Counting(简单DFS深搜)
Lake Counting Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 18201 Accepted: 9192 De ...
- 【SVM】清晰明了的理论文章
http://www.cnblogs.com/jerrylead/archive/2011/03/13/1982639.html 松弛变量和惩罚因子: http://blog.csdn.net/yan ...
- Chem 3D中怎么创建立体模型
ChemDraw作为一款很受大家欢迎的化学绘图软件,其在绘制平面化学方面的功能已经非常的强大了,其实它也可以绘制3D图形.Chem 3D就是绘制3D图形的重要组件.而且为了满足不同的用户绘图的需求,可 ...
- Python入门(六):标准库
操作系统接口 os模块提供了不少与操作系统相关联的函数. import os os.getcwd() # 返回当前的工作目录 os.chdir('d:/') # 修改当前的工作目录 os.system ...
- python入门(一):基础语法
1.修改字符编码# -*- coding: cp-1252 -*-2.标识符以字母或下划线开头,大小写敏感3.以缩进表示代码块,同一个代码块缩进必须一致4.多行代码用反斜杠表示,() [] {}则不需 ...