提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
网络编程
提示:以下是本篇文章正文内容,下面案例可供参考
一、Berkeley Socket
- TCP/IP协议族标准只规定了网络各个层次的设计和规范,具体实现则需要由各个操作系统厂商完成最出名的网络库由BSD 4.2版本最先推出,所以称作伯克利套接字,这些API随后被移植到各大操作系统中,并成为了网络编程的事实标准。
-
socket 即套接字是指网络中一种用来建立连接、网络通信的设备,用户创建了 socket 之后,可以通过其发起或者接受 TCP 连接、可以向 TCP 的发送和接收缓冲区当中读写TCP数据段,或者发送 UDP 文本。
二、地址信息设置
1.struct sockaddr 和 struct sockaddr_in
- 我们主要以IPv4为例介绍网络的地址结构。
- 主要涉及的结构体有 struct in_addr 、 structsockaddr 、 struct sockaddr_in 。
-
struct sockaddr 是一种通用的地址结构,它可以描述一个IPv4或者IPv6的结构,所有涉及到地址的接口都使用了该类型的参数,但是缺点是,过于通用的结果是直接用它来描述一个具体的IP地址和端口号十分困难。
- 所以一般先使用 struct sockaddr_in 来构造地址,再将其进行强制类型转换成 struct sockaddr 以作为网络接口的参数。
//man 7 ip
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
2.大小端转换
- TCP/IP协议规定,当数据在网络中传输的时候,一律使用网络字节序即大端法。
- 接收方和发送方都是主机字节序即小端法
- 对于应用层协议的载荷部分,如果不需要第三方工具检测内容,可以不进行大小端转换。
- 但是对于其他层次的头部部分,在发送之前就一定要进行小端到大端的转换了(因为网络通信以及 tcpdump 、 netstat 等命令都是以大端法来解析内容的)。
- 下面是整数大小端转换相关的函数
#include <arpa/inet.h>
//h --> host n --> net l --> 32bit s --> 16bit
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
- 下面是32位网络字节序IP地址和点分十进制的IP地址互相转换的函数:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
//线程安全版本是inet_atop inet_ptoa
3.域名和IP地址的对应关系
1. 查hosts文件
2. 通过DNS协议
3. nslook www.baidu.com
4. gethostbyname
- IP层通过IP地址的结构进行路由,选择最终找到一条通往目的地的路由,但是一些著名的网站如果采用IP地址的方式提供地址,用户将无法记忆,所以更多的时候需要一个方便人类记忆的域名(比如www.kernel.org)作为其实际IP地址(145.40.73.55)的别名,显然我们需要一种机制去建立域名和IP地址的映射关系.
- 一种方法是修改本机的hosts文件 /etc/hosts ,但是更加通用的方案是利用DNS协议,去访问一个DNS服务器,服务器当中存储了域名和IP地址的映射关系。
- 与这个操作相关的函数是gethostbyname ,下面是其用法:
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
-
char * 不是字符串,是一片ip地址的内存首地址
-
使用hostent需要包含#include<netdb.h>和#include<sys/socket.h>等头文件.
-
结构体hostent有五个元素,即h_name,它是一个字符指针,指向主机名;
h_aliases是一个二重指针,指向主机的别名,一个主机可以有多个别名,
h_aliases可以用下标的方式访问不同的主机别名;
h_addrtype,为int型数据,指定主机使用的IP地址类型;
length指定IP地址长度;
h_addr_list也是一个二重指针,与h_aliases一样,也可以通过下标进行访问。
-
这个结构体指定主机的不同IP地址,在实际的应用中,一台服务器往往有好几个IP地址,而域名只有一个,这样设计的好处是,可以使系统分布设计,提升服务器的稳定性和抗灾难能力。
-
一般对服务器的访问,则是先经过DNS服务器,DNS通过均衡设计,返回合适的IP与客户端进行交互,避免客户端只连接一个IP,导致网络拥堵。
三、TCP通信
1. 鸟瞰TCP通信
下面是使用TCP通信的流程图:
2. socket
-
socket 函数用于创建一个 socket 设备。
- 调用该函数时需要指定通信的协议域、套接字类型和协议类型。
- 一般根据选择TCP或者UDP有着固定的写法。
- socket 函数的返回值是一个非负整数,就是指向内核 socket 设备的文件描述符。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//domain AF_INET --> IPv4 AF_INET6 --> IPv6
//type SOCK_STREAM --> TCP SOCK_DGRAM --> UDP
//protocol IPPROTO_TCP --> TCP IPPROTO_UDP -->UDP
- 每个连接的一端在内核中都对应了一个输入缓冲区(SO_RCVBUF)和一个输出缓冲区(SO_SNDBUF),其默认大小由 /proc/sys/net/core/rmem_default 和 /proc/sys/net/core/wmem_default 文件指定。
3.connect
- 客户端使用 connect 来建立和TCP服务端的连接。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
客户端在调用 connect 可以不使用 bind 来指定本地的端口信息,这客户端就会随机选择一个临时端口号来作为源端口。
-
调用 connect 预期是完成TCP建立连接的三次握手。
-
如果服务端未开启对应的端口号或者未监听,则只能收到一个RST回复,并且报错返回的内容是"Connection refused"。
-
完成第一次和第三次
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
//把淀粉十进制的ip地址转换成32位大端存储的地址
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
//端口用大端存储
serAddr.sin_port = htons(atoi(argv[2]));
int ret = connect(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"connect");
return 0;
}
- inet_addr函数将包含 IPv4 dotted-decimal 地址的字符串转换为IN_ADDR结构的正确地址。
- 使用 tcpdump 命令可以查看包的状态
#tcpdump -i any port 2778
4. netstat命令
-an 列出所有网络
-ntlp 列出所有的监听
4.bind
-
bind 函数用于给套接字赋予一个本地协议地址(即IP地址加端口号)。
- 为socket绑定ip和port
- bind 在选择端口号时应当避开知名端口号的范围(<1024)
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- 使用 bind 函数时要注意其地址是大端法描述的,并且可能需要执行强制类型转换。
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
//创建监听套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);//如果赋值为INADDR_ANY,表示选择本机
IP地址
serAddr.sin_port = htons(atoi(argv[2]));
int ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
return 0;
}
5.listen
- 使TCP服务端开启监听。服务端在开启了 listen 之后,就可以开始接受客户端连接了。
-
只有服务端调用。
- 在socket文件对象中,销毁了发送和接收缓冲区
- 新建了半连接队列(收到第一次握手)和全连接队列(收到第三次握手)
int listen(int sockfd, int backlog);
-
一旦启用了 listen 之后,操作系统就知道该套接字是服务端的套接字,操作系统内核就不再启用其发送和接收缓冲区,转而在内核区维护两个队列结构:半连接队列和全连接队列。
-
半连接队列用于管理成功第一次握手的连接,全连接队列用于管理已经完成三次握手的队列。
-
backlog 在有些操作系统用来指明半连接队列和全连接队列的长度之和,一般填一个正数即可。如果队列已经满了,那么服务端受到任何再发起的连接都会直接丢弃(大部分操作系统中服务端不会回复RST,以方便客户端自动重传)
-
使用 netstat -an 命令可以查看主机上某个端口的监听情况。
6.accept
- 是一种读行为。
-
accept 函数由服务端调用,用于从全连接队列中取出下一个已经完成的TCP连接。如果全连接队列为空,那么 accept 会陷入阻塞。一旦全连接队列中到来新的连接,此时 accept 操作就会就绪,这种就绪是读操作就绪,所以可以使用 select 函数的读集合进行监听。
- 当 accept 执行完了之后,内核会创建一个新的套接字文件对象,该文件对象关联的文件描述符是 accept 的返回值,文件对象当中最重要的结构是一个发送缓冲区和接收缓冲区,可以用于服务端通过TCP连接发送和接收TCP段。
- 区分两个套接字是非常重要的。通过把旧的管理连接队列的套接字称作监听套接字,而新的用于发和接收TCP段的套接字称作已连接套接字。
- 通常来说,监听套接字会一直存在,负责建立各个不同的TCP连接(只要源IP、源端口、目的IP、目的端口四元组任意一个字段有区别,就是一个新的TCP连接),而某一条单独的TCP连接则是由其对应的已连接套接字进行数据通信的。
- 客户端使用 close 关闭套接字或者服务端使用 close 关闭已连接套接字的时候就是主动发起断开连接四次挥手的过程。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 需要特别注意的是, addrlen 参数是一个传入传出参数,所以使用的时候需要主调函数提前分配好内存空间。
ret = listen(sfd,10);
ERROR_CHECK(ret,-1,"listen");
//newFd代表的是跟客户端的TCP连接
socklen_t len;
len = sizeof(struct sockaddr)
int newFd = accept(sfd,NULL, &len);
ERROR_CHECK(newFd,-1,"accept");
printf("newFd = %d\n", newFd);
7.send和recv
-
send的本质是write,recv的本质是read。
-
数据的实机发送过程,由内核缓冲区处理。
-
当接收缓冲区为空时,recv会阻塞
-
多次send,可能一次性recv出来(消息没有边界)。
-
recv 不一定能把数据全部取完。
-
send 把数据拷贝到发送缓冲区
-
recv把数据从接收缓冲区拷贝出来
-
send 和 recv 用于将数据在用户态空间和内核态的缓冲区之间进行传输,无论是客户端还是服务端均可使用,但是只能用于TCP连接。
-
将数据拷贝到内核态并不意味着会马上传输,而是会根据时机再由内核协议栈按照协议的规范进行分节,通常缓冲区如果数据过多会分节成MSS的大小,然后根据窗口条件传输到网络层之中。
//将buf的数据拷贝到发送缓冲区
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//将接收缓冲区的数据拷贝打buf
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- 使用 read 和 write 可以实现同样的效果,相当于 flags 参数为0。
下面是一个完整的客户端和服务端通信的例子:
//clinet.c
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret = connect(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"connect");
char buf[64]={0};
ret = recv(sfd,buf,sizeof(buf),0);
printf("buf=%s\n",buf);
send(sfd,"helloserver",11,0);
close(sfd);
return 0;
}
//server.c
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
//创建监听套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
ret = listen(sfd,10);
ERROR_CHECK(ret,-1,"listen");
//newFd代表的是跟客户端的TCP连接
socklen_t len;
len = sizeof(struct sockaddr);
int newFd = accept(sfd,NULL,&len);
ERROR_CHECK(newFd,-1,"accept");
ret = send(newFd,"helloclient",11,0);
ERROR_CHECK(ret,-1,"send");
char buf[64]={0};
ret = recv(newFd,buf,sizeof(buf),0);
printf("buf=%s\n",buf);
close(newFd);
close(sfd);
return 0;
}
- 需要特别注意的是, send 和 recv 的次数和网络上传输的TCP段的数量没有关系,多次的 send 和 recv可能只需要一次TCP段的传输。
- 另外一方面,TCP是一种流式的通信协议,消息是以字节流的方式在信道中传输,这就意味着一个重要的事情,消息和消息之间是没有边界的。
- 在不加额外约定的情况下,通信双方并不知道发送和接收到底有没有接收完一个消息,有可能多个消息会在一次传输中被发送和接收(江湖俗称"粘包"),也有有可能一个消息需要多个传输才能被完整的发送和接收(江湖俗称"半包")。
8. 服务端 netfd 和sockfd
- sockfd读就绪 accept不会阻塞
- netfd读就绪 recv不会阻塞
6. 使用select实现TCP即时聊天
- 基于TCP的聊天程序的实现思路和之前利用管道实现即时聊天的思路是一致的。
- 客户端和服务端都需要使用 select 这种IO多路复用机制监听读事件,客户端需要监听套接字的读缓冲区以及标准输入,服务端需要监听已连接套接字的读缓冲区以及标准输入。
7. TIME_WAIT和setsockopt
- 如果是服务端主动调用 close 断开的连接,即服务端是四次挥手的主动关闭方,由之前的TCP状态转换图可知,主动关闭方在最后会处于一个固定2MSL时长的TIME_WAIT等待时间。
- 在此状态期间,如果尝试使用 bind 系统调用对重复的地址进行绑定操作,那么会报错。
netstat -an|grep 1234
- 让bind无视time_wait 状态
- 在实际工作当中,TIME_WAIT状态的存在虽然有可能会提高连接的可靠性,但是一个服务端当中假如存在大量的TIME_WAIT状态,那么服务端的工作能力会极大地受到限制,而取消TIME_WAIT状态其实对可靠性的影响比较小,所以用户可以选择使用 setsockopt 函数修改监听套接字的属性,使其可以在TIME_WAIT状态下依然可以 bind 重复的地址(需要在 bind 之前执行)。
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
//...
int reuse = 1;
ret = setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
ERROR_CHECK(ret,-1,"setsockopt");
//...后续再执行bind等操作
8. select监听socket支持断开重连
-
服务端除了接收缓冲区和标准输入以外,还有一个操作也会造成阻塞,那就是 accept 操作。
-
accept是读行为。
-
服务端sockfd可以被select监听
-
有连接的情况下,sockfd就绪,accept不会阻塞
-
实际上服务端可以使用 select 管理监听套接字,检查其全连接队列是否存在已经建好的连接,如果存在连接,那么其读事件即 accept 操作便就绪。将监听套接字加入监听会导致服务端的代码发生一个结构变化:
- 每次重新调用 select 之前需要提前准备好要监听的文件描述符,这些文件描述符当中可能会包括新的已连接套接字的文件描述符。
- select 的第一个参数应当足够大,从而避免无法监听到新的已连接套接字的文件描述符(它们的数值可能会比较大)。
- 需要处理 accept 就绪的情况。
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
ret = listen(sfd,10);
ERROR_CHECK(ret,-1,"listen");
int newFd=0;
char buf[64]={0};
fd_set rdset;
//记录的是需要监听的文件描述符的集合
fd_set needMonitorSet;
FD_ZERO(&rdset);
FD_ZERO(&needMonitorSet);
FD_SET(STDIN_FILENO,&needMonitorSet);
FD_SET(sfd,&needMonitorSet);
while(1)
{
memcpy(&rdset,&needMonitorSet,sizeof(fd_set));
ret = select(10,&rdset,NULL,NULL,NULL);
ERROR_CHECK(ret,-1,"readyNum");
//select的rdset是一个传入传出参数,select成功返回的时候
//rdset里保存的是就绪的描述符
printf("select return\n");
if(FD_ISSET(STDIN_FILENO,&rdset))
{
memset(buf,0,sizeof(buf));
read(STDIN_FILENO,buf,sizeof(buf));
send(newFd,buf,strlen(buf)-1,0);
}
else if(FD_ISSET(newFd,&rdset))
{
memset(buf,0,sizeof(buf));
ret = recv(newFd,buf,sizeof(buf),0);
//对端断开的时候,newFd一直可读
//recv读数据的返回值是0
if(0 == ret)
{
printf("byebye\n");
close(newFd);
FD_CLR(newFd,&needMonitorSet);
continue;
}
printf("buf=%s\n",buf);
}
else if(FD_ISSET(sfd,&rdset))
{
//sfd可读,表示客户端连接我们
//newFd代表的是跟客户端的TCP连接
printf("accpet\n");
struct sockaddr_in cliAddr
socklen_t sockLen = sizeof(cliAddr);
printf("sockLen=%d\n",sockLen);
/* socklen_t sockLen = 0; */
newFd = accept(sfd,(struct sockaddr*)&cliAddr,&sockLen);
ERROR_CHECK(newFd,-1,"accept");
FD_SET(newFd,&needMonitorSet);
printf("sockLen=%d\n",sockLen);
printf("newFd=%d\n",newFd);
printf("addr=%s,port=%d\n",inet_ntoa(cliAddr.sin_addr),ntohs(cliAddr.sin_port))
;
}
}
close(newFd);
close(sfd);
return 0;
}
二、UDP编程
1. 鸟瞰UDP
1. sendto和recvfrom
- 这两个函数的行为类似于 send 和 recv ,不过会有一些额外的参数,用来指定或者保存对端的套接字地址结构以及对应的大小。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- 在使用UDP进行的通信的时候,要特别注意的是这是一个无连接的协议。
- 一方面调用 socket 函数的时候需要设置 SOCK_DGRAM 选项
- 因为没有建立连接的过程,所以必须总是由客户端先调用 sendto 发送消息给服务端,这样服务端才能知道对端的地址信息,从进入后续的通信。
下面是使用UDP通信的一个例子:
//client.c
int main(int argc,char* argv[]){
ARGS_CHECK(argc,3);
int sfd = socket(AF_INET,SOCK_DGRAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
char buf[64]={0};
socklen_t len = sizeof(serAddr);
sendto(sfd,"hello udp",9,0,(struct sockaddr*)&serAddr,len);
sendto(sfd,"hello server",12,0,(struct sockaddr*)&serAddr,len);
recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&serAddr,&len);
printf("buf=%s\n",buf);
close(sfd);
return 0;
}
//server.c
int main(int argc,char* argv[]){
ARGS_CHECK(argc,3);
int sfd = socket(AF_INET,SOCK_DGRAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
char buf[4]={0};
struct sockaddr_in cliAddr;
memset(&cliAddr,0,sizeof(cliAddr));
socklen_t len = sizeof(cliAddr);
recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
printf("buf=%s\n",buf);
recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
printf("buf=%s\n",buf);
sendto(sfd,"hello client",12,0,(struct sockaddr*)&cliAddr,len);
close(sfd);
return 0;
}
- recvfrom获取所有收到的消息的第一个消息,
- 取出该消息发送者的ip和端口
在服务端recvfrom 完成之后,服务端可以执行
- 消息有边界无连接,没有断开连接事件
四、epoll
select 的缺陷:
① 存在大量的用户态-》内核态的拷贝
② fdset底层是位图(1024)文件限制
③ 监听和就绪耦合
④ 就绪判断不合理 select();
导致海量连接 少量就绪
1. epoll IO多路复用机制
-
优势:
① 除了水平触发,还支持边缘触发。
② 监听事件集合容量很大,有多少内存就能放下多少文件描述符。
③ 监听事件集合常驻内核态,调用 epoll_wait 函数不会修改监听性质,不需要每次将集合从用户态
拷贝到内核态。
④ 监听事件和就绪事件的状态分为两个数据结构存储,当 epoll_wait 就绪之后,用户可以直接遍历就绪事件队列,而不需要在所有事件当中进行轮询。
-
使用流程:
① 创建epoll文件对象
② 设置监听集合
③ 等待,直到任意监听的事件就绪
④ 遍历就绪集合。
2. epoll IO多路复用函数
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);
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
使用 epoll 主要有这样几个函数:
-
epoll_create 用于在内核之中创建一个 epoll 文件对象,这个文件对象中就包含之前所描述的监听事件集合和就绪设备集合。
监听集合:红黑树
就绪集合:线性表
- epoll_create 的参数目前已经没有意义,填写一个大于0的数值即可。 epoll_create 的返回值是该文件对象对应的文件描述符。
-
epoll_ctl 用于调整监听事件集合。 op 的选项是 EPOLL_CTL_ADD 、 EPOLL_CTL_MOD 和EPOLL_CTL_DEL ,分别表示添加、修改和删除事件, event->events 用于描述事件的类型,其中EPOLLIN 表示读, EPOLLOUT 表示写。可以通过命令 man 7 socket 查看每个操作对应的事件类型如何。
-
epoll_wait 用于使线程陷入阻塞,直到监听的设备就绪或者超时。
events 是一个传入传出参数,用于存储就绪设备队列, epoll_wait 的返回值就是就绪设备队列的长度,即就绪设备的个数。
timeout 描述超时时间,单位是毫秒,-1是永久等待。
maxevent 传入一个足够大的正数即可。
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。