前两篇文章都是使用fork子进程来处理客户端请求,所以我们需要在这里了解一下fork的具体信息。

    当server从accept返回时,它获得socket连接另一端的client socket 文件描述符和socket地址信息,然后使用fork创建子进程来执行对应client socket上的操作。

    使用fork应该注意以下几点:

    (1)子进程可以关闭不必要的文件描述符或者释放其他资源,因为使用fork后,如果子进程不调用exec以使用新的进程空间的话,子进程会复制父进程的进程空间内容,包括数据段等(代码段是共享的,数据段等采用一种写时复制的策略来提高性能)。所以不必要的资源可以尽快释放。

    (2)由于子进程在退出后会成为僵尸进程,这些信息会遗留在父进程内,所以父进程注册所监听的信号及其处理函数(SIGCHLD是子进程退出时向父进程发送的信号,默认情况下是不采取任何动作,所以我们需要调用wait或者waitpid来彻底清除子进程。)较为常用的信号注册和信号处理可以在fork前调用,使得子进程也可以完成同样的功能。

    (3)最后父进程要关闭已经accept返回的socket文件描述符,因为父进程不处理相应活动,而是交由fork出来的子进程来处理。

    (4)使用fork的时候,要注意它对父子进程有执行顺序是不做任何限制的(vfork的话会先运行子进程),所以必要的时候可以让进程睡眠或者用消息唤醒。

    (5)注意虽然子进程复制了父进程,但是还有下面这些东西是不同的:

       1. 子进程ID

       2. 虽然子进程复制了父进程的file descriptors,但是他们都指向同一个open file description(也就是文件表项),所以类似于文件偏移量等还是共享的。

       3. 子进程的tms_utime, tms_stime,tms_cutime,tms_cstime都被设置为0

       4. 父进程的文件锁不会被继承

       5. the set of signals pending for the child process shall be initialized to the empty set

       6. 父进程中已打开的semaphore,在子进程中仍然继续打开

       7. 最好在单线程的进程内使用fork,不然在多线程的情况下会有很多冗余(例如一些互斥量等可能会带来一些同步性的问题)

    使用fork会有以下问题:

       1 fork是昂贵的,内存映像要从父进程拷贝到子进程,所有描述字要在子进程中复制等等,还有进程的上下文切换开销。

       2 fork子进程后,需要用 进程间通信IPC 在父子进程之间传递信息,特别是fork内的信息。

    这时候可以考虑使用线程!它比较轻,创建速度比进程快10-100倍。但因为线程共享全局内存,所以也就带来了同步的问题。(注意errno自有)

下面是copy过来的一个不完整的例子,主要是处理线程间数据共享的问题,如其中的connfd.

static int sockfd; static FILE *fp; void str_cli( FILE *fp_arg, int sockfd_arg) { char recvline[MAXLINE]; pthead_t tid; sockfd = sockfd_arg; fp = fp_arg; pthread_create(&tid,NULL,copyto,NULL); while( readline(sockfd,recvline,MAXLINE)>0) fputs(recvline,stdout); } void *copyto( void *arg) { char sendline[MAXLINE]; while( fgets(sendline,MAXLINE,fp)!=NULL) write(sockfd,sendline,strlen(sendline)); shutdown(sockfd,SHUT_WR); return NULL; } static void *doit(void *arg) { pthread_detach(pthread_self()); connfd = *((int*)arg); str_echo( connfd ); close(connfd ); return NULL; free(arg); //记得一定要free掉,不然内存泄露 } int main( int argc, char **argv) { int listenfd,connfd; socklen_t addrlen, len; struct sockaddr *cliaddr; pthread_t tid; if( argc==2) listenfd = tcp_listen(NULL,argv[1],&addrlen); else if( argc==3) listenfd = tcp_listen(argv[1] , argv[2], &addrlen); else err_quit (“usage: tcpserv01[<host>]<service or port>”); cliaddr=malloc(addrlen); for(;;) { len=addrlen; connfd=accept( listenfd, cliaddr, &len); pthread_create ( &tid, NULL, &doit, (void*)connfd); //accept之后创建线程来处理connfd上的信息交互 //线程传入的参数是新接的socket //注意!我们将整型变量connfd强制转换为void指针,虽然通常都是4个字节,但是不能保证在所有系统上工作。 //如果我们传入connfd的地址呢 (void*)&connfd? 这样也不行。因为取地址就是取到实际对象了,而下次accept会覆盖connfd,这样就会造成共享变量的竞争访问!!! /* //选择下面这种方式会更好 for(;;) { len=addrlen; lptr=malloc(sizeof(int)); *lptr=accept(listenfd,cliaddr,&len); pthread_create(NULL,NULL,&doit,lptr); } */ } }

相关文章: