API
socket
int socket(int family, int type, int protocol);
// socket(AF_INET, SOCK_STREAM, 0); returnTCP套接字
// socket(AF_INET, SOCK_DGRAM, 0); return UCP套接字
bind
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
// IP地址可以设置为通配地址0(宏定义是INADDR_ANY),端口也可以设置为0,这样,ip和端口则由内核选择。如果用户指明了ip和端口,则使用这个IP地址和端口,否则由内核选择。
// 当服务器绑定了通配地址,套接字会接收到达它绑定端口的任何TCP连接。
listen
int listen(int sockfd, int backlog);
// backlog可能是未完成队列和已完成队列总和的最大值
accept
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
// 参数cliaddr和addrlen用来返冋已连接的对端进程(客户〕的协议地址,其中addrlen是协议地址的长度,内核根据此长度向cliaddr指向的地址中存入相应的数据
recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// 参数buf指向一个缓冲区,该缓冲区用来存放接收到的数据,,参数len是指明buf的长度,第四个参数一般设置为0
// 接收网络数据的是内核的协议栈,recv只是负责把数据从协议栈拷贝到用户空间。
// 函数返回实际copy的字节数,如果出现错误,会返回-1,如果连接被关闭,会返回0.
send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 参数buf指明一个存放应用程序要发送数据的缓冲区,len指明实际要发送的数据的字节树,参数flags一般是0;
// 函数返回实际copy的字节数,如果出现错误,会返回-1,如果连接被关闭,会返回0
connect
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
// sockfd 是系统调用 socket() 返回的套接字文件描述符; serv_addr 是保存着目的地端口和 IP 地址的数据结构 struct sockaddr; addrlen 设置 为 sizeof(struct sockaddr)。
socket特性
socket分为阻塞模式和非阻塞模式,它们是指socket在进行输入输出操作时,是否会等待数据的到来或发送。默认情况下,socket都是阻塞模式,即如果没有数据可读或写,socket会一直等待,知道有数据或超时。非阻塞模式的socket不会等待,二十立即返回一个错误码,表示当前无法进行输入输出操作。
socket设置为非阻塞模式的方法:
// 创建socket的时候
int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
// 使用fcntl函数
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
// 使用ioctl函数:
ioctl(sockfd, FIONBIO, 1); //1:非阻塞 0:阻塞
阻塞模式下api的特点:
当内核发送缓冲区没有足够的空间时,继续使用send,阻塞模式下,会导致线程阻塞,直到内核中有足够的空间位置;非阻塞模式下,会直接返回错误码(-1);
当内核接收缓冲区中没有数据时,继续使用recv,阻塞模式下,会导致线程阻塞,直到内核中有数据;非阻塞模式下,会直接返回错误码(-1);
调用connect,会发起三次连接,阻塞模式下,会等到完成连接再返回,非阻塞模式下,会直接返回完成。
accept在非阻塞模式下,如果有连接,返回正确的fd,否则返回错误码.
Epoll
Epoll API
int epoll_create(int size);
size参数表示所要监视文件描述符的最大值,不过在后来的Linux版本中已经被弃用,参数大于0即可,一般为1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// struct epoll_event结构:
// struct epoll_event
// {
// uint32_t events; /* Epoll events */
// epoll_data_t data; /* User data variable */
// }op参数说明:
op参数说明操作类型:
- EPOLL_CTL_ADD:向interest list添加一个需要监视的描述符
- EPOLL_CTL_DEL:从interest list中删除一个描述符
- EPOLL_CTL_MOD:修改interest list中一个描述符
struct epoll_event结构描述一个文件描述符的epoll行为。在使用epoll_wait函数返回处于ready状态的描述符列表时:
-
data域是唯一能给出描述符信息的字段,所以在调用epoll_ctl加入一个需要监测的描述符时,一定要在此域写入描述符相关信息
-
events域是bit mask,描述一组epoll事件,在epoll_ctl调用中解释为:描述符所期望的epoll事件,可多选。
常用的epoll事件是什么:
- EPOLLIN:描述符处于可读状态
- EPOLLOUT:描述符处于可写状态
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
阻塞等待注册的事件发生,返回事件的数目,并将触发的事件写入events数组中。
events: 用来记录被触发的events,其大小应该和maxevents一致
maxevents: 返回的events的最大个数
参数timeout描述在函数调用中阻塞时间上限。在文件描述符没有进入ready状态时,-1表示调用将一直阻塞;0表示不管结果怎么样,调用都立即返回;大于0表示调用最多持续timeout时间,超时后,立即返回。
两种触发方式
epoll两种触发方式分别是:边缘触发(ET)和水平触发(LT)。epoll的默认的工作模式是LT模式。
水平触发:
- 对于读操作,只要缓冲内容不为空,LT模式返回读就绪。
- 对于写操作,只要缓冲区还不满,LT模式会返回写就绪。
边缘触发:
只有当socket的缓冲区状态变化时才触发事件。
- 当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知,
- 当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知
所以边缘触发需要一次性的把缓冲区的数据读完为止。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
设置边缘触发的方法:
ev.events = EPOLLOUT | EPOLLET;
示例代码:
https://github.com/q962875152/mycode/blob/master/epolltcp.c
本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接,详细查看详细的服务:链接