您好,欢迎来到思海网络,我们将竭诚为您提供优质的服务! 诚征网络推广 | 网站备案 | 帮助中心 | 软件下载 | 购买流程 | 付款方式 | 联系我们 [ 会员登录/注册 ]
促销推广
客服中心
业务咨询
有事点击这里…  531199185
有事点击这里…  61352289
点击这里给我发消息  81721488
有事点击这里…  376585780
有事点击这里…  872642803
有事点击这里…  459248018
有事点击这里…  61352288
有事点击这里…  380791050
技术支持
有事点击这里…  714236853
有事点击这里…  719304487
有事点击这里…  1208894568
有事点击这里…  61352289
在线客服
有事点击这里…  531199185
有事点击这里…  61352288
有事点击这里…  983054746
有事点击这里…  893984210
当前位置:首页 >> 技术文章 >> 文章浏览
技术文章

Linux系统下实现多线程客户/服务器

添加时间:2011-4-13  添加: admin 
 

在传统的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 ); 
} 
}
关键字:服务器、多线程、系统
分享到:

顶部 】 【 关闭
版权所有:佛山思海电脑网络有限公司 ©1998-2024 All Rights Reserved.
联系电话:(0757)22630313、22633833
中华人民共和国增值电信业务经营许可证: 粤B1.B2-20030321 备案号:粤B2-20030321-1
网站公安备案编号:44060602000007 交互式栏目专项备案编号:200303DD003  
察察 工商 网安 举报有奖  警警  手机打开网站