参考链接:IO多路复用之epoll总结
参考链接:套接字的阻塞与非阻塞
Linux环境:C编程之网络通信进阶操作
套接字选项
套接字选项详细规定了套接字的属性,影响socket的各项操作。我们可以通过setsockopt
函 数和getsockopt
函数愉快的设定和获取socket的属性。
首先来看一下setsockopt函数:
setsockopt函数
- 作用:设定socket的属性,注意不是修改,setsockopt操作应该在bind之前
- 原型:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
- 参数:
- sockfd:要设置的socket的文件描述符
- level:指定了套接字选项所适用的协议,比如IPPROTO_TCP、IPPROTO_IP 和 IPPROTO_IPV6分别代表了tcp、ip和ipv6协议,通常情况下为SOL_SOCKET,表示作用于套接字API层。
- optname:需设置的socket选项
- optval:指向缓冲区的指针,用来指定要设置的值,根据选项不同指向整数或者结构体
- opilen:表明缓冲区的长度、
- 返回值:成功返回0,失败返回-1
getsockopt函数
- 作用:获取socket的属性
- 原型:
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
- 参数:
- sockfd:要获取选项的socket的文件描述符
- level:指定了套接字选项所适用的协议,比如IPPROTO_TCP、IPPROTO_IP 和 IPPROTO_IPV6分别代表了tcp、ip和ipv6协议,通常情况下为SOL_SOCKET,表示作用于套接字API层。
- optname:需获取的socket选项
- optval:传出参数,指向缓冲区的指针,用来存储要获取的选项,根据选项不同指向整数或者结构体
- opilen:传出参数,返回缓冲区的长度、
- 返回值:成功返回0,失败返回-1
套接字API层常用的socket属性
名称 | 选项 | 数值类型 |
---|---|---|
SO_BROADCAST | 允许发送广播数据 | int |
SO_DEBUG | 允许调试 | int |
SO_DONTROUTE | 不查找路由 | int |
SO_ERROR | 获得套接字错误 | int |
SO_KEEPALIVE | 保持连接 | int |
SO_LINGER | 延迟关闭连接 | struct linger |
SO_OOBINLINE | 带外数据放入正常数据流 | int |
SO_RCVBUF | 接收缓冲区大小 | int |
SO_SNDBUF | 发送缓冲区大小 | int |
SO_RCVLOWAT | 接收缓冲区下限 | int |
SO_SNDLOWAT | 发送缓冲区下限 | int |
SO_RCVTIMEO | 接收超时 | struct timeval |
SO_SNDTIMEO | 发送超时 | struct timeval |
SO_REUSEADDR | 允许重用本地地址和端口 | int |
SO_TYPE | 获得套接字类型 | int |
SO_BSDCOMPAT | 与 BSD 系统兼容 | int |
epoll 多路复用模型
epoll多路复用模型是linux中特有的,相比之前学过的select,参见linux环境:C编程文件操作,epoll更加灵活,性能也更出色,支持水平触发和边缘触发两种触发模式。
接口函数
epoll模型由三个函数组成:
int epoll_create(int size);
//创建一个epoll实例
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);
//返回I/O就绪的套接字
函数原理:
- epoll_creat函数创建epoll实例,返回对应的文件描述符。size参数指定文件的初始大小,即要监控的套接字的个数,没有太大意义,因为epoll实例可以根据需要修改添加监控对象。
- epoll_ctl事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
- 第一个参数是epoll_create()的返回值,
- 第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd; - 第三个参数是需要监听的fd
- 第四个参数是告诉内核需要监听什么事,
struct epoll_event
结构如下
events可以是以下几个宏的集合://联合体,事件触发时返回的信息,一般填对应的文件描述符,来表示该事件由谁触发 typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; //触发事件 epoll_data_t data; };
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里 - epoll_wait函数返回就绪的套接字列表,第一个参数是epoll实例,第二个参数是返回的结构体数组,每个元素对应一个套接字的信息,第三个列表是该结构体数组的容量, 值不能大于epoll实例中就绪列表的最大长度,第四个参数是监视计时器(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
触发模式
LT模式:工作逻辑和select一致,只要缓冲区中存在没有读写的数据就一直触发。
ET模式:仅当缓冲区状态发生变化的时候才获得通知。这里的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,否则未读写的数据就会丢失。
套接字的阻塞和非阻塞模式
套接字的默认状态是阻塞状态,即在进行bind,read,write,send,recv,connect,accept等操作时,如果没有输入,则会阻塞进程。
设置非阻塞套接字
可以通过设置文件描述符的标志位来把套接字设置为非阻塞模式。
用到以下函数:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
fcntl函数可以获取或者设置文件的标志位,fd是文件描述符,cmd是命令,如果有第三个参数则是命令的参数。
通过以下代码可以设置非阻塞套接字
//获取当前文件的文件描述标志位
int status=fcntl(fd,F_GETFL);
//将该标志位或上非阻塞标志
status=status|O_NONBLOCK;
//重新设置文件的标志位
fcntl(fd,F_SETFL,status);
阻塞和非阻塞的区别
套接字调用可分为四种
-
输入操作,包括read,readv,recv,recvfrom,recvmsg。
阻塞:
-
TCP:如果接收缓冲区没有数据读,则阻塞,直到数据到达。
-
UDP:如果接收缓冲区没有数据读,则阻塞,直到UDP数据报到达。
非阻塞:
- 如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数据可读,对于UDP套接字即有一个完整的数据报可读),相应调用将立即返回EWOULDBLOCK。
-
-
输出操作,包括write,writev,send,sendto,sendmsg
阻塞:- TCP:如果发送缓冲区没有空间,则阻塞。有一些空间时,则返回不足计数
非阻塞:
- TCP:如果发送缓冲区没有空间,会立即返回一个EWOULDBLOCK错误。如
果有一些空间,返回值将是内核能够复制到该缓冲区中的字节数。 - UDP:没有发送缓冲区,不会因与TCP套接字一样的原因而阻塞,不过有可能会因其他原因而阻塞。
-
接收外来连接,即用于accept函数
- 阻塞:阻塞在accept,直到有新的连接
- 非阻塞:没有新的连接来时,accept调用将立即返回一个EWOULDBLOCK
-
发起外出连接,即用于TCP的connect函数。
-
阻塞:connect函数一直要等到客户收到对于自己的SYN的ACK为止才返回。所以TCP的每一个connect总会阻塞其调用的进程至少一个到服务器的RTT时间。
-
非阻塞:调用connect,并连接不能立即建立,那么连接的建立能照样发起,不过会返回一个EINPROGRESS错误。这个错误不同于上述三个情形,但客户端和服务端在同一主机,这些连接会立即建立。所以也要预备connect成功返回的情况。
-