标签:
进程和线程的使用在前面博文已经讲述完毕,在完成一个最简单的服务器之后,就是要考虑下如何实现并发服务器了。
要实现服务的并发,只能通过进程和线程两种方式。
之前提到过listen_fd和connect_fd,listen用于监听是否有客户端连接,维护两个fd队列,没完成握手的和完成就绪的。
connect从就绪队列取描述符,这个connect_fd描述符将用于数据通信,所以要实现并发,就是将connect_fd分发到线程或进程上,由他们去独立完成通信。
在实际并发服务器应用场合,在IO层大多通过两个地方来提高代码效率,一个是描述符处理,一个是线程/进程调度处理。
下图简单描述了并发服务器的原理:
在处理IO时,会用到IO复用技术提高效率,在线程/进程分配时,会先构造线程池或进程池,并以某种方式调度,这些在后续博文详细描述。
下面是并发实现的简单代码,利用线程和进程实现服务器的并发。
进程实现:
1 /* File Name: server.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <unistd.h> 6 #include <sys/wait.h> 7 #include <errno.h> 8 #include <sys/types.h> 9 #include <sys/socket.h> 10 #include <sys/unistd.h> 11 #include <netinet/in.h> 12 13 const int DEFAULT_PORT = 2500; 14 const int BUFFSIZE = 1024; 15 const int MAXLINK = 10; 16 17 class sigOp 18 { 19 public: 20 void addSigProcess(int sig,void (*func)(int)); 21 void sendSig(const int sig, const int pid); 22 }; 23 24 void sigOp::addSigProcess(int sig,void (*func)(int)) 25 { 26 struct sigaction stuSig; 27 memset(&stuSig, ‘\0‘, sizeof(stuSig)); 28 stuSig.sa_handler = func; 29 stuSig.sa_flags |= SA_RESTART; 30 sigfillset(&stuSig.sa_mask); 31 sigaction(sig, &stuSig, NULL); 32 } 33 34 void sigOp::sendSig(const int sig, const int pid) 35 { 36 kill(pid, sig); 37 } 38 39 void waitchlid(int sig) 40 { 41 pid_t pid; 42 int stat; 43 while((pid = waitpid(-1, &stat, WNOHANG)) > 0); 44 } 45 46 int main(int argc, char** argv) 47 { 48 int socket_fd, connect_fd; 49 struct sockaddr_in servaddr; 50 char buff[BUFFSIZE]; 51 52 if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 53 { 54 printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); 55 exit(0); 56 } 57 58 memset(&servaddr, 0, sizeof(servaddr)); 59 servaddr.sin_family = AF_INET; 60 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 61 servaddr.sin_port = htons(DEFAULT_PORT); 62 63 if (bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) 64 { 65 printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); 66 exit(0); 67 } 68 69 if (listen(socket_fd, MAXLINK) == -1) 70 { 71 exit(0); 72 } 73 74 sigOp sig; 75 sig.addSigProcess(SIGCHLD, waitchlid);//回收进程 76 77 while(1) 78 { 79 if ((connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1) 80 { 81 break; 82 } 83 else 84 { 85 if (0 == fork()) 86 { 87 close(socket_fd); //关闭监听描述符 88 while(1) 89 { 90 memset(&buff,‘\0‘, sizeof(buff)); 91 recv(connect_fd, buff, BUFFSIZE - 1, 0); 92 send(connect_fd, buff, BUFFSIZE - 1, 0); 93 printf("recv msg from client: %s\n", buff); 94 } 95 close(connect_fd); 96 exit(0); 97 } 98 close(connect_fd);//父进程关闭连接描述符 99 } 100 } 101 close(connect_fd); 102 close(socket_fd); 103 }之前提到过listen描述符和connect描述符是完全独立的,关闭都不会影响另一个描述符工作。所以在代码中,父子进程都会关闭不需要的描述符。
测试结果如下:
ps -aux查看系统进程,可以看到三个进程,一个是主进程用于listen监听,两个子进程进行通信。
下面是线程并发实现:
1 /* File Name: server.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <unistd.h> 6 #include <pthread.h> 7 #include <errno.h> 8 #include <sys/types.h> 9 #include <sys/socket.h> 10 #include <sys/unistd.h> 11 #include <netinet/in.h> 12 13 const int DEFAULT_PORT = 2500; 14 const int BUFFSIZE = 1024; 15 const int MAXLINK = 10; 16 17 void* run(void* pConnect_fd) 18 { 19 char buff[BUFFSIZE]; 20 int *connect_fd = reinterpret_cast<int *>(pConnect_fd); 21 pthread_detach(pthread_self()); 22 while(1) 23 { 24 memset(&buff,‘\0‘, sizeof(buff)); 25 recv(*connect_fd, buff, BUFFSIZE - 1, 0); 26 send(*connect_fd, buff, BUFFSIZE - 1, 0); 27 printf("recv msg from client: %s\n", buff); 28 } 29 } 30 31 int main(int argc, char** argv) 32 { 33 int socket_fd, connect_fd; 34 struct sockaddr_in servaddr; 35 36 if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 37 { 38 printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); 39 exit(0); 40 } 41 42 memset(&servaddr, 0, sizeof(servaddr)); 43 servaddr.sin_family = AF_INET; 44 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 45 servaddr.sin_port = htons(DEFAULT_PORT); 46 47 if (bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) 48 { 49 printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); 50 exit(0); 51 } 52 53 if (listen(socket_fd, MAXLINK) == -1) 54 { 55 exit(0); 56 } 57 58 while(1) 59 { 60 if ((connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1) 61 { 62 break; 63 } 64 else //accept获取到描述符创建线程 65 { 66 pthread_t _Tread; 67 pthread_create(&_Tread, NULL, run, &connect_fd);//传参为连接描述符 68 } 69 } 70 close(connect_fd); 71 close(socket_fd); 72 }测试结果如下:
效果和进程一样,执行netstat查看tcp状态
两组连接相互通信。
线程并发和进程并发各有优劣,目前大多服务器还是用线程进行并发的,进程要对父进程进行拷贝,资源消耗大,但相互直接资源互不影响,线程效率高但是要注意锁的使用,一个线程可能会影响整个服务器的运行。
详细优缺点详细可参考:http://blog.chinaunix.net/uid-20556054-id-3067672.html
标签:
原文地址:http://www.cnblogs.com/binchen-china/p/5487307.html