• linux并发服务器 —— IO多路复用(八)


    半关闭、端口复用

    半关闭只能实现数据单方向的传输;当TCP 接中A向 B 发送 FIN 请求关闭,另一端 B 回应ACK 之后 (A 端进入 FIN_WAIT_2 状态),并没有立即发送 FIN 给 A,A 方处于半连接状态 (半开关),此时 A 可以接收 B 发送的数据,但是 A 已经不能再向 B 发送数据

    close不会影响到其他进程,shutdown会影响到其他进程;

    网络信息相关的命令

    netstat

            -a 所有的Socket

            -p 正在所用socket的程序名称

            -n 直接使用IP地址,不通过域名服务器

    端口复用

    1. 防止服务器重启时之前绑定的端口还没释放

    2. 程序突然退出而系统没有释放端口

    IO多路复用简介

    I/O多路复用使程序可以同时监听多个文件描述符,提高程序性能;select/poll/epoll

    阻塞等待:不占用CPU宝贵时间;但同一时刻只能处理一个操作,效率低。

    非阻塞,忙轮询:提高了程序执行效率;但会占用更多的CPU资源。

    select/poll:委托内核进行检测,但仍需要进行遍历

    epoll:同样委托内核,但无需进行遍历

    select

    主旨思想:

    1. 构造关于文件描述符的列表,将要监听的文件描述符添加到表中

    2. 调用系统函数,监听该列表中的文件描述符,知道描述符中的一个/多个进行了I/O操作,函数才返回(该函数是阻塞的,且该函数对于文件描述符的检测是由内核完成的)

    3. 返回时,告诉进程有多少描述符要进行I/O操作

    返回值: 失败 - -1,成功 - 检测到的描述符个数

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. int main(){
    10. // 创建socket
    11. int lfd = socket(PF_INET , SOCK_STREAM , 0);
    12. struct sockaddr_in saddr;
    13. saddr.sin_port = htons(9999);
    14. saddr.sin_family = AF_INET;
    15. saddr.sin_addr.s_addr = INADDR_ANY;
    16. // 绑定
    17. bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
    18. // 监听
    19. listen(lfd , 8);
    20. fd_set rdset , tmp;
    21. FD_ZERO(&rdset);
    22. FD_SET(lfd , &rdset);
    23. int maxfd = lfd+1;
    24. while(1){
    25. tmp = rdset;
    26. // 调用select 系统检测
    27. int ret = select(maxfd+1 , &tmp , NULL , NULL , NULL);
    28. if(ret == -1){
    29. perror("select");
    30. exit(-1);
    31. }
    32. else if(ret == 0){
    33. continue;
    34. }
    35. else{
    36. // 检测到了文件描述符的数据发生了改变
    37. if(FD_ISSET(lfd , &tmp)){
    38. // 有客户端连接进来
    39. struct sockaddr_in caddr;
    40. socklen_t len = sizeof(caddr);
    41. int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
    42. // 添加进去
    43. FD_SET(cfd , &rdset);
    44. maxfd = maxfd>cfd?maxfd:cfd+1;
    45. }
    46. for(int i = lfd+1 ; i <= maxfd ; i++){
    47. if(FD_ISSET(i , &tmp)){
    48. // 说明客户端发来了数据
    49. char buf[1024];
    50. int len = read(i , buf , sizeof(buf));
    51. if(len == -1){
    52. perror("read");
    53. exit(-1);
    54. }
    55. else if(len == 0){
    56. cout<<"client close..."<
    57. close(i);
    58. FD_CLR(i,&rdset);
    59. }
    60. else{
    61. cout<<"发来了数据:"<
    62. write(i , buf , strlen(buf)+1);
    63. }
    64. }
    65. }
    66. }
    67. }
    68. close(lfd);
    69. return 0;
    70. }

    poll

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. int main(){
    10. // 创建socket
    11. int lfd = socket(PF_INET , SOCK_STREAM , 0);
    12. struct sockaddr_in saddr;
    13. saddr.sin_port = htons(9999);
    14. saddr.sin_family = AF_INET;
    15. saddr.sin_addr.s_addr = INADDR_ANY;
    16. // 绑定
    17. bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
    18. // 监听
    19. listen(lfd , 8);
    20. // 初始化检测文件描述符数组
    21. struct pollfd fds[1024];
    22. for(int i = 0 ; i<1024 ; i++){
    23. fds[i].fd = -1;
    24. fds[i].events = POLLIN;
    25. }
    26. fds[0].fd = lfd;
    27. int nfds = 0;
    28. while(1){
    29. // poll 系统检测
    30. int ret = poll(fds , nfds+1 , -1);
    31. if(ret == -1){
    32. perror("poll");
    33. exit(-1);
    34. }
    35. else if(ret == 0){
    36. continue;
    37. }
    38. else{
    39. // 检测到了文件描述符的数据发生了改变
    40. if(fds[0].revents & POLLIN){
    41. // 有客户端连接进来
    42. struct sockaddr_in caddr;
    43. socklen_t len = sizeof(caddr);
    44. int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
    45. // 添加进去
    46. for(int i = 1 ; i < 1024 ; i++){
    47. if(fds[i].fd == -1){
    48. fds[i].fd = cfd;
    49. fds[i].events = POLLIN;
    50. break;
    51. }
    52. }
    53. nfds = nfds>cfd?nfds:cfd;
    54. }
    55. for(int i = 1 ; i <= nfds ; i++){
    56. if(fds[i].revents & POLLIN){
    57. // 说明客户端发来了数据
    58. char buf[1024];
    59. int len = read(fds[i].fd , buf , sizeof(buf));
    60. if(len == -1){
    61. perror("read");
    62. exit(-1);
    63. }
    64. else if(len == 0){
    65. cout<<"client close..."<
    66. close(fds[i].fd);
    67. fds[i].fd = -1;
    68. }
    69. else{
    70. cout<<"发来了数据:"<
    71. write(fds[i].fd , buf , strlen(buf)+1);
    72. }
    73. }
    74. }
    75. }
    76. }
    77. close(lfd);
    78. return 0;
    79. }

    epoll

    内核,红黑树记录要检测的文件描述符,避免了用户态到内核态的数据拷贝开销;

    内核,双链表存放数据改变的文件描述符

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. int main(){
    10. // 创建socket
    11. int lfd = socket(PF_INET , SOCK_STREAM , 0);
    12. struct sockaddr_in saddr;
    13. saddr.sin_port = htons(9999);
    14. saddr.sin_family = AF_INET;
    15. saddr.sin_addr.s_addr = INADDR_ANY;
    16. // 绑定
    17. bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
    18. // 监听
    19. listen(lfd , 8);
    20. // 创建epoll实例
    21. int epfd = epoll_create(100);
    22. // 添加监听文件描述符
    23. struct epoll_event epev;
    24. epev.events = EPOLLIN;
    25. epev.data.fd = lfd;
    26. epoll_ctl(epfd , EPOLL_CTL_ADD , lfd , &epev);
    27. struct epoll_event epevs[1024];
    28. while(1){
    29. int ret = epoll_wait(epfd , epevs , 1024 , -1);
    30. if(ret == -1){
    31. perror("epoll");
    32. exit(-1);
    33. }
    34. cout<"个发生了改变"<
    35. for(int i = 0 ; i
    36. int curfd = epevs[i].data.fd;
    37. if(curfd == lfd){
    38. // 监听的文件描述符有客户端连接
    39. struct sockaddr_in caddr;
    40. socklen_t len = sizeof(caddr);
    41. int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
    42. epev.events = EPOLLIN;
    43. epev.data.fd = cfd;
    44. epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , &epev);
    45. }
    46. else{
    47. // 有数据到达
    48. char buf[1024];
    49. int len = read(curfd , buf , sizeof(buf));
    50. if(len == -1){
    51. perror("read");
    52. exit(-1);
    53. }
    54. else if(len == 0){
    55. cout<<"client close..."<
    56. epoll_ctl(epfd , EPOLL_CTL_DEL , curfd , NULL);
    57. close(curfd);
    58. }
    59. else{
    60. cout<<"发来了数据:"<
    61. write(curfd , buf , strlen(buf)+1);
    62. }
    63. }
    64. }
    65. }
    66. close(lfd);
    67. close(epfd);
    68. return 0;
    69. }

    epoll的两种工作模式

    LT模式 - 水平触发

    默认的工作模式,支持block/no-block;,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行IO操作。如果你不作任何操作,内核还是会继续通知你

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. int main(){
    10. // 创建socket
    11. int lfd = socket(PF_INET , SOCK_STREAM , 0);
    12. struct sockaddr_in saddr;
    13. saddr.sin_port = htons(9999);
    14. saddr.sin_family = AF_INET;
    15. saddr.sin_addr.s_addr = INADDR_ANY;
    16. // 绑定
    17. bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
    18. // 监听
    19. listen(lfd , 8);
    20. // 创建epoll实例
    21. int epfd = epoll_create(100);
    22. // 添加监听文件描述符
    23. struct epoll_event epev;
    24. epev.events = EPOLLIN;
    25. epev.data.fd = lfd;
    26. epoll_ctl(epfd , EPOLL_CTL_ADD , lfd , &epev);
    27. struct epoll_event epevs[1024];
    28. while(1){
    29. int ret = epoll_wait(epfd , epevs , 1024 , -1);
    30. if(ret == -1){
    31. perror("epoll");
    32. exit(-1);
    33. }
    34. cout<"个发生了改变"<
    35. for(int i = 0 ; i
    36. int curfd = epevs[i].data.fd;
    37. if(curfd == lfd){
    38. // 监听的文件描述符有客户端连接
    39. struct sockaddr_in caddr;
    40. socklen_t len = sizeof(caddr);
    41. int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
    42. epev.events = EPOLLIN;
    43. epev.data.fd = cfd;
    44. epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , &epev);
    45. }
    46. else{
    47. // 有数据到达
    48. char buf[5];
    49. int len = read(curfd , buf , sizeof(buf));
    50. if(len == -1){
    51. perror("read");
    52. exit(-1);
    53. }
    54. else if(len == 0){
    55. cout<<"client close..."<
    56. epoll_ctl(epfd , EPOLL_CTL_DEL , curfd , NULL);
    57. close(curfd);
    58. }
    59. else{
    60. cout<<"发来了数据:"<
    61. write(curfd , buf , strlen(buf)+1);
    62. }
    63. }
    64. }
    65. }
    66. close(lfd);
    67. close(epfd);
    68. return 0;
    69. }
    ET模式 - 边沿触发

    告诉工作方式,只支持no-block,在这种模式下,当描述符从未就绪变为就绪时,内核通过epol告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意如果一直不对这个 fd 作IO操作,内核不会发送更多的通知 (only once) 。但是缓冲区中的数据不会丢失

    ET模式效率比LT模式高,ET模式下必须使用非阻塞套接口,避免由于一个描述符的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死;

    要设置边沿触发

    1. 需要在epoll_event中设置EPOLLET

    2. 

    UDP通信实现 - 无需多进程/多线程的并发实现

    1. // server
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. int main(){
    10. // 创建socket
    11. int fd = socket(PF_INET , SOCK_DGRAM , 0);
    12. if(fd == -1){
    13. perror("socket");
    14. exit(-1);
    15. }
    16. // 绑定
    17. struct sockaddr_in addr;
    18. addr.sin_family = AF_INET;
    19. addr.sin_port = htons(9999);
    20. addr.sin_addr.s_addr = INADDR_ANY;
    21. int ret = bind(fd ,(struct sockaddr*) &addr , sizeof(addr));
    22. if(ret == -1){
    23. perror("bind");
    24. exit(-1);
    25. }
    26. // 通信
    27. while(1){
    28. // 接收数据
    29. char buf[128];
    30. char ip[16];
    31. struct sockaddr_in caddr;
    32. socklen_t len = sizeof(caddr);
    33. int num = recvfrom(fd , buf , sizeof(buf) , 0 , (struct sockaddr*)&caddr , &len);
    34. string s1 = "IP: ";
    35. s1 += inet_ntop(AF_INET , &caddr.sin_addr.s_addr , ip , sizeof(ip));
    36. string s2 = "Port: ";
    37. s2 += ntohs(caddr.sin_port);
    38. cout<" "<
    39. cout<<"rcv data: "<
    40. sendto(fd , buf , strlen(buf)+1 , 0 , (struct sockaddr*)&caddr , len);
    41. }
    42. close(fd);
    43. return 0;
    44. }
    1. // client
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. int main(){
    10. // 创建socket
    11. int fd = socket(PF_INET , SOCK_DGRAM , 0);
    12. if(fd == -1){
    13. perror("socket");
    14. exit(-1);
    15. }
    16. struct sockaddr_in addr;
    17. addr.sin_family = AF_INET;
    18. addr.sin_port = htons(9999);
    19. inet_pton(AF_INET , "127.0.0.1" , &addr.sin_addr.s_addr);
    20. // 通信
    21. int num = 0;
    22. socklen_t len = sizeof(addr);
    23. while(1){
    24. char buf[128];
    25. sprintf(buf , "hello 647 %d" , num++);
    26. sendto(fd , buf , strlen(buf)+1 , 0 , (struct sockaddr*)&addr , len);
    27. // 接收数据
    28. int num = recvfrom(fd , buf , sizeof(buf) , 0 , NULL , NULL);
    29. cout<<"rcv data: "<
    30. sleep(1);
    31. }
    32. close(fd);
    33. return 0;
    34. }

    广播和组播 - 只能使用UDP

    广播 - 向子网中多台计算机发送消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1;

    1. 只能在局域网中使用

    2. 客户端需要绑定服务器广播使用的端口,才能接收到广播消息

    组播 (多播) - 标识一组IP接口,是在单播和广播之间的一种折中方案,多播数据包只由对其感兴趣的接口接收;

    1. 组播可以用于局域网和广域网

    2. 客户端需要加入多播组才能接收到

    本地套接字通信

    作用:用于进程间的通信;实现流程和网络套接字类似,一般采用TCP的通信流程

    1. 服务器端
    2. 1. 创建监听的套接字
    3.       int lfd = socket(AF_UNIX/AF_LOCAL , SOCK_STREAM , 0);
    4. 2. 监听套接字绑定本地的套接字文件
    5.         struct sockaddr_un addr;
    6.         bind(lfd, addr, len); // 绑定成功后sun_path中的套接字文件会自动生成
    7. 3. 监听
    8. 4. 等待并接受客户端请求
    9. 5. 通信
    10. 6. 关闭连接
    1. 客户端
    2. 1. 创建通信的套接字
    3. 2. 绑定本地IP端口
    4. 3. 连接服务器
    5. 4. 通信
    6. 5. 关闭连接
    1. // 服务端
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. using namespace std;
    10. int main(){
    11. unlink("server.sock");
    12. // 创建监听套接字
    13. int lfd = socket(AF_LOCAL , SOCK_STREAM , 0);
    14. if(lfd == -1){
    15. perror("socket");
    16. exit(-1);
    17. }
    18. // 绑定本地套接字文件
    19. struct sockaddr_un addr;
    20. addr.sun_family = AF_LOCAL;
    21. strcpy(addr.sun_path , "server.sock");
    22. int ret = bind(lfd , (struct sockaddr*)&addr , sizeof(addr));
    23. if(ret == -1){
    24. perror("bind");
    25. exit(-1);
    26. }
    27. // 监听
    28. ret = listen(lfd , 100);
    29. if(ret == -1){
    30. perror("listen");
    31. exit(-1);
    32. }
    33. // 等待客户端连接
    34. struct sockaddr_un caddr;
    35. socklen_t len = sizeof(caddr);
    36. int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
    37. if(cfd == -1){
    38. perror("accept");
    39. exit(-1);
    40. }
    41. cout<<"客户端文件:"<
    42. // 通信
    43. while(1){
    44. char buf[128];
    45. int len = recv(cfd , buf , sizeof(buf) , 0);
    46. if(len == -1){
    47. perror("recv");
    48. exit(-1);
    49. }
    50. else if(len == 0){
    51. cout<<"client close..."<
    52. break;
    53. }
    54. else{
    55. cout<<"recv data: "<
    56. send(cfd , buf , len , 0);
    57. }
    58. }
    59. close(cfd);
    60. close(lfd);
    61. return 0;
    62. }
    1. // 客户端
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. using namespace std;
    10. int main(){
    11. unlink("client.sock");
    12. // 创建监听套接字
    13. int cfd = socket(AF_LOCAL , SOCK_STREAM , 0);
    14. if(cfd == -1){
    15. perror("socket");
    16. exit(-1);
    17. }
    18. // 绑定本地套接字文件
    19. struct sockaddr_un addr;
    20. addr.sun_family = AF_LOCAL;
    21. strcpy(addr.sun_path , "client.sock");
    22. int ret = bind(cfd , (struct sockaddr*)&addr , sizeof(addr));
    23. if(ret == -1){
    24. perror("bind");
    25. exit(-1);
    26. }
    27. // 连接服务器
    28. struct sockaddr_un saddr;
    29. saddr.sun_family = AF_LOCAL;
    30. strcpy(saddr.sun_path , "server.sock");
    31. ret = connect(cfd , (struct sockaddr*)&saddr , sizeof(saddr));
    32. if(ret == -1){
    33. perror("connect");
    34. exit(-1);
    35. }
    36. // 通信
    37. int num = 0;
    38. while(1){
    39. char buf[128];
    40. sprintf(buf , "hello 647 %d\n" , num++);
    41. send(cfd , buf , strlen(buf)+1 , 0);
    42. int len = recv(cfd , buf , sizeof(buf) , 0);
    43. if(len == -1){
    44. perror("recv");
    45. exit(-1);
    46. }
    47. else if(len == 0){
    48. cout<<"Server close..."<
    49. break;
    50. }
    51. else{
    52. cout<<"recv data: "<
    53. }
    54. sleep(1);
    55. }
    56. close(cfd);
    57. return 0;
    58. }

  • 相关阅读:
    系分 - 操作系统 - 嵌入式
    【PID优化】基于樽海鞘算法PID控制器优化设计含Matlab源码
    Maven引入Aspose依赖
    六级高频词汇——Group02
    模拟一个火车站售票小例子
    Spark RDD算子
    mysql主从和mycat读写分离的安装及验证
    1.Javascript-JavaScript事件
    Jetson Nano TensorRT C++加速 YOLOV5,集成进qt项目中
    计算机网络:随机访问介质访问控制之令牌传递协议
  • 原文地址:https://blog.csdn.net/rygy_/article/details/132677809