博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux 并发服务器编程(多进程)
阅读量:3949 次
发布时间:2019-05-24

本文共 4554 字,大约阅读时间需要 15 分钟。

文章目录

说明

在Linux中通过流式套接字编程(TCP),实现一个并发服务器的访问回显,适合刚学完Linux套接字编程的朋友进行巩固训练

具体功能:

  • 服务器能够同时连接、处理多个客户端的信息
  • 客户端向服务器发送数据之后,服务器收到数据,然后反手发送给客户端
  • 服务器能够对客户端的退出做出反应,并在客户端退出连接的时候给出提示
  • 服务器能够识别每个客户端发送的信息,在显示的时候加上客户端的IP地址
  • 服务器中能够对已经退出的服务进程作回收处理
  • 客户端能够对服务器的退出作出反应,检测到服务器退出后客户端也退出

注意事项

  • 多进程并发服务器编程中,每次建立一个套接字连接,都会fork一个进程来处理
  • accept是自带阻塞的,所以fork返回父进程之后,父进程就会阻塞等待下一个已连接套接字
  • 客户端的关闭通过ctrl-c发出的信号(SIGINT)来终止客户端
  • 当客户端终止之后,服务器上对应的服务进程通过exit结束,此时由于服务器的主进程还阻塞在accept中,所以无法及时回收子服务进程,所以通过注册一个信号SIGCHLD处理函数,在信号处理的时候回收僵尸子进程。SIGCHLD是子进程结束的时候发送给父进程的信号,默认忽略。
  • 服务器进程如何检测客户端退出呢?通过recv()函数,当返回值为0的时候,表示客户端已经关闭套接字,即客户端退出。
  • 当服务线程主动关闭的时候,客户端也会通过recv()收到服务器关闭的信息,然后客户端主动退出
  • 关于套接字描述符,因为描述符也算是进程的资源,当套接字描述符的引用值为0的时候,才会关闭套接字,或者是进程退出的时候释放套接字描述符资源
  • 每次fork的时候,都会产生一个对于已经打开的套接字描述符的引用,所以要在进入子服务进程后关闭监听套接字描述符、在主服务进程中关闭已连接套接字描述符、在子服务进程退出的时候关闭已连接套接字描述符、在退出主服务器进程的时候关闭监听套接字描述符,这样才做到了有始有终(fork之后已连接套接字描述符的引用就有两份)在这里插入图片描述

server.c

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_ADDR "172.17.44.154"#define BUFSIZE 100void sigchld_handler(int arg);int main(int argc, const char *argv[]){ int socket_fd, new_fd; struct sockaddr_in server_addr, cli_addr; char buf[BUFSIZE]; int pid; struct sigaction sig; /* 注册中断信号处理函数 */ sig.sa_handler = sigchld_handler; sigaction(SIGCHLD, &sig, NULL); /* 创建套接字,并获取套接字描述符 */ socket_fd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == socket_fd) { perror("socket"); exit(-1); } /* 绑定地址 */ server_addr.sin_family = AF_INET; server_addr.sin_port = htons(5001); inet_pton(AF_INET, SERVER_ADDR, (void*)&server_addr.sin_addr.s_addr); //地址转换 if (-1 == bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))) { perror("bind"); exit(-1); } /* 转换为被动连接套接字 */ if (-1 == listen(socket_fd, 5)) { perror("listen"); exit(-1); }#if 0 //单进程服务器 /* 获取已连接套接字 */ socklen_t len = 0; new_fd = accept(socket_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len); if (-1 == new_fd) { perror("accept"); exit(-1); } printf("accept socket!\nclient ip :%s port:%d\n", inet_ntoa(cli_addr.sin_addr), cli_addr.sin_port); while (1) { memset(buf, 0, BUFSIZE); if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //接受数据 printf("the client is closed\n"); break; } printf("read:%s\n", buf); send(new_fd, buf, BUFSIZE, 0); //回应客户端 } close(new_fd);#else //多进程并发服务器 while (1) { socklen_t len = 0; new_fd = accept(socket_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len); if (-1 == new_fd) { if (errno == EINTR) continue; //accept可能会被信号中断 perror("accept"); exit(-1); } /* 并发服务器:子进程中进行TCP通信 */ pid = fork(); if (pid == -1) { perror("fork"); exit(0); } else if (pid == 0) { close(socket_fd); //关闭监听套接字 while (1) { memset(buf, 0, BUFSIZE); if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //接受数据,只有当客户端主动关闭的时候,才退出线程,还要对关闭之后的子进程 printf("the client socket %s is closed\n", inet_ntoa((struct in_addr)cli_addr.sin_addr)); close(new_fd); //退出之前记得关闭 已连接套接字 exit(0); //通过信号处理函数进行回收 } getsockname(new_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len); //获取连接套接字信息 printf("recv client IP:%s data:%s\n", inet_ntoa((struct in_addr)cli_addr.sin_addr), buf); send(new_fd, buf, BUFSIZE, 0); //回应客户端 } } else { close(new_fd); //父进程关闭 已连接套接字 } } #endif close(socket_fd); return 0;}void sigchld_handler(int arg){ int child_pid; if (SIGCHLD == arg) { if ((child_pid = waitpid(-1, NULL, WNOHANG)) == -1) { perror("sigchld"); } printf("a client %d is end\n", child_pid); }}

