Linux网络编程

文章目录

  • 参考资料在前
  • 1. 前置知识
  • 2. 进程概述
    • 2.1 fork()函数
    • 2.2 守护进程
  • 3. 浅谈printf()函数与write()函数
    • 3.1 printf()函数缓存问题
    • 3.2 write()函数思考
  • 4. 网络编程剖析
    • 4.1 listen()监听套接字
    • 4.2 阻塞/非阻塞IO
    • 4.3 同步/异步IO
    • 4.4 TCP/IP设计
      • 4.4.1 三次握手
      • 4.4.2 四次挥手
  • 5. epoll技术
    • 5.1 epoll_create()
    • 5.2 epoll_ctl()
    • 5.3 epoll_wait()
    • 5.4 epoll工作模式

参考资料在前

Linux C++网络编程
关于 TCP 三次握手和四次挥手,满分回答在此
面试官:说说TCP第三次握手丢失会发生什么?
TCP协议规定2MSL等待的原因
一篇文章让你真正搞懂epoll机制

1. 前置知识

  1. 查看linux系统的处理器核心数grep -c processor /proc/cpuinfo
  2. 为了服务器更加高效,避免频繁的进程切换,我们将worker进程数设置为处理器核心数
  3. 每开启一个终端,都会对应一个bash进程
    • 一个终端对应一个bash进程,终端上开启的进程为bash通过fork开启子进程
    • 每个进程还属于一个进程组,每个进程组有一个唯一的进程组编号pgrp
    • session会话:包含一个或多个进程组,session编号SID
    • 终端关闭,系统会给session的主线程发送SIGHUP信号,主线程会把信号发送个session中的所有进程,进程收到信号默认执行退出操作
    zyq@zyq-ThinkStation-P348:/usr/local/nginx$ ps -ef | grep bash
    zyq         2673    2630  0 16:50 pts/0    00:00:00 bash
    zyq         3101    2630  0 16:51 pts/2    00:00:00 bash
    zyq        25978    3101  0 21:16 pts/2    00:00:00 grep --color=auto bash
    
  4. 终端关闭进程不退出的方法
  • nginx进程拦截SIGHUP信号signal(SIGHUP, SIG_IGN),收到该信号不退出(nginx的父进程变为bash的父进程)
  • nginx进程与bash进程不放在同一个session里, setsid();自己成为新session的组长,为session的组长则无效
  • setsid ./nginx启动进程会启用新的session
  • nohup ./nginx启动进程会忽略SIGHUP信号,该命令会默认把输出重定向到当前目录的nohup.out。
  • 后台执行 ./nginx &,仍然可以执行其他命令,但是信息会输出到当前终端,fg切换到前台运行
  1. strace工具跟踪进程
  • 可以跟踪程序执行时进程的系统调用以及所收到的信号
  • 跟踪nginx进程,sudo strace -e trace=signal -p 20268
    zyq@zyq-ThinkStation-P348:~$ ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx'PID    PPID     SID TT          PGRP COMMAND
    2673    2630    2673 pts/0       2673 bash
    23838   12633   23838 pts/4      23838 bash
    31380   23838   23838 pts/4      31380 nginxzyq@zyq-ThinkStation-P348:~/av/cpp2024/epoll_server$ sudo strace -e trace=signal -p 31380
    strace: Process 31380 attached
    --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=23838, si_uid=1000} ---
    +++ killed by SIGHUP +++zyq@zyq-ThinkStation-P348:~/av/cpp2024/epoll_server$ sudo strace -e trace=signal -p 23838
    [sudo] password for zyq: 
    strace: Process 23838 attached
    --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=12633, si_uid=1000} ---
    rt_sigreturn({mask=[CHLD]})             = -1 EINTR (Interrupted system call)
    kill(-31380, SIGHUP)                    = 0 # 发送SIGHUP信号给31380所在的进程组(杀死了nginx进程)
    rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [CHLD], 8) = 0
    rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
    rt_sigprocmask(SIG_BLOCK, [CHLD], [CHLD], 8) = 0
    rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=31380, si_uid=1000, si_status=SIGHUP, si_utime=0, si_stime=0} ---
    rt_sigreturn({mask=[]})                 = 0
    rt_sigaction(SIGHUP, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7dff0f442520}, {sa_handler=0x631f91d08530, sa_mask=[HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], sa_flags=SA_RESTORER, sa_restorer=0x7dff0f442520}, 8) = 0
    kill(23838, SIGHUP)                     = 0 # 杀死自己
    --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=23838, si_uid=1000} ---
    +++ killed by SIGHUP +++
    
  1. 内存泄漏检查工具valgrind
  • 安装工具sudo apt install valgrind
  • 参数说明
    • 使用内存泄漏检查工具--tool=memcheck
    • 完全检查内存泄漏 --leak-check=full
    • 显示内存泄漏的位置--show-reachable=yes
    • 是否跟入子进程--trace-children=yes
    • 指定信息输出文件,默认是当前终端--log-file=log.txt
  • 使用示例,检查nginx程序执行过程中的内存泄露问题valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx

