Linux网络编程——socket编程

一、socket

socket 网络套接字

一个文件文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现(接收缓冲区和发送缓冲区))

通讯过程中,套接字一定是 【成对】 出现的。

二、网络字节序


小端法(PC本地存储):	高位存高地址。低位存低地址。     
大端法(网络存储): 		高位存低地址。低位存高地址。


涉及的四个函数:

man htonl

		1.NAME
		       htonl, htons, ntohl, ntohs - convert values between host and network byte order
		
		2.SYNOPSIS
		       #include 
		
		       uint32_t htonl(uint32_t hostlong);	//本地->网络(IP) converts the unsigned integer hostlong from host byte order to network byte order.
													//这里的IP指的不是192.168.1.1这样的字符串形式
													
		       uint16_t htons(uint16_t hostshort); 	//本地->网络(Port) converts the unsigned short integer hostshort from host byte order to network byte order.
		
		       uint32_t ntohl(uint32_t netlong);	//网络->本地(IP) converts the unsigned integer netlong from network byte order to host byte order.
		
		       uint16_t ntohs(uint16_t netshort);	//网络->本地(Port) converts the unsigned short integer netshort from network byte order to host byte order.

三、IP地址转换函数

主机字节序(小端)和网络字节序(大端)相互转换时,需要用到此节提到的转换函数。
1.inet_pton()//本地字节序(string IP)-> 网络字节序
			//客户端connect()函数会用到
		man inet_pton
		
		1)NAME
		       inet_pton - convert IPv4 and IPv6 addresses from text to binary form
				
		2)SYNOPSIS
		       #include 
		
		       int inet_pton(int af, const char *src, void *dst);
		3)PARAMETER
				af:指定IP协议
							AF_INET  IPv4
							AF_INET6 IPv6
				src:传入IP地址(点分十进制,192.168.1.1)
				dst:值结果参数,传出转换后的网络字节序 IP 地址。
		4)RETURN VALUE
		       inet_pton() returns  
		       1 on success (network address was successfully converted).  
		       0 is returned if src does not contain a character string repre‐senting a valid network address in the specified address family.  If af does not contain a valid address family, 
		       -1 is returned and errno is  set to EAFNOSUPPORT.					
					

2.inet_ntop() //网络字节序-> 本地字节序(string IP)
			 //服务端accept()函数会用到
		1)NAME
		       inet_ntop - convert IPv4 and IPv6 addresses from binary to text form
		
		2)SYNOPSIS
		       #include 
		
		       const char *inet_ntop(int af, const void *src,
		                             char *dst, socklen_t size);
		3)PARAMETER
				af:指定IP协议
							AF_INET  IPv4
							AF_INET6 IPv6
				src: 传入网络字节序 IP 地址
				dst:值结果参数,传出转换后的IP地址(点分十进制,192.168.1.1)。
		4)RETURN VALUE
		       On success, inet_ntop() returns a non-null pointer to dst.  
		       NULL is returned if there was an error, with errno set to indicate the error.

四、sockaddr 和 sockaddr_in 地址结构体

  1. sockaddr 和 sockaddr_in 区别

    大小相同,都是16字节。
    区别在于:sockaddr 诞生日期早,底层封装应用的多。
    		 sockaddr_in 后来诞生,更精细化,现在更常用。
    
    故现在使用的都是 sockaddr_in,但在bind(),accept()时都需要将sockaddr_in强转为sockaddr才行 。
    

Linux网络编程——socket编程_第1张图片

  1. sockaddr_in结构体详解

    man 7 ip  //查看 sockaddr_in 信息
    
    			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. 实际应用

    man 7 ip  //查看 sockaddr_in 信息
    
    
    
    
    实际应用步骤如下:
    	1.定义并赋值 sockaddr_in 结构体
    	2.将 socketaddr_in 强转为 sockaddr 指针类型。
    
    示例:
    	struct sockaddr_in addr;
    	addr.sin_family = AF_INET; //AF_INET6
    	addr.sin_port = htons(8080);
    	/* 方式 1,这种不常用。
    	inet_pton(AF_INET,"10.219.10.193",(void*)&addr_s.sin_addr);
    	或者
    	int dst;
    	inet_pton(AF_INET,"192.168.1.1",(void*)dst);
    	addr.sin_addr.s_addr = dst;
    	*/
    	
    	//方式2 ,这是常用方式。
    	addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 取系统中有效的任意IP地址,二进制类型。
    	bind(fd,(struct sockaddr*)addr,size);
    	
    

五、服务端和客户端通讯

	一般通讯由 3 个描述符组成。
	一对用于客户端和服务器通讯,一个用于监听。
	
	listen()函数的作用是设置监听上线(同一时刻接收到连接的个数),而不是设置监听。
	accept()函数才是阻塞监听客户端连接。
	 		accept()接收到客户端connect()连接后会生成一个新的socket用于通讯,而accept()调用的 fd 会返回继续进行监听。
	
	客户端在connect()前没有显示bind()绑定客户端地址,那采用的就是“隐式绑定”。

Linux网络编程——socket编程_第2张图片
Linux网络编程——socket编程_第3张图片

六、TCP网络编程涉及函数

  1. socket

    man 2 socket
    
    1.NAME
           socket - create an endpoint for communication
    
    2.SYNOPSIS
           #include           /* See NOTES */
           #include 
    
           int socket(int domain, int type, int protocol);
    3.PARAMETER
    		domain:		常用的有3个:
    					 		AF_UNIX //Local communication   
    					 		AF_INET //IPv4 Internet protocols
    					 		AF_INET6 //IPv6 Internet protocols                    		 
    		type: 常用的有2个:
    					SOCK_STREAM //流 TCP
    					SOCK_DGRAM //报文 UDP
    		protocol:默认 0 即可。 //所选用协议的代表协议是什么,也就是type对应的协议。0就是默认的指代。默认:SOCK_STREAM -> TCP ;SOCK_DGRAM -> UDP.  
    4.RTETURN VALUE
    		On success, a file descriptor for the new socket is returned.  
    		On error, -1 is returned, and errno is set appropriately.
    
  2. bind

    man 2 bind
    
    1.NAME
           bind - bind a name to a socket
    
    2.SYNOPSIS
           #include           /* See NOTES */
           #include 
    
           int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    3.PARAMETER
    		sockfd: socket函数返回值
    				
    				struct sockaddr_in addr;
    				addr.sinfamily = AF_INET;
    				addr.sin_port = htons(8888);
    				addr.sin_addr.s_addr = htonl(INADDR_ANY); //系统自行选择IP
    				inet_pton(AF_INET,"10.219.10.193",(void*)&addr_s.sin_addr); //指定IP
    		addr:(struct sockaddr *)&addr	
    		addrlen:sizeof(addr)
    4.RETURN VALUE
           On success, zero is returned.  
           On error, -1 is returned, and errno is set appropriately.
    
  3. listen

    //设置同时与服务器建立连接的上线数 (同时进行三次握手的客户端数量)
    
    man 2 listen
    
    1.NAME
           listen - listen for connections on a socket
    
    2.SYNOPSIS
           #include           /* See NOTES */
           #include 
    
           int listen(int sockfd, int backlog);
    3.PARAMETER
    		sockfd: socket 函数返回值
    		backlog:上线数。最大值为 128
    4.RETURN VALUE
           On success, zero is returned.  
           On error, -1 is returned, and errno is set appropriately.		
    
  4. accept

    //阻塞等待客户端连接
    
    man 2 accept
    
    1.NAME
           accept, accept4 - accept a connection on a socket
    
    2.SYNOPSIS
           #include           /* See NOTES */
           #include 
    
           int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    3.PARAMETER
    		sockfd  :socket函数返回值(也必须是listen过的fd)
    		addr	:值结果参数。传出成功与服务器建立连接的客户端地址结构 
    		addrlen	:传入传出参数。
    				 传入:传入时addr的大小。一般为:sizeof(addr)
    				 传出:客户端addr的实际大小。
    4.RETURN VALUE
           On success, these system calls return a nonnegative integer that is a file descriptor for the accepted socket.  
           On error, -1 is  returned,  errno is set appropriately, and addrlen is left unchanged.
    
  5. connect

    //使用创建的socket与服务器连接
    
    man 2 connect
    
    1.NAME
           connect - initiate a connection on a socket
    
    2.SYNOPSIS
           #include           /* See NOTES */
           #include 
    
           int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    3.PARAMETER
    		sockfd	:socket函数返回值
    		
    				struct sockaddr_in addr; 
    				addr.sin_family = AF_INET;
    				addr.sin_port = htons(atoi(argv[2])); 
    				inet_pton(AF_INET,argv[1],&addr.sin_addr.s_addr);
    		addr	:传入参数。服务器地址结构。(也就是要连接的服务器的地址结构) 用inet_pton()给地址赋值。
    		addrlen	:服务器地址结构大小。
    4.RETURN VALUE
           If the connection or binding succeeds, zero is returned.  
           On error, -1 is returned, and errno is set appropriately.
    
    注: 如果connect前未使用bind显示绑定客户端地址,采用的就是“隐式绑定”。
    
  6. 实现简单的服务器和客户端DEMO

    DEMO流程如下图所示:Linux网络编程——socket编程_第4张图片

