• 通过多进程并发方式(fork)实现服务器(注意要回收子进程)


    以下内容为视频学习记录。

    1、父进程accept后返回的文件描述符为cfd以及用于创建连接的lfd;

    调用fork()创建子进程后,子进程继承cfd,lfd,通过该cfd与连接过来的客户端通信,lfd对子进程来说没用,可以直接close(lfd);

    对于父进程来说,只是用来建立连接的,故父进程中的cfd没有用,直接close(cfd);

    2、子进程完成数据交互后,close(cfd);exit(1);此时成为僵尸进程,所以需要在父进程中收尸,回收进程描述符等资源。否则的话,当父进程一直没有退出的时候,僵尸进程会导致资源消耗殆尽。

    但此时父进程在accept,无法waitpid(),此时使用signal的方式SIGCHILD来解决收尸问题,当子进程成僵尸进程后,由内核自动回收。

    注意:进程只能由父进程来回收,兄弟进程、叔叔进程都不行。

    1. #include <stdio.h>
    2. #include <string.h>
    3. #include <netinet/in.h>
    4. #include <arpa/inet.h>
    5. #include <signal.h>
    6. #include <sys/wait.h>
    7. #include <ctype.h>
    8. #include <unistd.h>
    9. #include "wrap.h"
    10. #define MAXLINE 8192
    11. #define SERV_PORT 8000
    12. void do_sigchild(int num)
    13. {
    14. while (waitpid(0, NULL, WNOHANG) > 0)
    15. ;
    16. }
    17. int main(void)
    18. {
    19. struct sockaddr_in servaddr, cliaddr;
    20. socklen_t cliaddr_len;
    21. int listenfd, connfd;
    22. char buf[MAXLINE];
    23. char str[INET_ADDRSTRLEN];
    24. int i, n;
    25. pid_t pid;
    26. struct sigaction newact;
    27. newact.sa_handler = do_sigchild;
    28. sigemptyset(&newact.sa_mask);
    29. newact.sa_flags = 0;
    30. sigaction(SIGCHLD, &newact, NULL);
    31. listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    32. int opt = 1;
    33. setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    34. bzero(&servaddr, sizeof(servaddr));
    35. servaddr.sin_family = AF_INET;
    36. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    37. servaddr.sin_port = htons(SERV_PORT);
    38. Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    39. Listen(listenfd, 20);
    40. printf("Accepting connections ...\n");
    41. while (1) {
    42. cliaddr_len = sizeof(cliaddr);
    43. connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    44. pid = fork();
    45. if (pid == 0) {
    46. Close(listenfd);
    47. while (1) {
    48. n = Read(connfd, buf, MAXLINE);
    49. if (n == 0) {
    50. printf("the other side has been closed.\n");
    51. break;
    52. }
    53. printf("received from %s at PORT %d\n",
    54. inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
    55. ntohs(cliaddr.sin_port));
    56. for (i = 0; i < n; i++)
    57. buf[i] = toupper(buf[i]);
    58. Write(STDOUT_FILENO, buf, n);
    59. Write(connfd, buf, n);
    60. }
    61. Close(connfd);
    62. return 0;
    63. } else if (pid > 0) {
    64. Close(connfd);
    65. } else
    66. perr_exit("fork");
    67. }
    68. return 0;
    69. }

    总结:子进程负责与客户端通信,父进程负责接收客户端连接,但因为父进程还需要回收子进程,所以通过信号的方式来回收,

    信号部分的代码也可以放在fork之后判断父进程位置。

    1. else if (pid > 0) {
    2.             Close(connfd);
    3. signal(SIGCHLD,wait_child);
    4.         }  
    5. main函数外定义回调函数
    6. void wait_child(int signo)
    7. {
    8. while(watipid(0,NULL,WNOHANG)>0);  //大于0则继续回收
    9. return;
    10. }

  • 相关阅读:
    工作中一定要学会拒绝?
    JavaScript学习(二)——基本对象
    《rust学习一》 fleet 配置rust环境
    基于鹈鹕算法优化概率神经网络PNN的分类预测 - 附代码
    什么是反向代理,它是如何工作的?
    WebDAV之葫芦儿·派盘+无忧日记
    20230910java面经整理
    Nginx流量控制
    PLC可以连接哪些工业设备实现远距离无线通讯?工业网关可以吗?
    Lambda表达式实现方式、标准格式、练习、省略模式、注意事项及和匿名内部类的区别
  • 原文地址:https://blog.csdn.net/modi000/article/details/136358653