2. 进程概述

2.1 fork()函数

  1. 基本概念
  • 进程是程序执行的实例,多个进程可以共享同一个可执行程序
  • fork()函数创建子进程,子进程和父进程从fork()返回,开始执行与父进程相同的代码,根据返回值判断父(1)子(0)进程
  • kill -9 子进程,父进程收到了SIGCHLD信号,子进程变成了僵尸进程
  • fork()出来的子进程与父进程共享内存,直到出现任何进程执行写操作时,才会复制新的内存给子进程,因此fork()很快
  1. 僵尸进程的产生
  • 子进程结束,父进程没有调用wait/waitpid来进行额外的处理,子进程就会变成一个僵尸进程
  • 父进程可能还需要该子进程的一些信息,内核保留子进程的信息
  1. 如何处理僵尸进程
  • 杀死父进程
  • 一个进程被杀死时,会给父进程发送SIGCHLD信号,需要自定义SIGCHLD处理函数,调用waitpid回收子进程
include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>#include <sys/types.h>
#include <sys/wait.h>void do_sign(int sign) {int status;pid_t pid;printf("收到了SIGCHLD,当前进程ID: %d!\n", getpid());// -1 表示等待所有子进程// status 保存子进程的状态信息// WNOHANG 表示不要阻塞,让waitpid立即返回pid = waitpid(-1, &status, WNOHANG);// pid == 0 子进程没结束// pid == -1 waitpid调用错误return;
}int main(int argc, char *const *argv) {pid_t pid;int count = 0;printf("当前进程ID: %d!\n", getpid());// 处理子进程被杀死变成僵尸进程的问题if (signal(SIGCHLD, do_sign) == SIG_ERR) {printf("设置SIGCHLD信号自定义处理逻辑失败!\n");exit(1);}pid = fork();if (pid < 0) {printf("Fail to fork()!\n");exit(1);}// pid == 1 父进程// pid == 0 子进程// 父子进程共同执行的代码,不确定哪个进程先执行while (1) {sleep(2);printf("%d 休息了2秒,当前进程ID: %d!\n", count++, getpid());} printf("再见\n");return 0;

2.2 守护进程

  1. 基本概念
  • 在后台运行,不跟任何的控制终端关联,没有控制终端
  • 一般生存周期长,随着操作系统启动(PPID == 0)
  • cmd列带着[],为内核守护进程
  • init进程,系统守护进程
  • 一般拥有root权限
  1. 守护进程编写规则
  • fork()子进程,父进程退出
  • 子进程调用setsid()建立新会话,脱离终端和父进程(否则crtl + c/终端关闭时父进程退出前会关闭子进程)
  • umask(0) 标识不要限制或屏蔽文件权限
  • 将子进程的标准输入输出重定向到空设备/dev/null,不与终端挂钩,不接收键盘输入,也不输出到屏幕
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char *const *argv) {pid_t pid;int fd;// fork()子进程pid = fork();if (pid < 0) {printf("Fail to fork()!\n");exit(1);}if (pid > 0) {// 父进程退出exit(0);}// 子进程才会执行 pid == 0// 子进程调用setsid()建立新会话if (setsid() == -1) {return -1;}// `umask(0)` 标识不要限制或屏蔽文件权限umask(0);// 打开空设备fd = open("/dev/null", O_RDWR);if (fd == -1) {return -1;}// 标准输入 重定向到 空设备if (dup2(fd, STDIN_FILENO) == -1) {return -1;}// 标准输出 重定向到 空设备if (dup2(fd, STDOUT_FILENO) == -1) {return -1;}// fd正常是大于3if (fd > STDERR_FILENO) {if (close(fd) == -1) {return -1;}}while (1){sleep(2);}return 0;
}
  1. 文件描述符
  • 打开或创建一个新文件时,操作系统都会返回一个文件描述符,三个特殊的文件描述符号,数字分别为0,1,2

    • 0: 标准输入,对应的符号常量为STDIN_FILENO
    • 1: 标准输出,对应的符号常量为STDOUT_FILENO
    • 2: 标准错误,对应的符号常量为STDERR_FILENO
  • 输出重定向,在命令行中用>, ls -la > out.txt

  • 输入重定向,在命令行中用<, cat < in.txt

  • 联合使用,cat < in.txt > out.txt

  • 空设备 /dev/null是一个空设备,与黑洞类似,丢弃一切输入

  1. 守护进程信号
  • SIGHUP 守护进程不会收到来自内核的SIGHUP信号,只能是其他进程发给它的(nginx -s reload就算给master进程发送SIGHUP)
  • SIGINT(ctrl+c),SIGWINCH(终端大小改变) 守护进程不会收到来自内核的SIGINT,SIGWINCH信号
  1. 守护进程和后台进程的区别
  • 守护进程不与终端挂勾,不能在终端输出东西,而后台进程则相反
  • 守护进程不受终端关闭影响,而后台进程随着终端关闭而退出

3. 浅谈printf()函数与write()函数

3.1 printf()函数缓存问题

printf()末尾不加\n就无法及时的将信息显示到屏幕 ,这是因为存在行缓存(windows上一般没有,类Unix上才有),也就是输出的数据不直接显示到终端,而是首先缓存到某个地方,当遇到行刷新标识或者该缓存已满的情况下,才会把缓存的数据显示到终端设备;
ANSI C中定义\n为行刷新标记,所以,printf()函数没有带\n是不会自动刷新输出流,直至行缓存被填满才显示到屏幕上。所以用printf的时候,注意末尾要添加\n;当然也可以printf()之后调用fflush(stdout)强制刷出数据;或者是使用函数setvbuf(stdout,NULL,_IONBF,0);,这个函数. 直接将printf缓冲区禁止, printf就直接输出了。

3.2 write()函数思考

  1. 如何保证多个进程写日志文件不会出现错乱?
  • open((const char *)plogname,O_WRONLY|O_APPEND|O_CREAT,0644); 打开文件使用O_APPEND标记,能够保证多个进程操作同一个文件时不会相互覆盖;
  • 内核wirte()写入时是原子操作;
  • 父进程fork()子进程是亲缘关系。会共享文件表项,
  1. fwrite()与write()
  • C语言的标准库fwrite()相比与系统调用write()多了一层缓冲区,缓冲区满了之后才会调用系统调用write()
  • fwrite()是标准I/O库一般在stdio.h文件
  • write():系统调用;
  • 所有系统调用都是原子性的

4. 网络编程剖析

4.1 listen()监听套接字

在这里插入图片描述

  1. 创建套接字int socket(int domain, int type, int protocol);
    • domain:使用的地址协议族,如 AF_INET、AF_INET6分别表示IPv4、IPv6格式。
    • type:套接字类型,如 SOCK_STREAM(流式传输协议)表示TCP套接字,SOCK_DGRAM(报式传输协议)表示UDP套接字。
    • protocol:通常为0,表示自动选择协议。
  2. IP和端口号绑定int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    • sockfd:套接字的文件描述符,通过socket调用得到的返回值。
    • addr:包含要绑定的IP地址和端口号的结构体。
    • addrlen:addr 结构体的大小,sizeof(addr)。
  3. 开始监听int listen(int sockfd, int backlog);
    • sockfd:套接字的文件描述符,通过socket调用得到的返回值。
    • backlog:在队列中等待接受的最大连接数(已完成连接队列 + 未完成连接队列)
  4. 从已完成连接队列中获取连接int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    • sockfd:套接字的文件描述符。
    • addr:用于存储客户端地址信息的结构体。
    • addrlen:addr 结构体的大小。
  5. 客户端连接到服务端的监听套接字int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    • sockfd:套接字的文件描述符,通过socket调用得到的返回值。
    • addr:用于存储服务端地址信息的结构体,这个IP和端口也需要转换为大端然后再赋值。
    • addrlen:addr 结构体的大小。
    • 返回时机:客户端调用connect只要收到服务端发来的SYN报文就返回了,也就是第二次握手包时就返回了
  6. 发送数据size_t send(int sockfd, const void *buf, size_t len, int flags);
    • sockfd:套接字的文件描述符。
    • buf:包含要发送数据的缓冲区。
    • len:要发送的数据的长度。
    • flags:发送标志,通常为0。
  7. 接收数据size_t recv(int sockfd, void *buf, size_t len, int flags);
    • sockfd:套接字的文件描述符。
    • buf:包含要发送数据的缓冲区。
    • len:要发送的数据的长度。
    • flags:发送标志,通常为0。
  8. 关闭套接字int close(int sockfd);