DEMO:
	https://github.com/Panor520/LinuxCode/tree/master/socket/tcp/simpledemo

七、UDP网络编程涉及函数及DEMO

  1. socket
    同 tcp中的socket

  2. recvfrom

    man 2 recvfrom
    
    1.NAME
           recv, recvfrom, recvmsg - receive a message from a socket
    
    2.SYNOPSIS
           #include 
           #include 
    
           ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
           ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
    
    3.PARAMETER
    		sockfd	:通信的套接字
    		buf		:缓冲区地址 //char buf[1024];
    		len		:缓冲区大小	//sizeof(buf)
    		flags	: 默认0.
    		src_addr:(struct addr*)&addr_c 传出,对端地址结构	//不需要对端地址信息可置 0
    		addrlen	:传入传出参数。 socklen_t len_addr_c;    //不需要对端地址信息可置 0
    4.RETURN VALUE
    		成功	:成功接收数据字节数。
    		失败	:-1 errno
    		0		:对端关闭。
    
  3. sendto

man 2 sendto

1.NAME
       send, sendto, sendmsg - send a message on a socket

2.SYNOPSIS
       #include 
       #include 

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
       
3.PARAMETER
		sockfd	:通信的套接字
		buf		:缓冲区地址 //char buf[1024];
		len		:缓冲区大小	//sizeof(buf)
		flags	:默认0.
		dest_addr:(struct addr*)&addr 传入,对端地址结构
		addrlen	:sizeof(addr).
		
4.RETURN VALUE
		成功	:成功写如数据字节数
		失败	:-1. errno
  1. UDP通信DEMO
    server连接地址
    client连接地址

八、优化——自定义封装通讯函数

Linux网络编程——socket编程_第5张图片
在这里插入图片描述

八、read函数返回值(必须掌握)

read函数返回值:
		>0	:实际读到的字节数
		=0	:已经读到结尾(对端已经关闭)【重!点!,必须掌握】
		-1	:进一步判断errno的值
				errno = EAGAIN or EWOULDBLOCK : 设置了非阻塞方式 读。没有数据到达
				errno = EINTR 慢速系统调用被 中断。
				errno = “其他情况” 异常。

九、多进程并发服务器

连接地址

十、多线程并发服务器

连接地址

十一、服务端端口和地址复用(设置套接字选项避免2MSL时长报错问题)

先关闭服务端,主动关闭端就会经历 TIMEWAIT 状态(2MSL时长),立即再次启动服务端时会出现bind server error的错误,
可以用下面方法避免。 

详见 unix网络编程 第7章

man setsockopt

先前关闭的服务端还在经历 TIMEWAIT状态,只是 端口和地址可以复用。

用法:
	在服务端的socket()bind()调用之间插入如下代码:
	
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

十二、close和shutdown区别

