• lv7 嵌入式开发-网络编程开发 08 TCP并发功能


    目录

    1 TCP 多进程并发

    1.1 现象:

    1.2 多进程并发

    2 僵尸进程处理

    3 TCP并发多线程

    4 练习


    1 TCP 多进程并发

    1.1 现象:

    之前的代码,先关服务端,再次打开会出现错误bind:Address already in use

    使用setsockopt 地址快速重用可解决(后续会讲套接字设置)

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define BACKLOG 5
    10. void ClinetHandle(int newfd);
    11. int main(int argc, char *argv[])
    12. {
    13. int fd, newfd;
    14. struct sockaddr_in addr, clint_addr;
    15. socklen_t addrlen = sizeof(clint_addr);
    16. pid_t pid;
    17. if(argc < 3){
    18. fprintf(stderr, "%s\n", argv[0]);
    19. exit(0);
    20. }
    21. /*创建套接字*/
    22. fd = socket(AF_INET, SOCK_STREAM, 0);
    23. if(fd < 0){
    24. perror("socket");
    25. exit(0);
    26. }
    27. addr.sin_family = AF_INET;
    28. addr.sin_port = htons( atoi(argv[2]) );
    29. if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
    30. fprintf(stderr, "Invalid address\n");
    31. exit(EXIT_FAILURE);
    32. }
    33. /*地址快速重用*/
    34. int flag=1,len= sizeof (int);
    35. if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) {
    36. perror("setsockopt");
    37. exit(1);
    38. }
    39. /*绑定通信结构体*/
    40. if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
    41. perror("bind");
    42. exit(0);
    43. }
    44. /*设置套接字为监听模式*/
    45. if(listen(fd, BACKLOG) == -1){
    46. perror("listen");
    47. exit(0);
    48. }
    49. while(1){
    50. /*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
    51. newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
    52. if(newfd < 0){
    53. perror("accept");
    54. exit(0);
    55. }
    56. printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
    57. if( (pid = fork() ) < 0){
    58. perror("fork");
    59. exit(0);
    60. }else if(pid == 0){
    61. close(fd);
    62. ClinetHandle(newfd);
    63. exit(0);
    64. }
    65. else
    66. close(newfd);
    67. }
    68. close(fd);
    69. return 0;
    70. }
    71. void ClinetHandle(int newfd){
    72. int ret;
    73. char buf[BUFSIZ] = {};
    74. while(1){
    75. //memset(buf, 0, BUFSIZ);
    76. bzero(buf, BUFSIZ);
    77. ret = read(newfd, buf, BUFSIZ);
    78. if(ret < 0)
    79. {
    80. perror("read");
    81. exit(0);
    82. }
    83. else if(ret == 0)
    84. break;
    85. else
    86. printf("buf = %s\n", buf);
    87. }
    88. close(newfd);
    89. }

     原因:

    虽然程序关闭,但是系统认为服务还在,所以会出现这种情况。

    1.2 多进程并发

    复习fork函数,wait阻塞,会使得子进程结束,父进程才结束,这样两个printf都会打印。

    重点要fork()之后的代码,都会执行两遍,一遍是子进程,一遍是父进程。

    1. #include
    2. #include
    3. #include
    4. int main(int argc, char *argv[])
    5. {
    6. pid_t pid = fork();
    7. if(pid < 0){
    8. perror("fork");
    9. exit(0);
    10. }else if(pid == 0){
    11. printf("This is child process.\n");
    12. }else{
    13. printf("This is father process.\n");
    14. wait(NULL);
    15. }
    16. return 0;
    17. }

    多进程并发服务端实现:

    注意子进程和父进程中的处理细节,防止子进程产生孙进程,防止父、子进程未关闭占用的资源。

    另外启用了accept中两个原来参数,使用函数进行转换

    char * inet_ntoa(struct in_addr in);
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define BACKLOG 5
    10. void ClinetHandle(int newfd);
    11. int main(int argc, char *argv[])
    12. {
    13. int fd, newfd;
    14. struct sockaddr_in addr, clint_addr;
    15. socklen_t addrlen = sizeof(clint_addr);
    16. pid_t pid;
    17. if(argc < 3){
    18. fprintf(stderr, "%s\n", argv[0]);
    19. exit(0);
    20. }
    21. /*创建套接字*/
    22. fd = socket(AF_INET, SOCK_STREAM, 0);
    23. if(fd < 0){
    24. perror("socket");
    25. exit(0);
    26. }
    27. addr.sin_family = AF_INET;
    28. addr.sin_port = htons( atoi(argv[2]) );
    29. if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
    30. fprintf(stderr, "Invalid address\n");
    31. exit(EXIT_FAILURE);
    32. }
    33. /*地址快速重用*/
    34. int flag=1,len= sizeof (int);
    35. if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) {
    36. perror("setsockopt");
    37. exit(1);
    38. }
    39. /*绑定通信结构体*/
    40. if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
    41. perror("bind");
    42. exit(0);
    43. }
    44. /*设置套接字为监听模式*/
    45. if(listen(fd, BACKLOG) == -1){
    46. perror("listen");
    47. exit(0);
    48. }
    49. while(1){
    50. /*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
    51. newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
    52. if(newfd < 0){
    53. perror("accept");
    54. exit(0);
    55. }
    56. //注意理解转换函数
    57. printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
    58. if( (pid = fork() ) < 0){
    59. perror("fork");
    60. exit(0);
    61. }else if(pid == 0){
    62. close(fd); //子进程需要关闭fd,对子进程来讲已经不适用fd了,占用了资源
    63. ClinetHandle(newfd);
    64. exit(0); //退出子进程,防止后面生成孙进程,也进入了accept等待
    65. }
    66. else
    67. close(newfd); //父进程关闭newfd,因为newfd被子进程占用了
    68. }
    69. close(fd);
    70. return 0;
    71. }
    72. void ClinetHandle(int newfd){
    73. int ret;
    74. char buf[BUFSIZ] = {};
    75. while(1){
    76. //memset(buf, 0, BUFSIZ);
    77. bzero(buf, BUFSIZ);
    78. ret = read(newfd, buf, BUFSIZ);
    79. if(ret < 0)
    80. {
    81. perror("read");
    82. exit(0);
    83. }
    84. else if(ret == 0)
    85. break;
    86. else
    87. printf("buf = %s\n", buf);
    88. }
    89. close(newfd);
    90. }

    实验效果:

    2 僵尸进程处理

    现象:如果客户端退出,会产生僵尸进程

    解决方法:使用信号的方式解决僵尸进程,注意flags设置为SA_RESTART的意义

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #define BACKLOG 5
    12. void SigHandle(int sig){
    13. if(sig == SIGCHLD){
    14. printf("client exited\n");
    15. wait(NULL);
    16. }
    17. }
    18. void ClinetHandle(int newfd);
    19. int main(int argc, char *argv[])
    20. {
    21. int fd, newfd;
    22. struct sockaddr_in addr, clint_addr;
    23. socklen_t addrlen = sizeof(clint_addr);
    24. #if 0
    25. struct sigaction act;
    26. act.sa_handler = SigHandle;
    27. act.sa_flags = SA_RESTART; //如果flag = 0会退出,那么让被终止的进程继续运行。注意实验
    28. sigemptyset(&act.sa_mask);
    29. sigaction(SIGCHLD, &act, NULL);
    30. #else
    31. signal(SIGCHLD, SigHandle);
    32. #endif
    33. pid_t pid;
    34. if(argc < 3){
    35. fprintf(stderr, "%s\n", argv[0]);
    36. exit(0);
    37. }
    38. /*创建套接字*/
    39. fd = socket(AF_INET, SOCK_STREAM, 0);
    40. if(fd < 0){
    41. perror("socket");
    42. exit(0);
    43. }
    44. addr.sin_family = AF_INET;
    45. addr.sin_port = htons( atoi(argv[2]) );
    46. if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
    47. fprintf(stderr, "Invalid address\n");
    48. exit(EXIT_FAILURE);
    49. }
    50. /*地址快速重用*/
    51. int flag=1,len= sizeof (int);
    52. if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) {
    53. perror("setsockopt");
    54. exit(1);
    55. }
    56. /*绑定通信结构体*/
    57. if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
    58. perror("bind");
    59. exit(0);
    60. }
    61. /*设置套接字为监听模式*/
    62. if(listen(fd, BACKLOG) == -1){
    63. perror("listen");
    64. exit(0);
    65. }
    66. while(1){
    67. /*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
    68. newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
    69. if(newfd < 0){
    70. perror("accept");
    71. exit(0);
    72. }
    73. printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
    74. if( (pid = fork() ) < 0){
    75. perror("fork");
    76. exit(0);
    77. }else if(pid == 0){
    78. close(fd);
    79. ClinetHandle(newfd);
    80. exit(0);
    81. }
    82. else
    83. close(newfd);
    84. }
    85. close(fd);
    86. return 0;
    87. }
    88. void ClinetHandle(int newfd){
    89. int ret;
    90. char buf[BUFSIZ] = {};
    91. while(1){
    92. //memset(buf, 0, BUFSIZ);
    93. bzero(buf, BUFSIZ);
    94. ret = read(newfd, buf, BUFSIZ);
    95. if(ret < 0)
    96. {
    97. perror("read");
    98. exit(0);
    99. }
    100. else if(ret == 0)
    101. break;
    102. else
    103. printf("buf = %s\n", buf);
    104. }
    105. close(newfd);
    106. }

    3 TCP并发多线程

    目的:多线程占用的资源会更少

    复习:

    pthread_detach() 函数用于将指定的线程设置为分离模式。分离模式的线程在退出时会自动释放资源,不需要通过 pthread_join() 来等待线程结束并获取返回值。

    函数原型为:

    int pthread_detach(pthread_t thread);
    

    参数 thread 是要设置为分离模式的线程标识符。

    返回值:

    • 成功时,返回 0。
    • 失败时,返回一个非零的错误码。

    注意事项:

    • 必须在线程执行之前或者在其它线程中调用 pthread_detach() 函数,否则行为未定义。
    • 一旦线程被设置为分离模式,就无法再使用 pthread_join() 来等待线程结束。
    • 分离模式的线程会在退出时自动释放其资源,但必须确保线程在退出前不会产生资源泄漏。
    • 默认情况下,线程是非分离模式,需要显式调用 pthread_detach() 或 pthread_attr_setdetachstate() 函数将其设置为分离模式。

    示例用法:

    1. #include
    2. void* thread_func(void* arg) {
    3. // 线程执行的代码
    4. return NULL;
    5. }
    6. int main() {
    7. pthread_t tid;
    8. // 创建线程
    9. if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {
    10. // 处理创建线程失败的情况
    11. return -1;
    12. }
    13. // 设置线程为分离模式
    14. if (pthread_detach(tid) != 0) {
    15. // 处理设置线程分离模式失败的情况
    16. return -1;
    17. }
    18. // 继续执行其他操作
    19. // ...
    20. return 0;
    21. }
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define BACKLOG 5
    11. void *ClinetHandle(void *arg);
    12. int main(int argc, char *argv[])
    13. {
    14. int fd, newfd;
    15. struct sockaddr_in addr, clint_addr;
    16. pthread_t tid;
    17. socklen_t addrlen = sizeof(clint_addr);
    18. if(argc < 3){
    19. fprintf(stderr, "%s\n", argv[0]);
    20. exit(0);
    21. }
    22. /*创建套接字*/
    23. fd = socket(AF_INET, SOCK_STREAM, 0);
    24. if(fd < 0){
    25. perror("socket");
    26. exit(0);
    27. }
    28. addr.sin_family = AF_INET;
    29. addr.sin_port = htons( atoi(argv[2]) );
    30. if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
    31. fprintf(stderr, "Invalid address\n");
    32. exit(EXIT_FAILURE);
    33. }
    34. /*地址快速重用*/
    35. int flag=1,len= sizeof (int);
    36. if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) {
    37. perror("setsockopt");
    38. exit(1);
    39. }
    40. /*绑定通信结构体*/
    41. if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
    42. perror("bind");
    43. exit(0);
    44. }
    45. /*设置套接字为监听模式*/
    46. if(listen(fd, BACKLOG) == -1){
    47. perror("listen");
    48. exit(0);
    49. }
    50. while(1){
    51. /*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
    52. newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
    53. if(newfd < 0){
    54. perror("accept");
    55. exit(0);
    56. }
    57. printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
    58. pthread_create(&tid, NULL, ClinetHandle, &newfd);
    59. pthread_detach(tid); //把线程属性设置为分离模式
    60. }
    61. close(fd);
    62. return 0;
    63. }
    64. void *ClinetHandle(void *arg){
    65. int ret;
    66. char buf[BUFSIZ] = {};
    67. int newfd = *(int *)arg;
    68. while(1){
    69. //memset(buf, 0, BUFSIZ);
    70. bzero(buf, BUFSIZ);
    71. ret = read(newfd, buf, BUFSIZ);
    72. if(ret < 0)
    73. {
    74. perror("read");
    75. exit(0);
    76. }
    77. else if(ret == 0)
    78. break;
    79. else
    80. printf("buf = %s\n", buf);
    81. }
    82. printf("client exited\n");
    83. close(newfd);
    84. return NULL;
    85. }

    makefile也需要修改

    1. CC=gcc
    2. CFLAGS=-Wall
    3. all:client server
    4. server:server.c
    5. $(CC) $^ -Wall -o $@ -lpthread
    6. clean:
    7. rm client server

    4 练习

    使用多线程实现TCP并发代码,并使用Makefile进行编译。提交代码和完成通信的截图

    tcp_server.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define CLIENT_MAX_NUM 5
    10. void * ClientHandle(void *arg);
    11. int main(int argc, char * argv[])
    12. {
    13. int sockfd, clientfd;
    14. struct sockaddr_in server_addr,client_addr;
    15. pthread_t tid;
    16. socklen_t addrlen = sizeof(client_addr);
    17. if( argc < 3)
    18. {
    19. printf("%s \n",argv[0]);
    20. return 0;
    21. }
    22. //1 创建socket
    23. sockfd = socket(AF_INET, SOCK_STREAM,0);
    24. if(sockfd == -1)
    25. {
    26. perror("socket");
    27. return 0;
    28. }
    29. server_addr.sin_family = AF_INET;
    30. server_addr.sin_port = htons( atoi(argv[2]) ) ;
    31. if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
    32. {
    33. printf("Invalid address:%s\n",argv[1]);
    34. return 0;
    35. }
    36. /*地址快速重用*/
    37. int flag = 1, len = sizeof(int);
    38. if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag,len) == -1)
    39. {
    40. perror("setsockopt");
    41. return 0;
    42. }
    43. //2 绑定bind
    44. if(bind(sockfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    45. {
    46. perror("bind");
    47. return 0;
    48. }
    49. //3 监听
    50. if(listen(sockfd, CLIENT_MAX_NUM) == -1)
    51. {
    52. perror("listen");
    53. return 0;
    54. }
    55. while(1)
    56. {
    57. //4 等待连接
    58. clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
    59. if( clientfd == -1)
    60. {
    61. perror("accept");
    62. return 0;
    63. }
    64. printf("addr:%s port:%d\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    65. pthread_create(&tid, NULL, ClientHandle, &clientfd);
    66. pthread_detach(tid); //线程属性设置为分离模式
    67. }
    68. close(sockfd);
    69. return 0;
    70. }
    71. void * ClientHandle(void *arg)
    72. {
    73. int ret;
    74. char buf[BUFSIZ] = {};
    75. int clientfd = *(int *)arg;
    76. while(1)
    77. {
    78. //bzero(buf, BUFSIZ);
    79. memset(buf, 0, BUFSIZ);
    80. ret = read(clientfd, buf, BUFSIZ);
    81. if(ret < 0)
    82. {
    83. perror("read");
    84. exit(0);
    85. }
    86. else if( ret == 0 )
    87. {
    88. break;
    89. }
    90. else
    91. {
    92. printf("buf = %s\n", buf);
    93. }
    94. }
    95. printf("client exited\n");
    96. close(clientfd);
    97. return NULL;
    98. }

    tcp_client.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define CLIENT_MAX_NUM 5
    9. int main(int argc, char * argv[])
    10. {
    11. int clientfd;
    12. struct sockaddr_in server_addr;
    13. char buf[BUFSIZ];
    14. if( argc < 3)
    15. {
    16. printf("%s \n",argv[0]);
    17. return 0;
    18. }
    19. clientfd = socket(AF_INET, SOCK_STREAM,0);
    20. if(clientfd == -1)
    21. {
    22. perror("socket");
    23. return 0;
    24. }
    25. server_addr.sin_family = AF_INET;
    26. server_addr.sin_port = htons( atoi(argv[2]) ) ;
    27. if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
    28. {
    29. printf("Invalid address:%s\n",argv[1]);
    30. return 0;
    31. }
    32. if(connect(clientfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    33. {
    34. perror("connect");
    35. return 0;
    36. }
    37. while(1)
    38. {
    39. printf(">");
    40. fgets(buf, BUFSIZ, stdin);
    41. write(clientfd, buf, strlen(buf));
    42. }
    43. close(clientfd);
    44. return 0;
    45. }

    makefile

    1. CC=gcc
    2. CFLAGS=-Wall
    3. all:tcp_client tcp_server
    4. tcp_server:tcp_server.c
    5. $(CC) tcp_server.c -Wall -o tcp_server -lpthread
    6. clean:
    7. rm tcp_server tcp_client

  • 相关阅读:
    Redis 定长队列的探索和实践
    Java中的map集合顺序如何与添加顺序一样,LinkedHashMap,使用entrySet遍历
    最小生成树——Prim算法与Kruskal算法
    【DL with Pytorch】第 4 章 : 卷积神经网络
    精心整理了超详细的Linux入门笔记,零基础也能看懂,一学就会
    【软件部署】Linux源码安装Jenkins
    【学习笔记】深度学习入门:基于Python的理论与实现-神经网络
    【一】曾经那些错误,你又踩坑了吗?
    金仓数据库KingbaseES安全指南--6.7. GSSAPI身份验证
    企业架构概述及业务架构详解
  • 原文地址:https://blog.csdn.net/m0_60718520/article/details/133563674