TCP会为每个监听套接字维护两个队列,通过accept获取的是已经完成三次握手的连接。
往返时间RTT是指未完成连接队列中连接的留存时间。对于客户端,这个RTT时间是第一次和第二次握手加起来的时间;对于服务器,这个RTT时间实际上是第二次和第三次握手加起来的时间;如果一个恶意客户,迟迟不发送三次握手的第三个包。那么这个连接就建立不起来,那么连接一直处于SYN_RCVD队列【服务器端的未完成队列中】,这个停留时间大概是75秒,如果超过这个时间,这一项会被操作系统干掉。

  • 如果两个队列之和【已完成连接队列,和未完成连接队列】达到了listen()所指定的第二参数,也就是说队列满了。此时,再有一个客户发送syn请求,服务器怎么反应?

    • 实际上服务器会忽略这个syn,不给回应; 客户端这边,发现syn没回应,过一会会重发syn包;
    • 从连接被扔到已经完成队列中去,到accept()从已完成队列中把这个连接取出这个之间是有个时间差的,如果还没等accept()从已完成队列中把这个连接取走的时候,客户端如果发送来数据,这个数据就会被保存再已经连接的套接字的接收缓冲区里,这个缓冲区有多大,最大就能接收多少数据量;
  • syn攻击【syn flood】:典型的利用TCP/IP协议涉及弱点进行坑爹的一种行为

    • 拒绝服务攻击(DOS/DDOS);
    • backlog:进一步明确和规定了:指定给定套接字上内核为之排队的最大已完成连接数【已完成连接队列中最大条目数】;
    • 大家在写代码时尽快用accept()把已完成队列里边的连接取走,尽快 留出空闲为止给后续的已完成三路握手的条目用,那么这个已完成队列一般不会满;
    • 一般这个backlog值给300左右;

参考资料:套接字-Socket网络编程4(TCP通信流程)

4.2 阻塞/非阻塞IO

  1. 阻塞IO
    调用阻塞式函数,这个函数就卡在这里,整个程序流程不往下走了【休眠sleep,不会占用CPU】,该函数卡在这里等待一个事情发生,只有这个事情发生了,这个函数才会往下走;这种函数,就认为是阻塞函数;accept(); 这种阻塞,并不好,效率很低;一般我们不会用阻塞方式来写服务器程序,效率低;
  2. 非阻塞IO
    不会卡住,充分利用时间片,执行效率更高;非阻塞模式的两个鲜明特点:
    • 不断的调用accept(),recvfrom()函数来检查有没有数据到来,如果没有,函数会返回一个特殊的错误标记来告诉你,这种标记可能是EWULDBLOCK,也可能是EAGAIN;
    • 如果数据没到来,那么这里有机会执行其他函数,但是也得不停的再次调用accept(),recvfrom()来检查数据是否到来,非常累;
    • 如果数据到来,那么就得卡在这里把数据从内核缓冲区复制到用户缓冲区,所以复制这个阶段是卡着完成的;