当使用dup2()指定多个整数指向同一个文件描述符时:
	close() 关闭只会关闭指定的那个。【单个关闭】
	shutdown()关闭时,会将所有指向该文件描述符的连接全部关闭。【全关闭】

man 2 shutdown
1.NAME
       shutdown - shut down part of a full-duplex connection

2.SYNOPSIS
       #include 

       int shutdown(int sockfd, int how);
3.PARAMETERS
		how:
			SHUT_RD 关读端
			SHUT_WR 关写端
			SHUT_RDWR 两端都关闭
4.RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

十三、多路I/O转接 服务器(select、poll)

  1. 基础

    传统的 多线程和多进程服务器 就是是【阻塞】的。
    而 多路I/O转接(select、poll、epoll) 是【非阻塞】的。
    
    多路I/O转接 是由内核提供的 select、poll、epoll 监听机制。
    监听connect 事件及 read、write事件。只有当 client 发生相应事件时,多路转接实现的服务器才会响应。 平时是非阻塞的(不会一直等待)。
    
    select的实现如下图所示:
    

Linux网络编程——socket编程_第6张图片

  1. select

    man 2 select
    
    1.NAME
           select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
    
    2.SYNOPSIS
           /* According to POSIX.1-2001, POSIX.1-2008 */
           #include 
    
           /* According to earlier standards */
           #include 
           #include 
           #include 
    
           int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    
           void FD_CLR(int fd, fd_set *set);  	//将一个文件描述符从集合中清除
           int  FD_ISSET(int fd, fd_set *set);	//判断一个文件描述符是否在集合中
           void FD_SET(int fd, fd_set *set);	//将一个文件描述符添加到描述符集合中
           void FD_ZERO(fd_set *set);			//清空文件描述符集合
    
    3.PARAMETER
    	nfds	:监听的所有文件描述符中,最大文件描述符+1//例:要监听 4~50的文件描述符(47), nfds就应该填 48
    	readfds	:读 文件描述符监听集合。		传入 传出 参数。 
    			  传入:要设置读监听的文件描述符集合
    			  传出:发生了读事件的文件描述符集合
    			  //例:传入集合中有4、5、6文件描述符。而4文件描述符发生了读事件,故传出的集合中只存在文件描述符4。
    	writefds:写 文件描述符监听集合。		传入 传出参数
    			  传入:要设置写监听的文件描述符集合
    			  传出:发生了写时间的文件描述符集合
    			  //例:传入集合中有4、5、6文件描述符。而5文件描述符发生了写事件,故传出的集合中只存在文件描述符5。
    	exceptfds:异常 文件描述符集合。		传入  传出  参数。
    			  传入:要设置异常监听的文件描述符集合
    			  传出:发生了异常事件 的文件描述符集合
    			  //例:传入集合中有4、5、6文件描述符。而6文件描述符发生了异常事件,故传出的集合中只存在文件描述符6。
    	timeout:	>0	:设置监听时长
    				NULL:阻塞监听(也就是始终等待事件发生)
    				0	:非阻塞监听,轮询
    	
    4.fd_set
    		是一个位图。 0 1表示。
    	
    5.RETURN VALUE
    		>0	:所有监听集合(3个)中,所有传出集合的文件描述符的总数。
    			  //例:以上面参数的例子为例,三个集合共传出了4、5、6故RETURN VALUE=3.
    		=0	:没有满足监听条件的文件描述符
    		=-1	:ERROR. errno.	
    

    select服务器DEMO思路分析:
    Linux网络编程——socket编程_第7张图片
    基础select demo实现链接
    升级版select demo实现链接

    select
    缺点 监听上限受文件描述符限制,最大 1024。检测满足条件的fd,需自己增加业务逻辑提高效率,也是变向增加了编码难度
    优点 跨平台。可在 windowns、linux、macOS、Unix、类Unix、mips 上运行
  2. poll

    poll 就是升级版select (增加数组记录要遍历的文件描述符)
    
    man poll 
    
    1.NAME
           poll, ppoll - wait for some event on a file descriptor
    
    2.SYNOPSIS
           #include 
    
           int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    3.PARAMETER
    		fds	:监听的文件描述符【数组】
    					 struct pollfd {
    		               int   fd;         /* file descriptor */ //待监听的文件描述符
    		               short events;     /* requested events */ //待监听的文件描述符对应的监听事件
    		               short revents;    /* returned events */ //传入0。如果满足events给定的事件,会返回events对应的传入值。
    		           };
    		           events 或 revents 取值: POLLIN	//读事件
    		           							POLLOUT	//写事件
    		           							POLLERR	//错误事件
         	nfds:监听数组的 实际有效监听个数。
         	timeout: >0	:超时时长。 单位:毫秒。
         			 -1 :阻塞等待。
         			 0	:不阻塞。
    4.RETURN VALUE
    		满足对应监听事件的文件描述符 【总个数】。
    

    poll demo实现链接

    poll
    缺点 不能跨平台。只能在Linux或类Unix上运行。 不能直接定位到满足事件的文件描述符,也是变向增加了编码难度
    优点 自带数组结构。可以将 监听事件集合 和 返回事件集合 分离。 可拓展 监听上线(方法同epoll)(超出1024限制)

