Linux作業系統下實現多線程客戶/伺服器

來源:互聯網
上載者:User

在傳統的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 )
{
if (errno==EINTR )
nwritten=0;
else
return (-1);
}
nleft-=nwritten;
ptr++=nwritten;
}

用戶端主程式如下:


(client.c) 
#include “head.h";
#include “common.c";
/* 在str_cli中定義的要被線程執行的函數 */
void *copyto (void *arg)
{
char sendline[MAXLINE];
while (fgets (sendline,MAXLINE,fp) !=NULL )
writen(sockfd,sendline,strlen(sendline));
shutdown(sockfd,SHUT_WR);
return(NULL);
}

void str_cli(FILE *fp_arg, int sockfd_arg)
{
char recvline[MAXLINE];
pthread_t tid;
sockfd=sockfd_arg;
fp=fp_arg;
pthread_creat(&tid, NULL, copyto, NULL);
while (readline (sockfd,recvline,MAXLINE) >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 );
}

伺服器端主程式如下:


(server.c) 
#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 );
}
}

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.