4.3 同步/异步IO

  1. 异步IO

    • 调用一个异步I/O函数时,我门要给这个函数指定一个接收缓冲区,我还要给定一个回调函数;
    • 调用完一个异步I/O函数后,该函数会立即返回。
    • 其余判断交给操作系统,操作系统会判断数据是否到来,如果数据到来了,操作系统会把数据拷贝到你所提供的缓冲区里,然后调用你所指定的这个回调函数来处理数据。
  2. 同步I/O

    • 调用select()判断有没有数据,有数据,走下来,没数据卡在那里;
    • select()返回之后,用recvfrom()去取数据;当然取数据的时候也会卡那么一下;同步I/O感觉更麻烦,要调用两个函数才能把数据拿到手;但是同步I/O和阻塞式I/O比,就是所谓的 I/O复用【用两个函数来收数据的优势】 能力;
  3. I/O复用

    • 所谓I/O复用,就是我多个socket【多个TCP连接】可以弄成一捆【一堆】,我可以用select这种同步I/O函数在这等数据;
    • select()的能力是等多条TCP连接上的任意一条有数据来;然后哪条TCP有数据来,我再用具体的比如recvfrom()去收。
    • 这种调用一个函数能够判断一堆TCP连接是否来数据的这种能力,叫I/O复用,英文I/O multiplexing【I/O多路复用】
  4. 非阻塞和异步I/O的差别

    • 非阻塞I/O要不停的调用I/O函数来检查数据是否来,如果数据来了,就得卡在I/O函数这里把数据从内核缓冲区复制到用户缓冲区,然后这个函数才能返回;
    • 异步I/O根本不需要不停的调用I/O函数来检查数据是否到来,只需要调用一次,然后就可以干别的事情去了;内核判断数据到来,拷贝数据到你提供的缓冲区,调用你的回调函数来通知你,你并没有被卡在那里的情况;

4.4 TCP/IP设计

4.4.1 三次握手

在这里插入图片描述

  1. 为什么是三次握手?
    三次握手的目的是建立可靠的通信信道,也就是要确保双方的发送和接收能力都是正常的

    • 第一次握手,客户端向服务端发送SYN报文,在服务端的视角看,可以确认客户端发送能力正常,服务端的接收能力正常
    • 第二次握手,服务端向客户端发送ACK+SYN报文,在客户端的视角看,可以确认客户端的发送和接收能力是正常的,服务端的发送和接收能力正常
    • 第三次握手,客户端向服务端发送ACK包报文,在服务端的视角看,可以确认客户端发送和接收能力正常,服务端的发送和接收能力正常
      如果只有两次握手,在客户端的视角看,可以确认双反的收发能力正常;但在服务端的视角看,只能确认客户端的发送能力和服务的接收能力正常。
  2. 初始序列号为什么是随机的?
    当一端为建立连接而发送它的 SYN 时,它会为连接选择一个初始序号。ISN 随时间而变化,因此每个连接都将具有不同的 ISN。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

  3. 为什么只有第三次握手可以携带数据?

    • 假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,然后疯狂重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。
    • 对于第三次的话,完成第二次握手后,客户端已经处于 ESTABLISHED 状态,已经知道服务器的接收、发送能力是正常的了,所以能携带数据。
  4. 如果第三次握手丢失了,服务端会如何处理
    服务器发送完 SYN-ACK 包,如果未收到客户端响应的确认包,也即第三次握手丢失。需要分两种情况处理。

    • 第一种是客户端没有发送其他数据包,那么服务器就会重传SYN-ACK包,若等待一段时间仍未收到客户确认包,就进行第二次重传。如果重传次数超过系统规定的最大重传次数,则系统将该连接信息从半连接队列中删除。重传等待一般是指数增长1s,2s,4s,8s…
    • 第二种是客户端有发送其他数据包,那么这个数据包会被当做是携带了数据包的第三次握手。TCP规定,除了前两次握手报文之外,其他所有报文都将ACK标志位设置为1,所有服务端会把数据包当做是ack确认包从而完成了三次握手。

4.4.2 四次挥手

