歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux資訊 >> Linux文化 >> Linux系統下實現多線程客戶/服務器

Linux系統下實現多線程客戶/服務器

日期:2017/2/27 11:54:32   编辑:Linux文化

在傳統的Unix模型中,當一個進程需要由另一個實體執行某件事時,該進程派生(fork)一個子進程,讓子進程去進行處理。Unix下的大多數網絡服務器程序都是這麼編寫的,即父進程接受連接,派生子進程,子進程處理與客戶的交互。

雖然這種模型很多年來使用得很好,但是fork時有一些問題:

1. fork是昂貴的。內存映像要從父進程拷貝到子進程,所有描述字要在子進程中復制等等。目前有的Unix實現使用一種叫做寫時拷貝(copy-on-write)的技術,可避免父進程數據空間向子進程的拷貝。盡管有這種優化技術,fork仍然是昂貴的。

2. fork子進程後,需要用進程間通信(IPC)在父子進程之間傳遞信息。Fork之前的信息容易傳遞,因為子進程從一開始就有父進程數據空間及所有描述字的拷貝。但是從子進程返回信息給父進程需要做更多的工作。

線程有助於解決這兩個問題。線程有時被稱為輕權進程(lightweight process),因為線程比進程“輕權”,一般來說,創建一個線程要比創建一個進程快10~100倍。

一個進程中的所有線程共享相同的全局內存,這使得線程很容易共享信息,但是這種簡易性也帶來了同步問題。

一個進程中的所有線程不僅共享全局變量,而且共享:進程指令、大多數數據、打開的文件(如描述字)、信號處理程序和信號處置、當前工作目錄、用戶ID和組ID。但是每個線程有自己的線程ID、寄存器集合(包括程序計數器和棧指針)、棧(用於存放局部變量和返回地址)、error、信號掩碼、優先級。在Linux中線程編程符合Posix.1標准,稱為Pthreads。所有的pthread函數都以pthread_開頭。以下先講述5個基本線程函數,在調用它們前均要包括pthread.h頭文件。然後再給出用它們編寫的一個TCP客戶/服務器程序例子。

第一個函數:

int pthread_create (pthread_t *tid,const pthread_attr_t *attr,void * (*func)(void *),void *arg);

一個進程中的每個線程都由一個線程ID(thread ID)標識,其數據類型是pthread_t(常常是unsigned int)。如果新的線程創建成功,其ID將通過tid指針返回。 每個線程都有很多屬性:優先級、起始棧大小、是否應該是一個守護線程等等,當創建線程時,我們可通過初始化一個pthread_attr_t變量說明這些屬性以覆蓋缺省值。我們通常使用缺省值,在這種情況下,我們將attr參數說明為空指針。

最後,當創建一個線程時,我們要說明一個它將執行的函數。線程以調用該函數開始,然後或者顯式地終止(調用pthread_exit)或者隱式地終止(讓該函數返回)。函數的地址由func參數指定,該函數的調用參數是一個指針arg,如果我們需要多個調用參數,我們必須將它們打包成一個結構,然後將其地址當作唯一的參數傳遞給起始函數。

在func和arg的聲明中,func函數取一個通用指針(void *)參數,並返回一個通用指針(void *),這就使得我們可以傳遞一個指針(指向任何我們想要指向的東西)給線程,由線程返回一個指針(同樣指向任何我們想要指向的東西)。調用成功,返回0,出錯時返回正Exxx值。Pthread函數不設置errno。

第二個函數:

int pthread_join(pthread_t tid,void **status);

該函數等待一個線程終止。把線程和進程相比,pthread_creat類似於fork,而pthread_join類似於waitpid。我們必須要等待線程的tid,很可惜,我們沒有辦法等待任意一個線程結束。如果status指針非空,線程的返回值(一個指向某個對象的指針)將存放在status指向的位置。

第三個函數:

pthread_t pthread_self(void);

線程都有一個ID以在給定的進程內標識自己。線程ID由pthread_creat返回,我們可以pthread_self取得自己的線程ID。

第四個函數:

