相关原理与知识
- 调用fork函数产生一个子进程,返回值为0;父进程返回值为子进程的pid,子进程完全复制父进程。
- 将要监视的文件描述符集中到一起,可以利用select函数可以同时监视多个文件描述符。监视文件描述符可以视为监视套接字。程序在select处等待,直到被监视的文件描述符发生状态改变,文件描述符0,1,2分别表示标准输入,标准输出,标准错误输出。socket描述符从3开始。select执行完之后,未发生状态改变的文件描述符位被置零。fd_set是一个结构体,select默认支持文件描述符数量为1024。
- 主线程通过调用pthread_create函数用于创建一个线程,第一个参数是将创建线程的ID,第三个参数是线程要执行的函数的指针,最后的参数是函数需要的参数指针。调用返回时,主线程和调用的对等线程并发运行,且tid为新线程的ID。每个线程例程以通用指针作为参数,并返回一个通用指针。
- TCP套接字通信模型
- web服务器的客户端为浏览器。应用层采用HTTP协议,传输层和网络层采用TCP/IP协议。HTTP请求方法有GET、HEAD、PUT、POST、等,最常用的是GET和POST。
- HTML是一种标记语言,由文字和标记组合而成,学习HTML语言就是了解其标记和语法规则。
实验过程
多进程
- 服务器程序关键代码
调用Fork创建子进程,父进程继续监听。在子进程中对客户端进行处理,因为fork完全复制父进程,所以要在子进程中关掉监听套接字listenfd。子进程处理完之后,父子进程都要关掉连接套接字connfd。 - 执行服务器程序
- 查看结果
多路复用
-
服务器程序关键代码
Fd_set rds,tmp定义两个文件描述符集,rds为用于监控的集合,tmp用于临时复制rds。FD_ZERO将rds清零,RD_SET将选定的位置1,fd_max是监听的套接字中最大的套接字描述符加一。
因为每次执行完select之后,未发生状态变化的套接字位会被置0,所以在循环开始时要对rds进行复制,对副本tmp进行select操作。select会把未就绪的描述符位置0,遍历文件描述符至fd_max,对每个文件描述符进行判断是否在文件描述符集tmp中,若在则进行处理。因为处理的都是就绪的套接字,所以不会发生阻塞。
如果为监听套接字就绪,新建立连接的描述符要加入rds中,如果为连接套接字,处理完之后要将其从文件描述符集rds中清除 -
执行服务器程序
多线程
- 服务器程序关键代码
定义一个tid用于记录创建线程的id,主线程用于监听套接字,有新的连接建立之后创建一个副线程,主线程继续监听。副线程调用执行线程函数,在该函数中完成对新建立连接的处理,处理完之后关闭套接字,副线程结束。
- 执行服务器程序
实验结果分析
使用进程最简单,因为每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系,每个子进程都有自己的地址空间和相关资源,总体能够达到的性能上限非常大,但是调度开销比较大。
多路复用编程复杂度高,但是由于多路复用是在单一进程的上下文中的,因此每个逻辑流程都能访问该进程的全部地址空间,所以开销比多进程低得多。
多线程每个线程都有自己的线程上下文,所有的运行在一个进程里的线程共享该进程的整个虚拟地址空间。由于线程运行在单一进程中,因此共享这个进程虚拟地址空间的整个内容,包括它的代码、数据、堆、共享库和打开的文件。优点是程序逻辑和控制方式简单,所有线程可以直接共享内存和变量等,耗的总资源比进程方式好。但是由于每个线程与主程序共用地址空间,地址空间受限,一个线程的崩溃也可能影响到整个程序的稳定性,性能提高也有限制。
注意问题
- 每次执行完select之后,未发生状态变化的套接字位会被置0,所以在循环开始时要对文件描述符集进行复制备份,对副本进行select操作。
- pthread的库不是linux系统的库,所以在进行编译的时候要加上 -lpthread
- int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg); start_rtn是函数指针, 为线程开始执行的函数名,函数参数必须为通用型指针,使用参数时再转换成需要的类型以使用,原因是为了方便扩展,比如要传多个参数, 可以用结构封装,然后传指向该结构体的指针即可。
- 调用pthread_detach可以分离线程,这样在其结束后可以由系统自动释放资源。
源代码(仅main函数)
- tiny_fork.c
int main(int argc, char **argv)
{
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
struct sockaddr_storage clientaddr;
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
if (Fork() == 0) {
Close(listenfd);
Getnameinfo((SA *) &clientaddr, clientlen, hostname, MAXLINE,
port, MAXLINE, 0);
printf("Accepted connection from (%s, %s)\n", hostname, port);
doit(connfd);
Close(connfd);
exit(0);
}
Close(connfd);
}
return 0;
}
- tiny_select.c
int main(int argc, char **argv)
{
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
struct sockaddr_storage clientaddr;
fd_set rds, tmp;
int fd_max,i;
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
listenfd = Open_listenfd(argv[1]);
FD_ZERO(&rds);
FD_SET(listenfd, &rds);
fd_max = listenfd + 1;
while (1) {
tmp = rds;
select(fd_max, &tmp, NULL, NULL, NULL);
for (i=0; i<fd_max; i++) {
if (FD_ISSET(i, &tmp)) {
if (listenfd == i) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
FD_SET(connfd, &rds);
if (fd_max <= connfd) fd_max = connfd+1;
Getnameinfo((SA *) &clientaddr, clientlen, hostname, MAXLINE,
port, MAXLINE, 0);
printf("Accepted connection from (%s, %s)\n", hostname, port);
}
else {
doit(i);
FD_CLR(i, &rds);
Close(i);
}
}
}
}
}
- tiny_thread.c
void *thread(void *argv);
int main(int argc, char **argv)
{
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
struct sockaddr_storage clientaddr;
pthread_t tid;
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
Getnameinfo((SA *) &clientaddr, clientlen, hostname, MAXLINE,
port, MAXLINE, 0);
printf("Accepted connection from (%s, %s)\n", hostname, port);
Pthread_create(&tid, NULL, thread, &connfd);
}
return 0;
}
void *thread(void *argv)
{
int connfd = *((int *)argv);
Pthread_detach(pthread_self());
doit(connfd);
Close(connfd);
return NULL;
}