在这里插入图片描述
对于每个TCP连接,操作系统需要分别开辟一个收发缓冲区来处理数据的收发。当关闭一个TCP连接时,如果发送缓冲区有数据,操作系统会把发送缓冲区中残留的数据发完再发FIN包表示连接关闭。

  1. 为什么是四次挥手?
    四次挥手的目的是确保双方的数据都已发送完毕,确保连接安全关闭。

    • 第一次挥手,客户端向服务端发送FIN报文,此时代表客户端数据已经发送完毕
    • 第二次挥手,服务端向客户端发送ACK报文,此时客户端到服务端的连接关闭
    • 第三次挥手,服务端向客户端发送FIN报文,此时代表服务端数据已经发送完毕
    • 第四次挥手,客户端向服务端发送ACK报文,此时双方连接关闭
  2. 为什么主动关闭(客户端)的一方需要TIME-WAIT等待2MSL?

    • 保证TCP协议的全双工连接能够可靠关闭。如果第四次挥手ACK包丢失,服务端重发FIN包, 由于客户端已经关闭
      ,客户端就找不到与重发的FIN对应的连接,就会向服务端连接复位RST包,导致TCP协议不符合可靠连接的要求。
    • 保证这次连接的重复数据段从网络中消失。如果客户端直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的,特别是如果开启了快速重用端口选项SO_REUSEPORT。也就是说有可能新连接和老连接的端口号是相同的。假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,新旧连接的数据包发生混淆了。等待2MSL,这样可以保证本次连接的所有数据都从网络中消失。
  3. 浅谈SO_REUSEPORT选项
    SO_REUSEADDR:主要解决TIME_WAIT状态导致bind()失败的问题

    //setsockopt(SO_REUSEADDR)用在服务器端,socket()创建之后,bind()之前
    //setsockopt():设置一些套接字参数选项;
    //参数2:是表示级别,和参数3配套使用,也就是说,参数3如果确定了,参数2就确定了;
    //参数3:允许重用本地地址
    int  reuseaddr=1; //开启
    if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, (const void *) &reuseaddr,sizeof(reuseaddr)) == -1)
    {char *perrorinfo = strerror(errno); printf("setsockopt(SO_REUSEADDR)返回值为%d,错误码为:%d,错误信息为:%s;\n",-1,errno,perrorinfo);
    }
    
    • SO_REUSEADDR允许启动一个监听服务器并捆绑其端口,即使以前建立的将端口用作他们的本地端口的连接仍旧存在;【即便TIME_WAIT状态存在,服务器bind()也能成功】
    • 允许同一个端口上启动同一个服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可;
    • SO_REUSEADDR允许单个进程捆绑同一个端口到多个套接字,只要每次捆绑指定不同的本地IP地址即可;
    • SO_REUSEADDR允许完全重复的绑定:当一个IP地址和端口已经绑定到某个套接字上时,如果传输协议支持,同样的IP地址和端口还可以绑定到另一个套接字上;一般来说本特性仅支持UDP套接字[TCP不行];
    • 所有TCP服务器都应该指定本套接字选项,以防止当套接字处于TIME_WAIT时bind()失败的情形出现

5. epoll技术

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

epoll可以理解为event poll,它是一种事件驱动的I/O模型,可以用来替代传统的select和poll模型。epoll的优势在于它可以同时处理大量的文件描述符,而且不会随着文件描述符数量的增加而降低效率。

epoll的实现机制是通过内核与用户空间共享一个事件表,这个事件表中存放着所有需要监控的文件描述符以及它们的状态,当文件描述符的状态发生变化时,内核会将这个事件通知给用户空间,用户空间再根据事件类型进行相应的处理。

epoll的接口和工作模式相对于select和poll更加简单易用,因此在高并发场景下被广泛使用。

epoll底层维护两个数据结构,一个是红黑数,保存所有监听的socket,一个是双向链表,保存当前有事件发生的socket,当epoll监听的socket有事件发生时,内核调用epoll_event_callback()向双向链表增加节点。

5.1 epoll_create()

  • 函数声明int epoll_create(int size)
  • 参数 size 大于0就行
  • 功能 创建一个epoll对象,返回该对象的描述符【文件描述符】,这个描述符就代表这个epoll对象
  • 函数实现
    • 分配内存创建eventpoll结构体
    • eventpoll->rbr指向红黑数的根节点
    • eventpoll->rdlist双向链表的头节点

5.2 epoll_ctl()

  • 函数声明int epoll_ctl(int efpd,int op,int sockid,struct epoll_event *event);
  • 参数
    • efpd:epoll_create()返回的epoll对象描述符;
    • op:动作,添加/删除/修改 ,对应数字是1,2,3, EPOLL_CTL_ADD, EPOLL_CTL_DEL ,EPOLL_CTL_MOD
      • EPOLL_CTL_ADD添加事件:等于你往红黑树上添加一个节点,每个客户端连入对应一个socket,这个socket就是红黑树中的key,把这个节点添加到红黑树上去;
      • EPOLL_CTL_MOD:修改事件;你 用了EPOLL_CTL_ADD把节点添加到红黑树上之后,才存在修改;
      • EPOLL_CTL_DEL:是从红黑树上把这个节点干掉;这会导致这个socket【这个tcp链接】上无法收到任何系统通知事件;
    • sockid:表示客户端连接,就是你从accept();这个是红黑树里边的key;
    • event:事件信息,这里包括的是 一些事件信息;EPOLL_CTL_ADD和EPOLL_CTL_MOD都要用到这个event参数里边的事件信息;
  • 功能
    把一个socket以及这个socket相关的事件添加到这个epoll对象描述符中去,目的就是通过这个epoll对象来监视这个socket【客户端的TCP连接】上数据的来往情况;当有数据来往时,系统会通知我们;
  • 函数实现
    • 【EPOLL_CTL_ADD】增加节点到红黑树中,添加新的监听socket
    • 【EPOLL_CTL_DEL】从红黑树中把节点干掉,移除监听的socket
    • 【EPOLL_CTL_MOD】找到红黑树节点,修改这个节点中的内容,修改socket的监听事件(读/写/关闭)