int pthread_detach(pthread_t tid); 線程或者是可匯合的(joinable)或者是脫離的(detached)。當可匯合的線程終止時,其線程ID和退出狀態將保留,直到另外一個線程調用pthread_join。脫離的線程則像守護進程:當它終止時,所有的資源都釋放,我們不能等待它終止。如果一個線程需要知道另一個線程什麼時候終止,最好保留第二個線程的可匯合性。Pthread_detach函數將指定的線程變為脫離的。該函數通常被想脫離自己的線程調用,如:pthread_detach (pthread_self ( ));

第五個函數:

void pthread_exit(void *status);

該函數終止線程。如果線程未脫離,其線程ID和退出狀態將一直保留到調用進程中的某個其他線程調用pthread_join函數。指針status不能指向局部於調用線程的對象,因為線程終止時這些對象也消失。有兩種其他方法可使線程終止:

1. 啟動線程的函數(pthread_creat的第3個參數)返回。既然該函數必須說明為返回一個void指針,該返回值便是線程的終止狀態。

2. 如果進程的main函數返回或者任何線程調用了exit,進程將終止,線程將隨之終止。

以下給出一個使用線程的TCP回射客戶/服務器的例子,完成的功能是客戶端使用線程給服務器發從標准輸入得到的字符,並在主線程中將從服務器端返回的字符顯示到標准輸出,服務器端將客戶端發來的數據原樣返回給客戶端,每一個客戶在服務器上對應一個線程。利用該程序框架,通過擴展客戶端和服務器端的處理功能,可以完成多種基於多線程的客戶機/服務器程序。該程序在RedHat 6.0和TurboLinux4.02下調試通過。 共用頭文件如下:(head.h)

#include #include #include #include #include #include #include #include #include #include #include #define MAXLINE 1024 #define SERV_PORT 8000 #define LISTENQ 1024 static int sockfd; static FILE *fp;

公用函數如下(common.c):

/* 從一個描述字讀文本行 */ ssize_t readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; for (n=1; n0) { if ( (nwritten=write (fd, ptr, nleft ) )0) ---- fputs(recvline,stdout); }

int main ( int argc, char **argv ) { int sockfd; struct sockaddr_in servaddr; if (argc!=2 ) printf ( “ usage: tcpcli " ); exit(0); bzero(&servaddr, sizeof (servaddr)) ; servaddr.sin_family=AF_INET; servaddr.sinport=htons(SERV_PORT); inet_pton (AF_INET, argv[1], &servaddr.sin_addr ); connect (sockfd, (struct sockaddr *)&servaddr, siziof (servaddr ) ); str_cli (stdin, sockfd ); exit (0 ); }

服務器端主程序如下:

#include “head.h"; #include “common.c"; void str_echo (int sockfd ) { ssize_t n; char line[MAXLINE]; for (; ; ) { if ( (n=readline (sockfd, line, MAXLINE) )==0) return; writen (sockfd, line, n); } }

static void *doit ( void *arg) { pthread_detach(pthread_self ( ) ); str_echo ( (int ) arg ); close ( (int ) arg ); return ( NULL ) ; }

int main ( int argc, char **argv ) { int listenfd, connfd; socklen_t addrlen,len; struct sockaddr_in cliaddr, servaddr; pthread_t tid; listenfd=socket (AF_INET, SOCK_STREAM, 0 ); bzero (&servaddr, sizeof (servaddr ) ); servaddr.sin_family=AF_INET; servaddr.sin_addr.s_addr=htonl (INADDR_ANY ); servaddr.sin_port=SERV_PORT; bind (listenfd, ( struct sockaddr * )&servaddr, sizeof (servaddr ) ); listen (listenfd, LISTENQ ); addrlen=sizeof ( cliaddr ); cliaddr=malloc(addrlen ); for ( ; ; ) { len=addrlen; connfd=accept(listenfd, (struct sockaddr * )&cliaddr, &len ); pthread_creat ( &tid, NULL, &doit, ( void * )connfd ); } }


Copyright © Linux教程網 All Rights Reserved