歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux系統下實現多線程客戶/服務器

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

日期:2017/3/3 16:42:46   编辑:關於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 )
{
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 );
}

服務器端主程序如下:

#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