5.3 epoll_wait()

  • 函数声明 int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
  • 参数
    • epfd:是epoll_create()返回的epoll对象描述符;
    • events:是内存,也是数组,长度 是maxevents,表示此次调用可以收到maxevents个已经准备好的读写事件;说白了,就是返回的是 实际 发生事件的tcp连接数目;
    • timeout:阻塞等待的时长;
  • 功能
    • 阻塞一小段时间并等待事件发生,返回事件集合,也就是获取内核的事件通知;
    • 说白了就是遍历这个双向链表,把这个双向链表里边的节点数据拷贝出去,拷贝完毕的就从双向链表里移除;双向链表里记录的是所有有数据/有事件的socket【TCP连接】;
    • epitem结构设计的高明之处:既能够作为红黑树中的节点(rbr),又能够作为双向链表中的节点(rdlink);

5.4 epoll工作模式

  • LT 水平触发,低速模式,效率差(缺省),一个事件不处理,它会一直触发
  • ET 边缘触发,高速模式,速度快,只对非阻塞socket生效,内核只会通知一次(不管是否处理),编码难度加大

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/438887.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器人的性能指标

1. 负荷能力 负荷能力负荷能力是指机器人在满足其他性能要求的情况下,能够承载的负荷重量。例如,一台机器人的最大负荷能力可能远大于它的额定负荷能力,但是达到最大负荷时,机器人的工作精度可能会降低,可能无法准确地沿着预定的轨迹运动,或者产生额外的偏差。机器人的负荷量与…

【重学 MySQL】四十一、子查询举例与分类

【重学 MySQL】四十一、子查询举例与分类 引入子查询在SELECT子句中引入子查询在FROM子句中引入子查询在WHERE子句中引入子查询注意事项 子查询分类标量子查询列子查询行子查询表子查询 子查询注意事项子查询的位置子查询的返回类型别名的使用性能考虑相关性错误处理逻辑清晰 总…

Flet介绍:平替PyQt的好用跨平台Python UI框架

随着Python在各个领域的广泛应用&#xff0c;特别是在数据科学和Web开发领域&#xff0c;对于一个简单易用且功能强大的用户界面&#xff08;UI&#xff09;开发工具的需求日益增长。传统的Python GUI库如Tkinter、PyQt虽然功能强大&#xff0c;但在易用性和现代感方面略显不足…

数据结构--二叉树的顺序实现(堆实现)

引言 在计算机科学中&#xff0c;二叉树是一种重要的数据结构&#xff0c;广泛应用于各种算法和程序设计中。本文将探讨二叉树的顺序实现&#xff0c;特别是堆的实现方式。 一、树 1.1树的概念与结构 树是⼀种⾮线性的数据结构&#xff0c;它是由 n(n>0) 个有限结点组成…

【HTML5】html5开篇基础(5)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

vue-live2d看板娘集成方案设计使用教程

文章目录 前言v1.1.x版本&#xff1a;vue集成看板娘&#xff08;暂不使用&#xff0c;在v1.2.x已替换&#xff09;集成看板娘实现看板娘拖拽效果方案资源备份存储 当前最新调研&#xff1a;2024.10.2开源方案1&#xff1a;OhMyLive2D&#xff08;推荐&#xff09;开源方案2&…

【设计模式】软件设计原则——接口隔离迪米特

接口隔离原则引出 接口隔离原则 定义&#xff1a;用多个专门的接口,不使用单一的总接口,客户端不应该依赖它不需要的接口; 一个类对另一个类的依赖,应该建立在最小接口上;如果有一个大接口,里面有很多方法,如果使用一个类实现该接口,所有的类都要实现&#xff0c;导致代码冗余;…

android 全面屏最底部栏沉浸式

