1、实现代码
#include "csapp.h"
void echo(int connfd);
void *thread(void *vargp);
int main(int argc, char **argv)
{
int listenfd, connfd, port, clientl
int *connfdp;
// static pool pool;
struct sockaddr_in clientaddr;
struct hostent *hp;
char *haddrp;
pthread_t tid;
fd_set read_set,ready_set;
if (argc != 2)
{
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
port = atoi(argv[1]);
// Signal(SIGCHLD,sigchld_handler);
listenfd = Open_listenfd(port);
while (1)
{
clientlen=sizeof(struct sockaddr_in);
connfdp=Malloc(sizeof(int));
*connfdp=Accept(listenfd,(SA*)&clientaddr,&clientlen);
Pthread_create(&tid,NULL,thread,connfdp);
}
}
void *thread(void *vargp)
{
int connfd=*((int *)vargp);
Pthread_detach(pthread_self());
Free(vargp);
echo(connfd);
Close(connfd);
return NULL;
}
2、代码分析
基于线程的并发echo服务器代码整体结构类似于基于进程的设计。主线程不断等待连接请求,然后创建一个对等线程处理该请求。有一个值得注意的问题就是,调用pthread_create函数时,如何将已连接描述符传递给对等线程,最明显的方法就是传递一个指向这个描述符的指针,就像下面这样:
插入一下创建线程的函数原型,这样就能理解这个描述符是怎么样传递的了。
#include <pthread.h>
//第一个参数为指向线程标识符的指针。
//第二个参数用来设置线程属性。
//第三个参数是线程运行函数的起始地址。
//最后一个参数是运行函数的参数。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
然后在对等线程中间接引用这个指针,并将它赋值给一个局部变量。
然而这样会出错,因为在对等线程的赋值语句和主线程的accept语句之间引入了竞争。如果赋值语句在下一个accept函数之前完成,那么对等线程的局部变量connfd会得到正确地描述符。但是如果赋值语句在accept函数之后才完成,那么对等线程中的局部变量connfd就得到下一次连接的描述符。导致的结果就是,两个线程在同一个描述符上执行输入和输出,为了避免这个问题,必须将accept函数返回的每个已连接描述符分配到他自己的动态分配的内存块,像例程代码while(1)里面一样。另一个问题就是在线程例程中避免内存泄漏,既然不显式地回收线程,就必须分离每个线程(Pthread_detach(pthread_self());),使得它终止时内存资源能被收回。更进一步我们应该释放主线程分配地内存块(也就是accept返回值分配的内存块, Free(vargp);)。
3、结果
有一个需要注意的地方是,:pthread不是Linux下的默认的库,也就是在链接的时候,无法找到phread库中哥函数的入口地址,于是链接会失败。在这时需要在编译时添加选项-lpthread。可以如下图:
验证如下: