socket编程之select(),poll(),epoll()
socket编程,通信
client端 socket() ----->connect() ------->recv() -----> close();
server端 socket() ----->bind() ------> listen() ---->accept() ------>send() ------->close();
1> socket(int family,int type,int protocol);
family ->协议类型
AF_INET --------------> IPV4
AF_INET6 ---------------> IPV6
AF_ROUTE ---------------> 路由套接口
type->是指套接字类型
SOCK_STREAM --------------------> 字节流套接字 TCP
SOCK_DGRAM --------------------> 数据报套接字 UDP
SOCK_RAW --------------------> 原始套接字
函数调用成功返回小的非负整数,文件描述符类型,否则返回-1表示调用失败
2.connect(int sockfd,struct sockaddr*addr,socklen_t addrlen)
sockfd 是从socket的返回值;
addr是指向服务器的套接字的地址结构的指针
addrlen是该套接字的地址结构的大小
struct sockaddr_in server;
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htonl();
server.sin_addr.s_addr = inte_addr("127.0.0.1");
if(connect(sockfd,(struct sockaddr *)&server,sizeof(server)) == -)
{
//强制性地址转换,转为通用套接字结构
}
3>bind(int sockfd,struct sockaddr *server,socklen_len addrlen);
sockfd->socket的返回值
server->表示指向于特定协议的地址结构的指针
addrlen->表示该套接字的地址结构的长度
struct sockaddr_in server;
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htonl();
server.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd,(struct sockaddr *)&server,sizeof(server)) == -)
{
///sockaddr 强制转换
}
4>listen(int sockfd,int backlog)
sockfd --->socket的返回值
backlog --->规定了请求返回队列的最大连接数,它对队列中等待服务请求的数目进行限制,如果一个服务请求到来时,输入队列已满,该套接字将拒绝连接请求。
listen(sockfd,5);
对于一个监听套接字,内核要维护两个队列:未完成连接队列和已完成连接队列
未完成连接队列:为每个请求建立连接的SYN分节设一个条目,服务器正等待完成三次握手,当前的套接字处在SYN_RECD状态
已完成连接队列:为每个完成TCP三次握手的客户端开设一个条目,当前的套接字状态时ESTABLISHED。
5>accept(int sockfd,struct sockaddr *client,socklen_t *addrlen)
sockfd 是socket()返回的套接字描述符,在调用listen的时候此套接字变成了监听套接字
client 是返回对方的套接字和地址结构
addrlen 是对应结构的长度
accetp()函数返回,已连接套接字描述符。
注:
一个服务器只能有一个监听套接字,而且会一直存在,直到服务器关闭,而已连接套接字描述符是内核为每个被接受的客户都创建一个,当服务器完成与客户的数据传输时,要关闭连接套接字,所以监听套接字接受客户的链接请求,已连接套接字描述符负责对应客户进行数据传送。
int listenfd,connfd;
struct sockaddr_in client;
socklen_t addrlen;
addrlen = sizeof(client);
connfd = accept(listenfd,(struct sockaddr *)&client,&addrlen);
if(connfd == -)
{ }
原型如下:
各个参数含义如下:
- int maxfdp:最大描述符值 + 1
- fd_set *readfds:对可读感兴趣的描述符集
- fd_set *writefds:对可写感兴趣的描述符集
- fd_set *errorfds:对出错感兴趣的描述符集
- struct timeval *timeout:超时时间(注意:对于linux系统,此参数没有const限制,每次select调用完毕timeout的值都被修改为剩余时间,而unix系统则不会改变timeout值)
select函数会在发生以下情况时返回:
- readfds集合中有描述符可读
- writefds集合中有描述符可写
- errorfds集合中有描述符遇到错误条件
- 指定的超时时间timeout到了
当select返回时,描述符集合将被修改以指示哪些个描述符正处于可读、可写或有错误状态。可以用FD_ISSET宏对描述符进行测试以找到状态变化的描述符。如果select因为超时而返回的话,所有的描述符集合都将被清空。
select函数返回状态发生变化的描述符总数。返回0意味着超时。失败则返回-1并设置errno。可能出现的错误有:EBADF(无效描述符)、EINTR(因终端而返回)、EINVAL(nfds或timeout取值错误)。
设置描述符集合通常用如下几个宏定义:
FD_ZERO(fd_set *fdset); /* 将所有位设为0 */
FD_SET(int fd, fd_set *fdset); /* 将fd位设为1 */
FD_CLR(int fd, fd_set *fdset); /* 将fd位设为0 */
int FD_ISSET(int fd, fd_set *fdset); /* 检测fd位是否为1 */
如:
fd_set set;
FD_ZERO(&rset); /* i初始化rset */
FD_SET(, &rset); /* 将fd = 1 的描述符设为1 */
FD_SET(, &rset); /* 将fd = 4 的描述符设为1 */
FD_SET(, &rset); /* 将fd = 5 的描述符设为1 */
当select返回的时候,rset位都将被置0,除了那些有变化的fd位。
当发生如下情况时认为是可读的:
- socket的receive buffer中的字节数大于socket的receive buffer的low-water mark属性值。(low-water mark值类似于分水岭,当receive buffer中的字节数小于low-water mark值的时候,认为socket还不可读,只有当receive buffer中的字节数达到一定量的时候才认为socket可读)
- 连接半关闭(读关闭,即收到对端发来的FIN包)
- 发生变化的描述符是被动套接字,而连接的三路握手完成的数量大于0,即有新的TCP连接建立
- 描述符发生错误,如果调用read系统调用读套接字的话会返回-1。
当发生如下情况时认为是可写的:
- socket的send buffer中的字节数大于socket的send buffer的low-water mark属性值以及socket已经连接或者不需要连接(如UDP)。
- 写半连接关闭,调用write函数将产生SIGPIPE
- 描述符发生错误,如果调用write系统调用写套接字的话会返回-1。
注意:
select默认能处理的描述符数量是有上限的,为FD_SETSIZE的大小。
对于timeout参数,如果置为NULL,则表示wait forever;若timeout->tv_sec = timeout->tv_usec = 0,则表示do not wait at all;否则指定等待时间。
如果使用select处理多个套接字,那么需要使用一个数组(也可以是其他结构)来记录各个描述符的状态。而使用poll则不需要,下面看poll函数。
(二)poll()函数
原型如下:
各参数含义如下:
- struct pollfd *fdarray:一个结构体,用来保存各个描述符的相关状态。
- unsigned long nfds:fdarray数组的大小,即里面包含有效成员的数量。
- int timeout:设定的超时时间。(以毫秒为单位)
poll函数返回值及含义如下:
- -1:有错误产生
- 0:超时时间到,而且没有描述符有状态变化
- >0:有状态变化的描述符个数
着重讲fdarray数组,因为这是它和select()函数主要的不同的地方:
pollfd的结构如下:
struct pollfd {
int fd; /* 测试描述符*/
short events; /* 测试条件*/
short revents; /* 测试结果 */
};
其实poll()和select()函数要处理的问题是相同的,只不过是不同组织在几乎相同时刻同时推出的,因此才同时保留了下来。select()函数把可读描述符、可写描述符、错误描述符分在了三个集合里,这三个集合都是用bit位来标记一个描述符,一旦有若干个描述符状态发生变化,那么它将被置位,而其他没有发生变化的描述符的bit位将被clear,也就是说select()的readset、writeset、errorset是一个value-result类型,通过它们传值,而也通过它们返回结果。这样的一个坏处是每次重新select 的时候对集合必须重新赋值。而poll()函数则与select()采用的方式不同,它通过一个结构数组保存各个描述符的状态,每个结构体第一项fd代表描述符,第二项代表要监听的事件,也就是感兴趣的事件,而第三项代表poll()返回时描述符的返回状态。合法状态如下:
- POLLIN: 有普通数据或者优先数据可读
- POLLRDNORM: 有普通数据可读
- POLLRDBAND: 有优先数据可读
- POLLPRI: 有紧急数据可读
- POLLOUT: 有普通数据可写
- POLLWRNORM: 有普通数据可写
- POLLWRBAND: 有紧急数据可写
- POLLERR: 有错误发生
- POLLHUP: 有描述符挂起事件发生
- POLLNVAL: 描述符非法
对于POLLIN | POLLPRI等价与select()的可读事件;POLLOUT | POLLWRBAND等价与select()的可写事件;POLLIN 等价与POLLRDNORM | POLLRDBAND,而POLLOUT等价于POLLWRBAND。如果你对一个描述符的可读事件和可写事件以及错误等事件均感兴趣那么你应该都进行相应的设置。
对于timeout的设置如下:
INFTIME:wait forever
0 :不等待,轮训
>0 :等待特定的时间。
(三)epoll() 函数
epoll()函数是在LINUX2.6中被提到的。
epoll()函数有三个接口
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll_create()函数中size告诉内核监听数目有多少。
epoll_ctl()表第一个函数是epoll_create()的返回值,op表示要进行的操作,fd表示需要监听的fd,event告诉内核需要监听什么事
op有三种宏定义:
EPOLL_CTL_ADD://注册新的fd到epfd中;
EPOLL_CTL_MOD://修改已经注册的fd的监听事件;
EPOLL_CTL_DEL://从epfd中删除一个fd;
struct epoll_event *event函数的结构体特征是
struct epoll_event
{
__uint32_t events;/* epoll事件 */
epoll_data_t data;/*用户数据变量 */
};
其中epoll_event结构体中events事件:
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epfd:是epoll_create的返回值
events:是从内核得到的事件集合
maxevents:告诉内核events的最大数目
timeout:表示时间(0表示不等待,-1不确定,有的说法是阻塞)
epoll的工作模式:
epoll对文件描述符的操作有两种操作:LT(水平触发)&ET(边缘触发)
水平触发:当epoll_wait检测到描述符事件发生并将事件通知应用程序,应用程序可以不立刻处理事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
边缘触发:当epoll_wait检测到描述符事件发生并将时间通知应用程序,应用程序必须立刻处理事件。如果不处理下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
边缘触发事件在很大程度上减少了epoll事件被重复触发的次数,因此效率被水平触发高。epoll在边缘触发的时候,必须使用非阻塞接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把多个文件描述符的任务饿死。
socket编程之select(),poll(),epoll()的更多相关文章
- socket编程之 select、poll、kqueue、epoll
原生API select int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct tim ...
- 详述socket编程之select()和poll()函数
转自:http://www.cppblog.com/myjfm/archive/2011/10/26/159093.aspx select()函数和poll()函数均是主要用来处理多路I/O复用的情况 ...
- socket编程之select()
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); 参数 ...
- socket编程之select相关
FD_ZERO,FD_ISSET这些都是套节字结合操作宏 看看MSDN上的select函数, 这是在select io 模型中的核心,用来管理套节字IO的,避免出现无辜锁定. int se ...
- Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)
一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...
- 多进程、协程、事件驱动及select poll epoll
目录 -多线程使用场景 -多进程 --简单的一个多进程例子 --进程间数据的交互实现方法 ---通过Queues和Pipe可以实现进程间数据的传递,但是不能实现数据的共享 ---Queues ---P ...
- Python自动化 【第十篇】:Python进阶-多进程/协程/事件驱动与Select\Poll\Epoll异步IO
本节内容: 多进程 协程 事件驱动与Select\Poll\Epoll异步IO 1. 多进程 启动多个进程 进程中启进程 父进程与子进程 进程间通信 不同进程间内存是不共享的,要想实现两个进程间 ...
- select,poll,epoll的归纳总结区分
Select.Poll与Epoll比较 以下资料都是来自网上搜集整理.引用源详见文章末尾. 1 Select.Poll与Epoll简介 Select select本质上是通过设置或者检查存放fd标志位 ...
- 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】
下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...
随机推荐
- [Leetcode] jump game ii 跳跃游戏
Given an array of non-negative integers, you are initially positioned at the first index of the arra ...
- java里的 懒汉和恶汉模式-----讲解
------------java中的恶汉模式 public void Test{ private static Test inte = new Test(); // 内部自己创建好实例,私有属性(不建 ...
- sass的循环for,while,each
1. for @for $i from 1 to 10 { .border-#{$i} { border: #{$i}px solid blue; } } 2. while $i: 6; @while ...
- NOIP2015Day2T2子串(字符串dp)
又被“if(a=b)”坑了QAQ...写C++还是得开Warning,这么久了pascal还没改过来咋回事啊QWQ 题目大意就不说了OWO 网上的题解都不怎么看得懂啊...好像写得都很乱?还是我太sb ...
- php ul li 分类
<?phpfunction do_tree($arr,$pid){ echo "<ul>"; foreach ($arr as $key => $value ...
- 用camke编译python程序
project(test) cmake_minimum_required(VERSION 3.0) find_package(OpenCV REQUIRED) find_package (Python ...
- python代码格式规范
目前的规范基于pep-0008 基本格式 缩进 使用4个空格进行缩进 行宽 每行代码尽量不超过80个字符 理由: 这在查看side-by-side的diff时很有帮助 方便在控制台下查看代码 太长可能 ...
- C# 枚举的初始化
3.2 枚举类型(Enum types)的默认值 对于枚举类型(Enum types),.NET会自动将字面值0(literal 0)隐式地转换为对应的枚举类型. 3.2.1 有一个0值成员 如果枚举 ...
- pyttsx3 winsound win32api.MessageBox使用案例
import requests,time from lxml import etree import win32api,win32con import winsound import pyttsx3 ...
- 【usaco-Liars and Truth Tellers, 2013 Jan真假奶牛】并查集
题解: 原先我看错题了,以为是任意选择k个使得它们不矛盾. 这样的话怎么做呢?我想M^2判断,把它们分成若干个集合,集合里面两两不矛盾这个集合里所有的话就不矛盾了. 但是这样是错的.为什么呢? 每一句 ...