Activity的onCreate方法中添加 this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); Android 系统 Bar 沉浸式完美兼容方案自 Android 5.0 版本&#xff0c;Android 带来了沉浸式系统 ba - 掘金 (juejin.cn)https://juejin.cn/post/7075578…

【HTTP(3)】(状态码,https)

【认识状态码】 状态码最重要的目的&#xff0c;就是反馈给浏览器:这次请求是否成功&#xff0c;若失败&#xff0c;则出现失败原因 常见状态码: 200:OK&#xff0c;表示成功 404:Not Found&#xff0c;浏览器访问的资源在服务器上没有找到 403:Forbidden&#xff0c;访问被…

【每天学个新注解】Day 15 Lombok注解简解(十四)—@UtilityClass、@Helper

UtilityClass 生成工具类的注解 将一个类通过注解变成一个工具类&#xff0c;并没有什么用&#xff0c;本来代码中的工具类数量就极为有限&#xff0c;并不能达到减少重复代码的目的 1、如何使用 加在需要委托将其变为工具类的普通类上。 2、代码示例 例&#xff1a; Uti…

基于Java,SpringBoot,Vue智慧校园健康驿站体检论坛请假管理系统

摘要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#xf…

景区+商业,如何实现1+1>2?

景区商业&#xff0c;如何实现11&#xff1e;2&#xff1f; 近两年&#xff0c;随着旅游业的蓬勃发展&#xff0c;旅游热潮持续升温&#xff0c;游客的消费观念也在逐步升级。为了适应这一趋势&#xff0c;各大景区纷纷着手打造具有鲜明特色的文旅项目&#xff0c;希望能够吸引…

C++ | Leetcode C++题解之第457题环形数组是否存在循环

题目&#xff1a; 题解&#xff1a; class Solution { public:bool circularArrayLoop(vector<int>& nums) {int n nums.size();auto next [&](int cur) {return ((cur nums[cur]) % n n) % n; // 保证返回值在 [0,n) 中};for (int i 0; i < n; i) {if …

cherry-markdown开源markdown组件详细使用教程

文章目录 前言开发定位目标调研技术方案前提工作量安排数据库表设计实现步骤1、引入依赖2、实现cherry-markdown的vue组件&#xff08;修改上传接口路径&#xff09;3、支持draw.io组件4、支持展示悬浮目录toc前端使用&#xff1a;编辑状态使用cherry-markdown的vue组件前端使用…

解决npm安装不了element库(目前未解决。。。)

根据您提供的错误信息&#xff0c;安装 element-plus 时出现了一些问题。这些错误主要可以分为两类&#xff1a;权限问题和网络问题。以下是一些解决这些问题的建议&#xff1a; 1. 解决权限问题 您遇到的 EPERM: operation not permitted 错误通常与文件系统权限有关。尝试以…

Stable Diffusion绘画 | 插件-Deforum:动态视频生成(中篇)

本篇文章重点讲解参数最多的 关键帧 模块。 「动画模式」选择「3D」&#xff1a; 下方「运动」Tab 会有一系列参数&#xff1a; 以下4个参数&#xff0c;只有「动画模式」选择「2D」才会生效&#xff0c;可忽略&#xff1a; 运动 平移 X 让镜头左右移动&#xff1a; 大于0&a…

卷积神经网络(CNN)的计算量和参数怎么准确估计?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 1. 卷积层&#xff08;Convolutional Layer&#xff09; a) 计算量估计&#xff1a; 卷积层的 FLOPs 2 * H_out * W_out * C_in * C_out * K_h * K_w 详细解释&#xff1a; H_out, W_out&#xff…

YOLO11改进|注意力机制篇|引入HAT超分辨率重建模块

目录 一、HAttention注意力机制1.1HAttention注意力介绍1.2HAT核心代码 二、添加HAT注意力机制2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、HAttention注意力机制 1.1HAttention注意力介绍 HAT模型 通过结合卷积特征提取与多尺度注意…

推荐 uniapp 相对好用的海报生成插件

插件地址&#xff1a;自定义canvas样式海报 - DCloud 插件市场 兼容性也是不错的&#xff1a;

MySQL基础篇 - 事务

01 事务的简介 【1】什么是事务&#xff1a;事务是一组操作集合&#xff0c;要么同时操作成功&#xff0c;要么同时操作失败。 【2】对于MySQL数据库来说默认一条SQL语句就是一个事务&#xff0c;且事务是默认自动提交的。 我们可以把多条SQL语句设置成一个事务&#xff0c;使…