十四、epoll

  1. 基础

    epoll的本质是一个【红黑树】。监听结点为根节点。
    
    	epoll的使用由三个函数组成。
    	//epoll 应该使用非阻塞的ET模式写服务器程序(这是规则)
    	
    	man epoll_create
    	man epoll_ctl
    	man epoll_wait
    	
    	1.epoll_create() //创建一棵监听红黑树
    			1)NAME
    			       epoll_create, epoll_create1 - open an epoll file descriptor
    			
    			2)SYNOPSIS
    			       #include 
    			
    			       int epoll_create(int size);
    			3)PARAMETER
    					size:创建红黑树的监听结点数量 (仅供内核初始化使用,当实际使用超出该大小时,内核会自动扩容)
    			4)RETURN VALUE
    					On success, these system calls return a nonnegative file descriptor.  
    					On error, -1 is returned, and errno is set to indicate the error.
    	
    	2.epoll_ctl() //操作监听红黑树
    			1)NAME
    			       epoll_ctl - control interface for an epoll file descriptor
    			
    			2)SYNOPSIS
    			       #include 
    			
    			       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    			3)PARAMETER
    					1.epfd	:epoll_create()函数的返回值
    					2.op		:对该监听红黑树所做的操作。有三种值:
    									EPOLL_CTL_ADD //添加 fd 到监听红黑树
    									EPOLL_CTL_MOD //修改 fd 在监听红黑树上的监听事件
    									EPOLL_CTL_DEL //将一个 fd 从监听红黑树上摘下(取消监听)
    					3.fd		:待 op 操作的fd
    					4.event	:本质为 struct epoll_event 结构体 指针。
    									struct epoll_event {
    						               	uint32_t     events;      /* Epoll events */
    						               	epoll_data_t data;        /* User data variable */
    						           	};
    									成员 events 常用值:
    														EPOLLIN
    														EPOLLOUT
    														EPOLLERR
    									成员 data 原型如下:
    							           	typedef union epoll_data {
    								               void        *ptr;
    								               int          fd; //对应监听事件的fd
    								               uint32_t     u32;//不用
    								               uint64_t     u64;//不用
    								           } epoll_data_t;
    			4)RETURN VALUE
    					When successful, epoll_ctl() returns zero.  
    					When an error occurs, epoll_ctl() returns -1 and errno is set appropriately.
    	
    	3.epoll_wait()	//阻塞监听红黑树
    			1)NAME
    			       epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor
    			
    			2)SYNOPSIS
    			       #include 
    			
    			       int epoll_wait(int epfd, struct epoll_event *events,
    			                      int maxevents, int timeout);					
    			3)PARAMETER
    					1.epfd	:epoll_create()函数的返回值
    					2.events	:传出参数(数组),满足定义的监听事件的所有结点的结构体数组
    					3.maxevents:events 数组元素的总个数。 默认为1024struct epoll_event events[1024];
    					4.timeout	: -1	:阻塞
    							   0	:不阻塞
    							   -1	:失败 errno.					
    			4)RETURN VALUE
    			       >0	:满足监听事件的struct epoll_event结构体的总个数。可用作循环处理的上线。
    			       0	:没有满足监听事件的struct epoll_event结构体
    			       -1	:失败 errno
    
    epoll
    缺点
    优点

    基础epoll demo实现链接

  2. epoll 事件模型

    	ET 模式: 	   (服务器常用方式)		
    			即边沿触发。 
    				缓冲区剩余未读尽的数据不会导致 epoll_wait 返回。新的事件满足才会触发。
    				设置方式:struct epoll_event event;
    						 event.events = EPOLLIN | EPOLLET;
    				注意: ET模式只支持非阻塞。需给连接的文件描述符设置读写非阻塞(利用fcntl函数),如下:
    						epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
    						int flg = fcntl(cfd, F_GETFL);
    						flg |= O_NONBLOCK; //位或操作
    						fcntl(cfd, F_SETFL, flg);
    											
    
    	LT 模式:
    			即水平触发。 ---epoll默认采用方式
    					缓冲区剩余未读尽的数据会导致 epoll_wait 返回。 
    
    
    例:通信时,readn 读数据时,每次只读 100字节,但缓冲区接收了 250 字节。
    	① 默认的 LT模式下 会直接 再次触发 epoll_wait()函数,表明该连接fd又有数据写过来,并再读出100字节,
    		之后再次触发 epoll_wait()函数,表明又有数据写过来,接收50字节后会阻塞在readn的地方,直到写满100字节。
    		一旦 阻塞在readn的地方,代码就会有问题,因为应该阻塞在epoll_wait()的地方。
    	② 在 ET模式下,剩下的150字节会被读取忽略,不会触发 epoll_wait()函数,而下次在写入数据时,会再从之前的150字节里读前100字节,后面的新写入的数据累加进来,等待下次在读取100字节。
    

    【总结:应使用非阻塞的 ET模式写epoll服务器。】

  3. epoll 反应堆

    就是epoll基础demo的升级版,更完整些。
    也是libevent 框架采用的方式。
    
    epoll反应堆:
    			ET模式 + 非阻塞、轮询 + void *ptr. 
    
    			简单的 epoll demo:
    				socket、bind、listen	--	epoll_create 创建监听红黑树	-- 返回 epfd	--  epoll_ctl() 向树上添加一个监听fd	--
    				--	while(1)	--	epoll_wait 监听	--	对应监听fd有事件产生 -- 返回监听满足数组。 --	判断返回数组元素	--
    				--	lfd满足 --  Accept	--	cfd满足	-- read -- 小--大 -		【diff content】	- write回去。
    		
    			反应堆:
    				反应堆的优化就是在检测到对端有数据传输过来后,服务端写回时要检测,对端是否可写,确认可写再将数据写过去。
    				
    				
    				socket、bind、listen	--	epoll_create 创建监听红黑树	-- 返回 epfd	--  epoll_ctl() 向树上添加一个监听fd	--
    				--	while(1)	--	epoll_wait 监听	--	对应监听fd有事件产生 -- 返回监听满足数组。 --	判断返回数组元素	--
    				--	lfd满足 --  Accept	--	cfd满足	-- read -- 小--大 --   【diff begin】
    				
    				--	cfd从监听红黑树上摘下	--	EPOLLOUT	--	回调函数	--epoll_ctl()	-- EPOLL_CTL_ADD重新放到红黑树上监听 --
    				--	等待	epoll_wait() 返回	-- 说明 cfd 可写 -- write写回去
    				--	cfd 从监听红黑树上摘下	--	EPOLLIN | EPOLLET	--	epoll_ctl()	--	EPOLL_CTL_ADD	重新放到红黑上监听读事件	--	epoll_wait()监听		【diff end】
    

    反应堆epoll demo实现链接

你可能感兴趣的:(C,Linux,socket,网络,linux)