阅读量:0
目录
1、前言
前面实现了基本的TCP编程,Linux网络编程:TCP编程实现-CSDN博客,但是存在多个客户端去连接同一个服务器的情况,这时之前编写的基础TCP服务器连接一个客户端后就无法再与其他客户端建立连接,这是就需要考虑并发设计。
2、多进程代码实现
2.1 创建新的进程
若返回的pid小于0,则创建失败退出;
若返回的pid等于0,则为子进程,关闭服务器绑定socket文件描述符;
若返回的pid大于0,则为父进程,关闭客户端绑定socket文件描述符。
if((pid = fork())<0) { perror("accept"); exit(0); } else if(pid == 0) { close(fd); ClientHandle(newfd); exit(0); } else if(pid > 0) { close(newfd); }
2.2 客户端接收响应函数
每次创建子进程后进行客户端接收,读取客户端绑定socket文件描述符的buffer,随后进行关闭客户端绑定socket文件描述符。
void ClientHandle(int newfd) { int ret; char buf[BUFSIZ] = {};//BUFSIZ 8142 while(1) { memset(buf,0,BUFSIZ); ret = read(newfd,buf,BUFSIZ); if(ret < 0 ) { perror("read"); exit(0); } else if(ret == 0) break; printf("buf = %s\n",buf); } close(newfd); }
2.3 僵尸进程处理
当客户端与服务器连接后,终止客户端进程后,服务器的子进程会变成僵尸进程,所以要进行僵尸进程的回收。
子进程终止时会向父进程发送SIGCHLD信号,告知父进程回收自己,但该信号的默认处理动作为忽略,因此父进程仍然不会去回收子进程,需要捕捉处理实现子进程的回收;
进行信号机制的绑定,进行子进程终止信号的接收
signal(SIGCHLD,SigHandle);
实现僵尸进程接收函数
void SigHandle(int sig) { if(sig == SIGCHLD) { printf("Client exited\n"); wait(NULL); } }
2.4 完整代码
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <sys/wait.h> #define BACKLOG 5 void SigHandle(int sig) { if(sig == SIGCHLD) { printf("Client exited\n"); wait(NULL); } } void ClientHandle(int newfd); int main(int argc,char *argv[]) { int fd,newfd; struct sockaddr_in addr,client_addr; socklen_t addrlen = sizeof(client_addr); signal(SIGCHLD,SigHandle); pid_t pid; if(argc < 3) { printf("%s<addr><port>\n",argv[0]); exit(0); } /*创建套接字*/ fd = socket(AF_INET,SOCK_STREAM,0); if(fd < 0) { perror("socket"); exit(0); } addr.sin_family = AF_INET; addr.sin_port = htons(atoi(argv[2])); if(inet_aton(argv[1],&addr.sin_addr)==0) { fprintf(stderr,"Invalid address\n"); exit(0); } /*地址快速重用*/ int flag = 1,len = sizeof(int); if(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&flag,len)==-1) { perror("setsockopt"); exit(1); } /*绑定通信结构体*/ if(bind(fd,(struct sockaddr *)&addr,sizeof(addr)) == -1) { perror("bind"); exit(0); } /*设置套接字为侦听模式*/ if(listen(fd,BACKLOG) == -1) { perror("listen"); exit(0); } while(1) { /*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/ newfd = accept(fd,(struct sockaddr *)&client_addr,&addrlen); if(newfd < 0) { perror("accept"); exit(0); } printf("addr:%s port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); if((pid = fork())<0) { perror("accept"); exit(0); } else if(pid == 0) { close(fd); ClientHandle(newfd); exit(0); } else if(pid > 0) { close(newfd); } } close(fd); return 0; } void ClientHandle(int newfd) { int ret; char buf[BUFSIZ] = {};//BUFSIZ 8142 while(1) { memset(buf,0,BUFSIZ); ret = read(newfd,buf,BUFSIZ); if(ret < 0 ) { perror("read"); exit(0); } else if(ret == 0) break; printf("buf = %s\n",buf); } close(newfd); }
2.5 代码测试
3、多线程代码实现
3.1 创建新的线程
进行线程创建后进行线程分离
pthread_create(&tid,NULL,ClientHandle,&newfd); pthread_detach(tid);
3.2 线程函数定义
每次创建子进程后进行客户端接收,读取客户端绑定socket文件描述符的buffer,随后进行关闭客户端绑定socket文件描述符。
void *ClientHandle(void *arg) { int ret; char buf[BUFSIZ] = {};//BUFSIZ 8142 int newfd = *(int *)arg; while(1) { memset(buf,0,BUFSIZ); ret = read(newfd,buf,BUFSIZ); if(ret < 0 ) { perror("read"); exit(0); } else if(ret == 0) break; printf("buf = %s\n",buf); } printf("client exit\n"); close(newfd); return NULL; }
3.3 完整代码
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> #define BACKLOG 5 void *ClientHandle(void *arg); int main(int argc,char *argv[]) { int fd,newfd; struct sockaddr_in addr,client_addr; pthread_t tid; socklen_t addrlen = sizeof(client_addr); if(argc < 3) { printf("%s<addr><port>\n",argv[0]); exit(0); } /*创建套接字*/ fd = socket(AF_INET,SOCK_STREAM,0); if(fd < 0) { perror("socket"); exit(0); } addr.sin_family = AF_INET; addr.sin_port = htons(atoi(argv[2])); if(inet_aton(argv[1],&addr.sin_addr)==0) { fprintf(stderr,"Invalid address\n"); exit(0); } /*地址快速重用*/ int flag = 1,len = sizeof(int); if(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&flag,len)==-1) { perror("setsockopt"); exit(1); } /*绑定通信结构体*/ if(bind(fd,(struct sockaddr *)&addr,sizeof(addr)) == -1) { perror("bind"); exit(0); } /*设置套接字为侦听模式*/ if(listen(fd,BACKLOG) == -1) { perror("listen"); exit(0); } while(1) { /*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/ newfd = accept(fd,(struct sockaddr *)&client_addr,&addrlen); if(newfd < 0) { perror("accept"); exit(0); } printf("addr:%s port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); pthread_create(&tid,NULL,ClientHandle,&newfd); pthread_detach(tid); } close(fd); return 0; } void *ClientHandle(void *arg) { int ret; char buf[BUFSIZ] = {};//BUFSIZ 8142 int newfd = *(int *)arg; while(1) { memset(buf,0,BUFSIZ); ret = read(newfd,buf,BUFSIZ); if(ret < 0 ) { perror("read"); exit(0); } else if(ret == 0) break; printf("buf = %s\n",buf); } printf("client exit\n"); close(newfd); return NULL; }
3.4 代码测试
4、总结
本文通过多进程和多线程技术进行的TCP并发服务器的实现,在多进程方式下,解决了僵尸进程的问题。 最后通过完成代码的编写并测试,成功实现了TCP并发服务器。