client.c

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_ADDR "172.17.44.154"#define BUFSIZE 100int main(int argc, const char *argv[]){ int new_fd; struct sockaddr_in server_addr; char buf[BUFSIZE]; /* 创建套接字,并获取套接字描述符 */ new_fd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == new_fd) { perror("socket"); exit(-1); } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(5001); inet_pton(AF_INET, SERVER_ADDR, (void*)&server_addr.sin_addr.s_addr); if (-1 == connect(new_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))) { perror("connect"); exit(-1); } while (1) { printf("input:"); fgets(buf, BUFSIZE, stdin); //获取数据 if (-1 == send(new_fd, buf, BUFSIZE, 0)) { //发送数据 perror("send"); close(new_fd); exit(-1); } if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //收到数据 printf("server closed\n"); break; } printf("recv:%s\n", buf); } close(new_fd); return 0;}

运行截图

PS:这里是在同一主机下做实验的,所以各个客户端的IP地址都是一样的

正常运行的状态如下:
在这里插入图片描述

当有一个客户端退出时,服务器会显示信息,但是对其他客户端的服务正常进行:

在这里插入图片描述
当服务器主动关闭之后,所有客户端都会收到服务器关闭的信息,并且主动退出:
在这里插入图片描述

转载地址:http://wxwzi.baihongyu.com/

你可能感兴趣的文章
Sliding Window(POJ-2823)
查看>>
A. Greed CodeForces - 892A
查看>>
最短路 HDU - 2544
查看>>
7-12 列车厢调度(25 分)
查看>>
7-5 表达式转换(25 分)
查看>>
一个人的旅行 HDU - 2066
查看>>
浪里个浪 FZU - 2261 (多源最短路问题)
查看>>
D - Sorting It All Out POJ - 1094 (拓扑排序)
查看>>
Reward HDU - 2647 (拓扑排序)
查看>>
Divide by three, multiply by two CodeForces - 977D (拓扑排序)
查看>>
Big Event in HDU HDU - 1171 (多重背包)
查看>>
最长子序列长度 (动态规划 O(N^2))
查看>>
最长子序列长度 (贪心+二分 O( Nlog(N) ))
查看>>
数塔 HDU - 2084 (简单的dp)
查看>>
超级楼梯 HDU - 2041 ( 简单的dp )
查看>>
Piggy-Bank HDU - 1114 ( 完全背包 )
查看>>
Knapsack problem FZU - 2214 ( 01背包 )
查看>>
Bone Collector HDU - 2602 ( 01背包 )
查看>>
背包问题 V2 51Nod - 1806 ( 多重背包 )
查看>>
最少拦截系统 HDU - 1257 ( 动态规划 )
查看>>