unix网络编程各种TCP客户-服务器程序设计实例(二)

时间:2021-10-02 17:51:12

前面我们介绍了unix网络编程各种TCP客户-服务器程序设计实例附环境搭建和编译方法

本节我们接着介绍另外的几种TCP客户-服务器程序;

第四种:TCP并发服务器,每个客户一个子线程

在我们前面的并发服务器程序例子中可以看出:父进程接受连接,派生子进程,子进程处理与客户的交互。

这种模式的问题:

fork()是昂贵的。内存映像要从父进程拷贝到子进程,所有描述字要在子进程中复制等等。

fork()子进程后,需要用进程间通信在父子进程之间传递信息。

一个进程中的所有线程共享相同的全局内存,这使得线程很容易共享信息,但是这种简易型也带来了同步问题。一个进程中的所有线程不仅共享全局变量,而且共享:

进程指令,大多数数据,打开的文件(如描述字),信号处理程序和信号处置,当前工作目录,用户ID和组ID;

所以我们才引进了线程;

unix网络编程各种TCP客户-服务器程序设计实例(二)

当一个程序由exec启动时,会创建一个称作初始线程或主线程的单个线程。额外线程则由pthread_create函数创建。

一个进程中的每个线程都由一个线程ID标识,其数据类型是pthread_t(常常是unsigned int)。如果新的线程创建成功,其ID将通过tid指针返回。

每个线程都有很多属性:优先级、起始栈大小、是否应该是一个守护线程,等等。当创建线程时我们可以通过初始化一个phread_attr_t变量说明这些属性以覆盖缺省值。

最后,当创建一个线程时,我们要说明一个它将执行的函数。线程以调用该函数开始,然后或者显示地终止(调用pthread_exit)或者隐式的终止(让函数返回)。参数时以个指针arg。

我们可以调用pthread_join等待一个线程终止。类似于waitpid

我们必须指定要等待线程的tid。不能等待任意一个线程结束(类似于waitpid的进程ID参数为-1的情况)。

 

互斥锁:

多个线程修改一个共享变量,是最简单的问题,解决方法是用一个互斥锁(mutex)保护共享变量;只有我们持有互斥锁才能访问该变量。

unix网络编程各种TCP客户-服务器程序设计实例(二)

互斥锁是类型为pthread_mutex_t的变量。

条件变量:

互斥锁适于阻止对共享变量的同时访问,但是我们需要某种东西以使我们能够睡眠等待某种条件出现。我们需要一种方法使得主循环进入睡眠,直到有一个线程通知它某件事已就绪。条件变量加上互斥锁可以提供这种功能。互斥锁提供互斥机制,条件变量提供信号机制。

条件变量是一个pthread_cond_t类型的变量。

unix网络编程各种TCP客户-服务器程序设计实例(二)

多线程客户端程序:

 

unix网络编程各种TCP客户-服务器程序设计实例(二)

#include "unpthread.h"
void* copyto(void*);
static int sockfd;//全局变量,所有线程共有
static FILE *fp;
void str_cli1(FILE *fp_arg, int sockfd_arg){
char recvline[MAXLINE];
pthread_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)
Writen(sockfd,sendline,strlen(sendline));
Shutdown(sockfd,SHUT_WR);
return(NULL);
}

int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_inservaddr;

if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");

sockfd = Socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

str_cli1(stdin, sockfd);/* do it all */

exit(0);
}

服务器程序:

#include"unpthread.h"
static void*doit(void *);/* each thread executes this function */

int
main(int argc, char **argv)
{
intlistenfd, connfd;
pthread_ttid;
socklen_taddrlen, len;
struct sockaddr*cliaddr;

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);
}
}

static void *
doit(void *arg)
{
Pthread_detach(pthread_self());
str_echo((int) arg);/* same function as before */
Close((int) arg);/* done with connected socket */
return(NULL);
}


注意:这里我们编译的时候,由于pthread不是系统默认的库,所以要用命令:

gcc tcpcli.c -o client -lunp -lpthread

gcc tcpserv.c -o server -lunp -lpthread

服务器运行命令:sudo ./server 127.0.0.1 9877

客户端运行命令:./client 127.0.0.1

我们注意到每种客户端的程序其实只要是str_cli函数的改变,main函数没有变化;

好了,又介绍完了一种设计实例,明天继续,未完待续。。。