飞道的博客

linux网络编程-多进程实现TCP并发服务器

355人阅读  评论(0)

服务端流程步骤

  • bind函数将监听套接字绑定ip和端口

  • listen函数设置服务器为被动监听状态,同时创建一条未完成连接队列(没走完tcp三次握手流程的连接),和一条已完成连接队列(已完成tcp三次握手的连接)

  • accept函数循环的从已完成连接队列中提取连接,并返回一个新的套接字cfd跟客户端进行通信

  • fork函数创建一个子进程,让子进程与客户端进行通信

  • 子进程:read函数循环的从r缓冲区读取客户端发送的数据,write函数将要发送的数据写入w缓冲区

  • close函数关闭套接字

客户端流程步骤

  • socket函数创建套接字

  • connect函数连接服务器

  • write函数将要发送的数据写入w缓冲区,read函数从r缓冲区读取服务器发送给客户端的数据

  • close函数关闭套接字

客户端跟服务器通信流程图

相关函数


  1. int socket(int domain, int type, int protocol);

功能:创建一个用于网络通信的套接字文件描述符

参数:domain:协议族(AF_INET:ipv4,AF_INET6:ipv6,等等)

type:套接字类型(SOCK_DGRAM:udp,SOCK_STREAM:tcp,等等)

protocol:用于制定某个协议的特定类型,即type类型中的某个类型,通常不用管它,设置 为 0

返回值:成功则返回socket套接字描述符, 失败返回-1,并设置errno

头文件:#include <sys/socket.h>


  1. int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

功能:将sockfd绑定ip和端口

参数:sockfd:套接字

my_addr:存放有协议,ip,端口的结构体信息

addrlen:my_addr结构体大小

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <sys/types.h>

#include <sys/socket.h>


  1. int listen(int s, int backlog);

功能:让服务器处于被动监听状态,同时创建了一条未完成三次握手的连接队列和一条已经完成三次握 手的连接队列

参数:s:套接字

backlog:支持未完成连接和已完成连接之和的最大值,一般设置128

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <sys/socket.h>


  1. int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

功能:从已完成连接队列中提取客户端连接

参数:s:套接字

addr:存放成功连接的客户端的ip,端口等信息结构体

addrlen:存放addr结构体大小的变量地址

返回值:成功则返回一个非负整数标识这个连接套接字,是否返回-1

头文件:#include <sys/types.h>

#include <sys/socket.h>


  1. ssize_t read(int fd, void *buf, size_t count);

功能:从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中

参数:fd:文件描述符

buf:缓冲区

count:读count字节

返回值:成功时返回读取到的字节数,失败返回-1,并设置errno

头文件:#include <unistd.h>


  1. ssize_t write(int fd, const void *buf, size_t count);

功能:向文件描述符fd所引用的文件中写入从buf开始的缓冲区中count字节的数据

参数:fd:文件描述符

buf:缓冲区

count:写count字节

返回值:功时返回所写入的字节数,失败返回-1,并设置errno

头文件:#include <unistd.h>


  1. int close(int fd);

功能:关闭 一个文件描述符

参数:fd:要关闭的文件描述符

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <unistd.h>


  1. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:连接服务器

参数:sockfd:套接字

addr:服务器的ip,端口等结构体信息

addrlen:addr结构体大小

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <sys/socket.h>


  1. pid_t fork(void);

功能:创建一个子进程

参数:无

返回值:父进程返回子进程的进程id,子进程返回0

头文件:#include <unistd.h>


  1. int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

功能:设置信号集屏蔽字

参数:how:SIG_BLOCK (mask |= set),SIG_UNBLOCK (mask &= ~set),SIG_SETMASK (mask = set)

set:信号集

oldset:旧的信号集

返回值:成功返回0,失败返回-1,并设置errno

头文件:#include <signal.h>


  1. pid_t waitpid(pid_t pid, int *status, int options);

功能:回收已结束的子进程资源

参数:pid:pid<-1 等待进程组识别码为pid绝对值的任何子进程

pid=-1 等待任何子进程, 相当于wait()

pid=0 等待进程组识别码与目前进程相同的任何子进程

pid>0 等待任何子进程识别码为pid的子进程

status:子进程的结束状态值

options:WNOHANG 如果没有任何已经结束的子进程则马上返回, 不予以等待

WUNTRACED 如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会

返回值:成功返回子进程pid,失败返回-1

头文件: #include <sys/types.h>

#include <sys/wait.h>


