1、第5章 并发服务器目录n服务器分类n进程与线程n多进程服务器n多线程服务器并发服务器服务器分类n按连接类型分类n面向连接的服务器(如tcp)n面向无连接的服务器(如udp)n按处理方式分类n迭代服务器n并发服务器迭代服务器 vs.并发服务器绑定地址监听连接接收连接处理连接断开连接接收请求处理请求返回响应绑定地址监听连接接收连接创建子进程关闭连接套接字处理连接关闭连接套接字终止子进程关闭监听套接字服务器主进程服务器子进程TCP迭代服务器TCP并发服务器“进程”基本概念n进程定义了一个计算的基本单元,可以认为是一个程序的一次运行。它是一个动态实体,是独立的任务。它拥有独立的地址空间、执行堆栈、文件
2、描述符等。n每个进程拥有独立的地址空间,进程间正常情况下,互不影响,一个进程的崩溃不会造成其他进程的崩溃。n当进程间共享某一资源时,需注意两个问题:同步问题和通信问题。创建进程#include#include pid_t fork(void)返回:父进程中返回子进程的进程返回:父进程中返回子进程的进程ID,子进程返回子进程返回0,-1出错出错nfork后,子进程和父进程继续执行后,子进程和父进程继续执行fork()函数后的指令。()函数后的指令。子进程是父进程的副本。子进程拥有父进程的数据空间、子进程是父进程的副本。子进程拥有父进程的数据空间、堆栈的副本。但父、子进程并不共享这些存储空间部分。
3、堆栈的副本。但父、子进程并不共享这些存储空间部分。如果代码段是只读的,则父子进程共享代码段。如果父子如果代码段是只读的,则父子进程共享代码段。如果父子进程同时对同一文件描述字操作,而又没有任何形式的同进程同时对同一文件描述字操作,而又没有任何形式的同步,则会出现混乱的状况;步,则会出现混乱的状况;n父进程中调用父进程中调用fork之前打开的所有描述字在函数之前打开的所有描述字在函数fork返回返回之后子进程会得到一个副本。之后子进程会得到一个副本。fork后,父子进程均需要将后,父子进程均需要将自己不使用的描述字关闭,有两方面的原因:(自己不使用的描述字关闭,有两方面的原因:(1)以免)以免出
4、现不同步的情况;(出现不同步的情况;(2)最后能正常关闭描述字最后能正常关闭描述字#include#includemain()int i,sum;sum=0;for(i=0;i 0)printf(parent running.n);printf(parent exitn);exit(0);else printf(fork error.n);exit(1);创建进程(cont.)#include#include pid_t vfork(void);n在在BSD3.0中开始出现,主要为了解决中开始出现,主要为了解决fork昂贵的开销。它昂贵的开销。它是完全共享的创建,新老进程共享同样的资源,完全没
5、有拷是完全共享的创建,新老进程共享同样的资源,完全没有拷贝。贝。n两者的基本区别在于当使用两者的基本区别在于当使用vfork()创建新进程时,父进程创建新进程时,父进程将被暂时阻塞,而子进程则可以借用父进程的地址空间。这将被暂时阻塞,而子进程则可以借用父进程的地址空间。这个奇特状态将持续直到子进程退出或调用个奇特状态将持续直到子进程退出或调用execve()函数,函数,至此父进程才继续执行。至此父进程才继续执行。运行的结果:终止进程终止进程n进程的终止存在两个可能:进程的终止存在两个可能:n父进程先于子进程终止(父进程先于子进程终止(init进程领养)进程领养)n子进程先于主进程终止子进程先于
6、主进程终止n对于后者,系统内核为子进程保留一定的状态对于后者,系统内核为子进程保留一定的状态信息:进程信息:进程ID、终止状态终止状态、CPU时间等;当时间等;当父进程调用父进程调用wait或或waitpid函数时,获取这些函数时,获取这些信息;(什么叫信息;(什么叫“僵尸进程僵尸进程”?)?)n当子进程正常或异常终止时,系统内核向其父当子进程正常或异常终止时,系统内核向其父进程发送进程发送SIGCHLD信号;缺省情况下,父进信号;缺省情况下,父进程忽略该信号,或者提供一个该信号发生时即程忽略该信号,或者提供一个该信号发生时即被调用的函数。被调用的函数。终止进程(续)终止进程(续)#inclu
7、de void exit(int status);n本函数终止调用进程。关闭所有子进程打开的描述本函数终止调用进程。关闭所有子进程打开的描述符,向父进程发送符,向父进程发送SIGCHLD信号,并返回状态。信号,并返回状态。获取子进程终止信息获取子进程终止信息#include#include pid_t wait(int*stat_loc);返回:终止子进程的返回:终止子进程的ID成功;成功;-1出错;出错;stat_loc存储存储子进程的终止状态(一个整数);子进程的终止状态(一个整数);n如果没有终止的子进程,但是有一个或多个正在执如果没有终止的子进程,但是有一个或多个正在执行的子进程,则该
8、函数将堵塞,直到有一个子进程行的子进程,则该函数将堵塞,直到有一个子进程终止或者终止或者wait被信号中断时,被信号中断时,wait返回。返回。n当调用该系统调用时,如果有一个子进程已经终止,当调用该系统调用时,如果有一个子进程已经终止,则该系统调用立即返回,并释放子进程所有资源。则该系统调用立即返回,并释放子进程所有资源。获取子进程终止信息获取子进程终止信息在SIGCHLD信号处理函数中使用wait()函数可能会出现一个问题SIGCHLD服务器父进程服务器父进程服务器子进程服务器子进程服务器子进程服务器子进程服务器子进程服务器子进程客户客户FINFINFINSIGCHLDSIGCHLD由于由
9、于linux信号不排队,在信号不排队,在SIGCHLD信号同时到来后,信信号同时到来后,信号处理程序中调用了号处理程序中调用了wait函数,其只执行一次,这样将留函数,其只执行一次,这样将留下下2个僵尸进程。可以使用个僵尸进程。可以使用waitpid函数解决这个问题。函数解决这个问题。获取子进程终止信息获取子进程终止信息pid_t waitpid(pid_t pid,int*stat_loc,int options);返回:终止子进程的返回:终止子进程的ID成功;成功;-1出错;出错;stat_loc存储存储子进程的终止状态;子进程的终止状态;n当当pid=-1,option=0时,该函数等同
10、于时,该函数等同于wait,否则,否则由参数由参数pid和和option共同决定函数行为,其中共同决定函数行为,其中pid参参数意义如下:数意义如下:n-1:要求知道任何一个子进程的返回状态(等待第一:要求知道任何一个子进程的返回状态(等待第一个终止的子进程);个终止的子进程);n0:要求知道进程号为:要求知道进程号为pid的子进程的状态;的子进程的状态;n0)printf(“child%d terminatedn”,pid);waitpid函数用法pid_t pid;if(pid=fork()0)/*parent process*/int child_status;waitpid(pid,&
11、child_status,0);else if(pid=0)/*child process*/exit(0);else /*fork error*/printf(“fork error.n”);exit(1);程序例子:程序例子:#include#include#include int main(void)pid_t pid;int status;if(pid=fork()=0)printf(child running.n);sleep(1);printf(child sleeping.n);printf(child dead.n);exit(0);else if(pid 0)printf(p
12、arent running.n);waitpid(pid,&status,0);printf(parent is runningn);printf(parent exitn);else printf(fork error.n);exit(1);多进程并发服务器状态图服务器客户connect()函数listenfd客户/服务器状态图(调用accept函数时)连接请求多进程并发服务器状态图(cont.)服务器服务器客户客户connect()函数listenfd客户/服务器状态图(调用accept函数后)connfd连接建立多进程并发服务器状态图(cont.)服务器(父进程)服务器(父进程)客户客户
13、connect()函数函数listenfd客户/服务器状态图(调用fork函数后)connfd连接建立连接建立服务器(子进程)listenfdconnfdfork()函数多进程并发服务器状态图(cont.)服务器(父进程)服务器(父进程)客户客户connect()函数函数listenfd客户/服务器状态图(父进程关闭连接套接字,子进程关闭监听套接字)连接建立服务器(子进程)服务器(子进程)connfd多进程并发服务器模板int main(void)int listenfd,connfd;pid_t pid;intBACKLOG=5;if(listenfd=socket(AF_INET,SOCK
14、_STREAM,0)=-1)perror(“Create socket failed.”);exit(1);bind(listenfd,);listen(listenfd,BACKLOG);while(1)if(connfd=accept(sockfd,NULL,NULL)=-1)perror(“Accept error.”);exit(1);多进程并发服务器模板if(pid=fork()0)/*parent process*/close(connfd);.continue;else if(pid=0)/*child process*/close(lisetenfd);.exit(0);els
15、e printf(“fork errorn”);exit(1);一点说明n从以上模板看出,产生新的子进程后,父进程要关闭连接套接字,而子进程要关闭监听套接字,主要原因是:n关闭不需要的套接字可节省系统资源,同时可避免父子进程共享这些套接字可能带来的不可预计的后果;n另一个更重要的原因,是为了正确地关闭连接。和文件描述符一样,每个套接字描述符都有一个“引用计数”。当fork函数返回后,listenfd和connfd的引用计数变为2,而系统只有在某描述符的“引用计数”为0时,才真正关闭该描述符。多进程并发服务器实例n该实例包括服务器程序和客户程序,具体功能如该实例包括服务器程序和客户程序,具体功能
16、如下:下:n服务器等待客户连接请求,连接成功后显示客户地址,服务器等待客户连接请求,连接成功后显示客户地址,并接收该客户的名字并显示,然后接收来自客户的信并接收该客户的名字并显示,然后接收来自客户的信息(字符串)并显示,然后息(字符串)并显示,然后将该字符串反转,将该字符串反转,并将结并将结果送回客户,之后,继续等待接收该客户的信息直至果送回客户,之后,继续等待接收该客户的信息直至该客户关闭连接。要求服务器具有同时处理多个客户该客户关闭连接。要求服务器具有同时处理多个客户的能力。的能力。n客户首先与服务器连接,接着接收用户输入客户的名客户首先与服务器连接,接着接收用户输入客户的名字,并将该名字
17、发送给服务器,接收用户输入的字符字,并将该名字发送给服务器,接收用户输入的字符串,发送给服务器,接收服务器返回的经处理后的字串,发送给服务器,接收服务器返回的经处理后的字符串,并显示之。之后,继续等待用户输入直至用户符串,并显示之。之后,继续等待用户输入直至用户输入输入Ctrl+C,终止连接并退出。,终止连接并退出。多进程并发服务器服务器#include#define PORT1234#define BACKLOG10#define MAXDATASIZE1000void process_cli(int connectfd,struct sockaddr_in client);int main
18、(void)int listenfd,connectfd;pid_tpid;struct sockaddr_intserver,client;int sin_size;/*Create TCP Socket*/if(listenfd=socket(AF_INET,SOCK_STREAM,0)=-1)perror(Create socket failed);exit(-1);int opt=SO_REUSEADDR;setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt);bzero(&server,sizeof(server);s
19、erver.sin_family=AF_INET;server.sin_port=htons(PORT);server.sin_addr.s_addr=htonl(INADDR_ANY);if(bind(listenfd,(struct sockaddr*)&server,serrver)=-1)perror(Bind error);exit(-1);if(listen(listenfd,BACKLOG)=-1)perror(listen error);exit(-1);sin_size=sizeof(struct sockaddr_in);while(1)if(connectfd=accep
20、t(listenfd,(struct sockaddr*)&client,&sin_size)=-1)perror(accept error);exit(-1);if(pid=fork()0)/*parent process*/close(connectfd);continue;else if(pid=0)/*child process*/close(listenfd);process_cli(connectfd,client);exit(0);else printf(fork errorn);exit(0);/*while()*/close(listenfd);/*close listenf
21、d*/多进程并发服务器服务器void process_cli(int connectfd,struct sockaddr_in client)int num;char recvbufMAXDATASIZE,sendbufMAXDATASIZE,cli_nameMAXDATASIZE;printf(“You got a connection from%s.n”,inet_ntoa(client.sin_addr);num=recv(connectfd,cli_name,MAXDATASIZE,0);if(num=0)close(connectfd);printf(“cllient disconn
22、ected.n”);return;cli_namenum=0;printf(“Client name is%s.n”,cli_name);:多进程并发服务器服务器while(num=recv(connectfd,recvbuf,MAXDATASIZE,0)recvbufnum=0;printf(“Received client(%s)message:%sn”,cli_name,recvbuf);for(int i=0;i num;i+)sendbufi=recvbufnum-i-1;sendbufi=0;send(connectfd,sendbuf,strlen(sendbuf),0);clo
23、se(connectfd);多进程并发服务器-运行结果rootsheng sheng#./multiproserverThe server is runningYou got a connection from 222.18.113.153.Client name is shengReceived client(sheng)message:how a yYou got a connection from 127.0.0.1.Client name is limingReceived client(liming)message:abcdReceived client(sheng)message:
24、12345n可以看出,服务器能同时为多个客户服务。可以看出,服务器能同时为多个客户服务。(可以思考如果采用迭代服务器,结果如何?)(可以思考如果采用迭代服务器,结果如何?)多个客户同时请求处理多进程服务器的问题多进程服务器的问题n传统的网络服务器程序大都在新的连接到达时,传统的网络服务器程序大都在新的连接到达时,fork一个子进程来处理。虽然这种模式很多年使用一个子进程来处理。虽然这种模式很多年使用得很好,但得很好,但fork有一些问题:有一些问题:nfork是昂贵的。是昂贵的。fork时需要复制父进程的所有资源,包时需要复制父进程的所有资源,包括内存映象、描述字等;目前的实现使用了一种写时拷
25、括内存映象、描述字等;目前的实现使用了一种写时拷贝(贝(copy-on-write)技术,可有效避免昂贵的复制问)技术,可有效避免昂贵的复制问题,但题,但fork仍然是昂贵的仍然是昂贵的;nfork子进程后,父子进程间、兄弟进程间的通信需要进子进程后,父子进程间、兄弟进程间的通信需要进程间通信程间通信(IPC)机制,给通信带来了困难;机制,给通信带来了困难;n多进程在一定程度上仍然不能有效地利用系统资源多进程在一定程度上仍然不能有效地利用系统资源;n系统中进程个数也有限制。系统中进程个数也有限制。“线程线程”基本概念基本概念n线程是进程内的独立执行实体和调度单元,又称为线程是进程内的独立执行实
26、体和调度单元,又称为“轻量级轻量级”进程(进程(lightwight process);创建线程比进程快);创建线程比进程快10100倍。一个进程内的所有线程共享相同的内存空间、倍。一个进程内的所有线程共享相同的内存空间、全局变量等信息(这种机制又带来了同步问题)。而且它们全局变量等信息(这种机制又带来了同步问题)。而且它们还共享以下信息:还共享以下信息:共享信息共享信息 私有信息私有信息n进程指令进程指令 线程线程IDn大多数数据大多数数据 寄存器集合(包括程序计数器和栈指针)寄存器集合(包括程序计数器和栈指针)n打开的文件描述字打开的文件描述字栈(用于存放局部变量等)栈(用于存放局部变量等
27、)n信号处理程序和信号处置信号处理程序和信号处置errnon当前工作目录当前工作目录信号掩码信号掩码n用户用户ID和组和组ID优先级优先级线程调用函数(1)#include int pthread_create(pthread_t*tid,const pthread_attr_t*attr,void*(*func)(void*),void*arg);返回:成功时为返回:成功时为0;出错时为正的;出错时为正的Exxx值值n当一个程序开始运行时,系统会创建一个初始线程或主线程当一个程序开始运行时,系统会创建一个初始线程或主线程的单个线程。额外线程由上述函数创建;的单个线程。额外线程由上述函数创建;
28、n新线程由线程新线程由线程id标识:标识:tid,新线程的属性,新线程的属性attr包括:优先级、包括:优先级、初始栈大小、是否应该是守护线程等等。线程的执行函数和初始栈大小、是否应该是守护线程等等。线程的执行函数和调用参数分别是:调用参数分别是:func和和arg;n由于线程的执行函数的参数和返回值类型均为由于线程的执行函数的参数和返回值类型均为void*,因此,因此可传递和返回指向任何类型的指针;可传递和返回指向任何类型的指针;n常见的返回错误值:常见的返回错误值:EAGAIN:超过了系统线程数目的限制。:超过了系统线程数目的限制。ENOMEN:没有足够的内存产生新的线程。:没有足够的内存
29、产生新的线程。EINVAL:无效的属性:无效的属性attr值。值。例:创建一个线程#include#include pthread_t tid;void*ex()printf(this is a thread);main()pthread_create(&tid,NULL,ex,NULL);线程函数调用(线程函数调用(2)#inlcude int pthread_join(pthread_t tid,void*status);返回:成功时为返回:成功时为0;出错时为正的;出错时为正的Exxx值,不值,不设置设置errorn该函数类似与该函数类似与waitpid函数,但必须指定等函数,但必须指定
30、等待线程的待线程的ID,该函数不能等待任意一个线程,该函数不能等待任意一个线程结束;结束;n被等待线程必须是当前进程的成员,并且不被等待线程必须是当前进程的成员,并且不是是分离的线程分离的线程和和守护线程守护线程。pthread_t pthread_self(void);返回:调用线程的线程返回:调用线程的线程id;线程函数调用(3)#include int pthread_detach(pthread_t tid)返回:成功时为返回:成功时为0;出错时为正;出错时为正Exxx值;值;n线程或者是可汇合的(线程或者是可汇合的(joinable)(默认),或者)(默认),或者是脱离的(是脱离的(
31、detached)。当可汇合的线程终止时,)。当可汇合的线程终止时,其线程其线程id和退出状态将保留,直到另外一个线程调用和退出状态将保留,直到另外一个线程调用pthread_join。脱离的线程则像守护进程,当它终。脱离的线程则像守护进程,当它终止时,释放所有资源,我们不能等待它终止。止时,释放所有资源,我们不能等待它终止。n该函数将指定的线程变为脱离的。该函数将指定的线程变为脱离的。pthread_detach(pthread_self()();线程函数调用(线程函数调用(4)#include void pthread_exit(void*status);无返回值;无返回值;n如果线程为可
32、汇合的,将保留线程如果线程为可汇合的,将保留线程id和退出状态供和退出状态供pthread_join()函数调用()函数调用;n指针指针status:指向线程的退出状态。不能指向一个局部:指向线程的退出状态。不能指向一个局部变量,因为线程终止时其所有的局部变量将被撤销;变量,因为线程终止时其所有的局部变量将被撤销;n还有其他两种方法可使线程终止还有其他两种方法可使线程终止n启动线程的函数(启动线程的函数(pthread_create的第的第3个参数)个参数)返回。其返回值便是线程的终止状态;返回。其返回值便是线程的终止状态;n如果进程的如果进程的main函数返回,或者当前进程中,任一函数返回,
33、或者当前进程中,任一线程调用了线程调用了exit()函数,将终止该进程中所有线()函数,将终止该进程中所有线程。程。线程函数调用(线程函数调用(5)include int pthread_once(pthread_once_t*once_control,void(*init_routine)(void)成功返回成功返回0,否则返回错,否则返回错误码误码 n如果本函数中,如果本函数中,once_control变量使用的初值为变量使用的初值为PTHREAD_ONCE_INIT,可保证,可保证init_routine()函数在函数在执行序列中仅执行一次。执行序列中仅执行一次。n一般在一般在init_
34、routine函数中完成一些初始化工作。函数中完成一些初始化工作。nLinuxThreads使用互斥锁和条件变量保证由使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,指定的函数执行且仅执行一次,而而once_control则表征是否执行过。则表征是否执行过。pthread_once函数例子函数例子#include#include pthread_once_t once=PTHREAD_ONCE_INIT;void once_run(void)printf(once_run in thread%dn,pthread_self();void*child1(void
35、*arg)int tid=pthread_self();printf(thread%d entern,tid);pthread_once(&once,once_run);printf(thread%d returnsn,tid);pthread_exit(NULL);void*child2(void*arg)int tid=pthread_self();printf(thread%d entern,tid);pthread_once(&once,once_run);printf(thread%d returnsn,tid);pthread_exit(NULL);main(void)int ti
36、d1,tid2;printf(“hellon”);pthread_create(&tid1,NULL,child1,NULL);pthread_create(&tid2,NULL,child2,NULL);sleep(10);printf(“main thread exit”);线程函数调用(线程函数调用(6)int pthread_cancel(pthread_t tid);返回值:成功时为0,失败为非0;线程可以利用“取消机制”来终止另外一个线程;线程通过向另外一个线程发送取消请求,接收到取消请求的线程根据其设置状态,作出:1)忽略该请求;2)立即终止自己;3)延迟一段时间终止自己;线程的
37、例子线程的例子1/*thread1.c*/#include#include#include void*thread_function(void*arg)int i;for(i=0;i5;i+)printf(Thread says hi!n);sleep(1);return NULL;int main(void)pthread_t mythread;if(pthread_create(&mythread,NULL,thread_function,NULL)printf(error creating thread.);exit(1);if(pthread_join(mythread,NULL)pr
38、intf(error joining thread.);exit(1);exit(0);/*gcc thread1.c-o thread1-lpthread*/线程的例子2/*thread2.c*/#include#include int myglobal=0;void*thread_function(void*arg)int i,j;for(i=0;i5;i+)j=myglobal;j=j+1;printf(.);fflush(stdout);sleep(1);myglobal=j;return NULL;int main(void)pthread_t mythread;int i;if(p
39、thread_create(&mythread,NULL,thread_function,NULL)printf(error creating thread.);exit(1);for(i=0;i5;i+)myglobal=myglobal+1;printf(o);fflush(stdout);sleep(1);if(pthread_join(mythread,NULL)printf(error joining thread.);exit(1);printf(nmyglobal equals%dn,myglobal);exit(0);主线程和新线程都将myglobal加1 5次。结果应是10,
40、为什么是5?答案:新线程覆盖了主线程所做的修改。互斥锁 在linux系统中,提供一种基本的线程同步机制互斥锁,可以用来保护线程代码中共享数据的完整性。n操作系统将保证同时只有一个线程能成功操作系统将保证同时只有一个线程能成功完成对一个互斥锁的加锁操作。完成对一个互斥锁的加锁操作。n如果一个线程已经对某一互斥锁进行了加如果一个线程已经对某一互斥锁进行了加锁,其他线程只有等待该线程完成对这一锁,其他线程只有等待该线程完成对这一互斥锁解锁后,才能完成加锁操作。互斥锁解锁后,才能完成加锁操作。互斥锁函数pthread_mutex_lock(pthread_mutex_t *mptr)返回:成功0,否则
41、返回错误码nmptr:指向互斥锁的指针。n该函数接受一个指向互斥锁的指针作为参数并将其锁定。如果互斥锁已经被锁定,调用者将进入睡眠状态。函数返回时,将唤醒调用者。n如果互斥锁是静态分配的,就将它初始化为常值PTHREAD_MUTEX_INITIALIZER。pthread_mutex_unlock(pthread_mutex_t *mptr)用于互斥锁解锁操作。返回:成功0,否则返回错误码改进的程序#include#include#include#include int myglobal;pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;voi
42、d*thread_function(void*arg)int i,j;for(i=0;i5;i+)pthread_mutex_lock(&mymutex);j=myglobal;j=j+1;printf(.);fflush(stdout);sleep(1);myglobal=j;pthread_mutex_unlock(&mymutex);return NULL;int main(void)pthread_t mythread;int i;if(pthread_create(&mythread,NULL,thread_function,NULL)printf(error creating th
43、read.);abort();for(i=0;i connfd;info.other=(ARG*)arg)-other;/这种方法有问题,对一个客户可以工作,但多个客户则可能出现问题。这种方法有问题,对一个客户可以工作,但多个客户则可能出现问题。通过分配arg的空间来传递n主线程首先为每个新线程分配存储主线程首先为每个新线程分配存储arg的空间,再将的空间,再将arg传递给新线程传递给新线程使用,新线程使用完后要释放该空间。使用,新线程使用完后要释放该空间。void*start_routine(void*arg);int main(void)struct ARG*arg;int connfd;
44、loop if(connfd=accept(sockfd,NULL,NULL)=-1)arg=new ARG;arg-connfd=connfd;pthread_create(&tid,NULL,start_routine,(void*)arg);通过分配arg的空间来传递void*start_routine(void*arg)struct ARG info;info.connfd=(ARG*)arg)-connfd;/*handle client*/delete arg;多线程并发服务器#include#define PORT 1234#define BACKLOG2#define MAXD
45、ATASIZE 1000void process_cli(int connectfd,sockaddr_in client);void*start_routine(void*arg);struct ARG int connfd;sockaddr_in client;int main(void)intlistenfd,connectfd;pthread_ttid;ARG*arg;多线程并发服务器(多线程并发服务器(cont.)struct sockaddr_in server,client;int sin_size;/*Create TCP Socket*/*Bind server addres
46、s to listenfd.*/*Listen on listenfd*/sin_size=sizeof(struct sockaddr_in);while(1)if(connectfd=accept(listenfd,(struct sockaddr*)&client,&sin_size)=-1)/*handle error*/arg=new ARG;arg-connfd=connectfd;memcpy(void*)&arg-client,&client,sizeof(client);if(pthread_create(&tid,NULL,start_routine,(void*)arg)
47、/*handle erroe*/close(listendfd);多线程并发服务器(cont.)void process_cli(int connectfd,sockaddr_in client)void*start_routine(void*arg)ARG*info;info=(ARG*)arg;process_cli(info-connfd,info-client);delete arg;pthread_exit(NULL);多线程并发服务器(cont.):与多进程并发服务器不同的是,由于多个线程间共享相同的内存空间和描述字,因此pthread_create后,不能关闭监听套接字和连接套接字,否则程序不能正常工作。(可以做试验验证)