服务端代码


   
  1. #include <stdio.h>
  2. #include <sys/socket.h>
  3. #include <arpa/inet.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. #include <stdlib.h>
  8. #include <sys/types.h>
  9. #include <sys/wait.h>
  10. #include <errno.h>
  11. #define BUF_SIZE 256
  12. void do_sigchld(int signo, siginfo_t *siginfo, void *p)
  13. {
  14. int status;
  15. pid_t pid;
  16. while((pid = waitpid( 0, &status, WNOHANG)) > 0) //0:回收跟调用进程同组的子进程的资源,WNOHANG:不阻塞
  17. {
  18. if ( WIFEXITED(status))
  19. printf( "pid = %d, parent pid = %d, child pid = %d, exit status %d\n", pid, getpid(), siginfo->si_pid, WEXITSTATUS(status));
  20. else if ( WEXITSTATUS(status))
  21. printf( "pid = %d, parent pid = %d, child pid = %d, exit by signal %d\n ", pid, getpid(), siginfo->si_pid, WIFSIGNALED(status));
  22. }
  23. }
  24. void sys_err(const char *str, int err)
  25. {
  26. perror(str);
  27. exit(err);
  28. }
  29. int main(int argc, char *argv[])
  30. {
  31. if (argc < 2)
  32. {
  33. printf( "%s port\n", argv[ 0]);
  34. exit( 1);
  35. }
  36. //创建流式套接字
  37. int lfd = socket(AF_INET, SOCK_STREAM, 0);
  38. if (lfd < 0)
  39. sys_err( "socket", 1);
  40. //绑定ip端口
  41. struct sockaddr_in server_addr;
  42. server_addr.sin_family = AF_INET;
  43. server_addr.sin_port = htons(( unsigned short) atoi(argv[ 1]));
  44. server_addr.sin_addr.s_addr = 0; //0表示将本机所有ip都绑定上
  45. int ret = bind(lfd, ( struct sockaddr *)&server_addr, sizeof(server_addr));
  46. if (ret < 0)
  47. sys_err( "bind", 1);
  48. //监听
  49. ret = listen(lfd, 128);
  50. if (ret < 0)
  51. sys_err( "listen", 1);
  52. //阻塞SIGCHLD信号
  53. sigset_t set;
  54. sigemptyset(&set);
  55. sigaddset(&set, SIGCHLD);
  56. sigprocmask(SIG_BLOCK, &set, NULL);
  57. int block = 1;
  58. //循环提取
  59. int cfd;
  60. pid_t pid;
  61. struct sockaddr_in client_addr;
  62. socklen_t len = sizeof(client_addr);
  63. char buf[BUF_SIZE];
  64. ssize_t size;
  65. char ip[INET_ADDRSTRLEN] = "";
  66. while( 1)
  67. {
  68. cfd = accept(lfd, ( struct sockaddr *)&client_addr, &len);
  69. if (cfd < 0)
  70. {
  71. if (errno == EINTR)
  72. continue;
  73. sys_err( "accept", 1);
  74. }
  75. printf( "client ip = %s, port = %d connect success\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client_addr.sin_port));
  76. pid = fork();
  77. if (pid == 0)
  78. {
  79. //in child
  80. close(lfd); //关闭不用了的监听套接字
  81. //解除阻塞SIGCHLD信号
  82. sigprocmask(SIG_UNBLOCK, &set, NULL);
  83. block = 0;
  84. while( 1)
  85. {
  86. memset(buf, 0, sizeof(buf));
  87. size = read(cfd, buf, sizeof(buf));
  88. if (size == 0) //客户端断开连接
  89. {
  90. printf( "client close\n");
  91. break;
  92. }
  93. printf( "%s\n", buf);
  94. write(cfd, buf, size);
  95. }
  96. break;
  97. }
  98. else if (pid > 0)
  99. {
  100. //in parent
  101. close(cfd); //关闭不用了的跟客户端通讯的套接字
  102. if ( 1 == block)
  103. {
  104. //先捕捉
  105. struct sigaction sa;
  106. sa.sa_sigaction = do_sigchld;
  107. sigemptyset(&sa.sa_mask);
  108. sa.sa_flags = SA_SIGINFO;
  109. sigaction(SIGCHLD, &sa, NULL);
  110. //后解除阻塞
  111. sigprocmask(SIG_UNBLOCK, &set, NULL);
  112. block = 0;
  113. }
  114. }
  115. else
  116. sys_err( "fork", 1);
  117. }
  118. //关闭套接字
  119. if (pid == 0)
  120. close(cfd);
  121. else if (pid > 0)
  122. close(lfd);
  123. return 0;
  124. }

客户端代码


   
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <sys/socket.h>
  4. #include <arpa/inet.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. int main(int argc, char *argv[])
  8. {
  9. if (argc < 3)
  10. {
  11. printf( "%s sever_ip server_port\n", argv[ 0]);
  12. exit( 1);
  13. }
  14. //创建流式套接字
  15. int fd = socket(AF_INET, SOCK_STREAM, 0);
  16. if (fd < 0)
  17. {
  18. perror( "socket");
  19. exit( 1);
  20. }
  21. //连接服务器
  22. struct sockaddr_in addr;
  23. addr.sin_family = AF_INET;
  24. addr.sin_port = htons(( unsigned short) atoi(argv[ 2]));
  25. addr.sin_addr.s_addr = inet_addr(argv[ 1]);
  26. int ret = connect(fd, ( struct sockaddr *)&addr, sizeof(addr));
  27. printf( "connect ret = %d\n", ret);
  28. if (ret < 0)
  29. {
  30. perror( "connect");
  31. exit( 1);
  32. }
  33. //读写数据
  34. char buf_w[ 128] = "";
  35. char buf_r[ 128] = "";
  36. ssize_t size_r = 0;
  37. while( 1)
  38. {
  39. fgets(buf_w, sizeof(buf_w), stdin);
  40. buf_w[ strlen(buf_w) - 1] = 0;
  41. write(fd, buf_w, strlen(buf_w));
  42. size_r = read(fd, buf_r, sizeof(buf_r));
  43. if (size_r == 0) //服务器断开
  44. break;
  45. else
  46. printf( "%s\n", buf_r);
  47. memset(buf_w, 0, sizeof(buf_w));
  48. memset(buf_r, 0, sizeof(buf_r));
  49. }
  50. //关闭套接字
  51. close(fd);
  52. return 0;
  53. }

结果

  • 3个客户端连接服务器时

服务端

客户端

ps aux 可以看到有4个服务器进程(其中一个是主进程,用于监听处理新的连接,其他3个子进程用于跟客户端进行通信)

  • 回射服务信息截图

客户端1:

客户端2:

客户端3:

服务器收到进行打印:

3个客户端都退出时

服务端收到客户端退出,并回收跟客户端进行通信的3个子进程的资源

ps aux 查看可以看到只剩主进程,用于监听处理新的连接,其他3个子进程都已结束


转载:https://blog.csdn.net/d704791892/article/details/129168681
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场