您现在的位置是:主页 > news > 住房住房和城乡建设部网站首页/发帖平台

住房住房和城乡建设部网站首页/发帖平台

admin2025/5/2 11:12:42news

简介住房住房和城乡建设部网站首页,发帖平台,wordpress免费中文企业主题,小广告公司如何起步tinyhttpd是一个demo版的服务器。代码几百行。源码分析在这。从中可用一窥服务器的基础原理。他采用的是一个请求新开一个线程处理的方式。里面涉及了多进程、多线程、进程间通信等知识。 我们从main函数开始分析。 int main(void) {int server_sock -1;u_short port 0;…

住房住房和城乡建设部网站首页,发帖平台,wordpress免费中文企业主题,小广告公司如何起步tinyhttpd是一个demo版的服务器。代码几百行。源码分析在这。从中可用一窥服务器的基础原理。他采用的是一个请求新开一个线程处理的方式。里面涉及了多进程、多线程、进程间通信等知识。 我们从main函数开始分析。 int main(void) {int server_sock -1;u_short port 0;…

tinyhttpd是一个demo版的服务器。代码几百行。源码分析在这。从中可用一窥服务器的基础原理。他采用的是一个请求新开一个线程处理的方式。里面涉及了多进程、多线程、进程间通信等知识。
    我们从main函数开始分析。

int main(void)
{int server_sock = -1;u_short port = 0;int client_sock = -1;struct sockaddr_in client_name;int client_name_len = sizeof(client_name);pthread_t newthread;
// 拿到一个监听的文件描述符server_sock = startup(&port);while (1){// 等待连接到来client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);// 每一个连接用一个线程处理,主函数是accept_requestif (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)perror("pthread_create");}
// 服务器退出close(server_sock);return(0);
}

main函数的逻辑很简单。首先调用startup拿到一个监听型的文件描述符。然后启动服务器。阻塞在accept等待请求的到来。我们看看startup。

// 传统的服务器启动流程
int startup(u_short *port)
{int httpd = 0;struct sockaddr_in name;// 拿到一个用于监听的文件描述符httpd = socket(PF_INET, SOCK_STREAM, 0);memset(&name, 0, sizeof(name));name.sin_family = AF_INET;name.sin_port = htons(*port);name.sin_addr.s_addr = htonl(INADDR_ANY);// 绑定一个地址到socket,没有的话ip是本机地址,端口随机if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)error_die("bind");// port等于0,系统会随机分配一个端口(bind函数里实现)。这里通过文件描述符拿到系统随机分配的端口if (*port == 0)  /* if dynamically allocating a port */{int namelen = sizeof(name);// 获取socket绑定的地址信息if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)error_die("getsockname");*port = ntohs(name.sin_port);}if (listen(httpd, 5) < 0)error_die("listen");return(httpd);
}

startup函数是经典的socket编程流程。这时候服务器已经启动。等待请求的到来。我们回忆main函数里的accept函数。他返回的是一个和客户端通信的文件描述符。然后新开一个线程,线程里执行accept_request函数。把这个描述符传给线程,让他处理。accept_request函数的主要逻辑如下。

// 文件路径htdocs下sprintf(path, "htdocs%s", url);// 最后一个字符是/说明是个目录,则取该目录下的index.html文件if (path[strlen(path) - 1] == '/')strcat(path, "index.html");// 找不到该文件返回404if (stat(path, &st) == -1) {while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));not_found(client);}else{// 是个目录if ((st.st_mode & S_IFMT) == S_IFDIR)strcat(path, "/index.html");// 是可执行文件则说明是cgi程序if ((st.st_mode & S_IXUSR) ||(st.st_mode & S_IXGRP) ||(st.st_mode & S_IXOTH)    )cgi = 1;// 返回静态文件给客户端if (!cgi)serve_file(client, path);else// 执行cgi程序execute_cgi(client, path, method, query_string);}

主要是两个逻辑。
1 静态文件的请求,则直接读取文件内容,然后返回给客户端。线程退出。
2 执行动态脚本。
下面我们只分析2。通过执行execute_cgi函数执行动态脚本。该函数比较长,分开分析。

void execute_cgi(int client, const char *path,const char *method, const char *query_string)
{// 一系列简单解析http协议的逻辑// 获取两个匿名管道if (pipe(cgi_output) < 0) {cannot_execute(client);return;}if (pipe(cgi_input) < 0) {cannot_execute(client);return;}
}

申请两个管道。

// fork进程if ( (pid = fork()) < 0 ) {cannot_execute(client);return;}

创建一个进程。我们看看父进程和子进程都做了什么事情。先看子进程

char meth_env[255];char query_env[255];char length_env[255];// 先断开文件描述符1和标准输出file结构的关联,然后使1指向cgi_ouput[1]指向的file结构dup2(cgi_output[1], 1);dup2(cgi_input[0], 0);// 关闭读端close(cgi_output[0]);// 关闭写端close(cgi_input[1]);// 输入参数给处理请求的cgi进程使用sprintf(meth_env, "REQUEST_METHOD=%s", method);putenv(meth_env);if (strcasecmp(method, "GET") == 0) {sprintf(query_env, "QUERY_STRING=%s", query_string);putenv(query_env);}else {   /* POST */sprintf(length_env, "CONTENT_LENGTH=%d", content_length);putenv(length_env);}// 执行cgi进程execl(path, path, NULL);exit(0);

子进程输入关闭管道的一端,然后输入环境变量给cgi进程。然后执行cgi程序。再看父进程。

  close(cgi_output[1]);close(cgi_input[0]);if (strcasecmp(method, "POST") == 0)for (i = 0; i < content_length; i++) {/*读数据然后写入写端cgi_input[1],对端是子进程的cgi_input[0],作为子进程的标准读入,即子进程可以读到这里写入的数据*/recv(client, &c, 1, 0);write(cgi_input[1], &c, 1);}/*等待子进程写入,然后返回给客户端,cgi_output[1]是子进程的标准输出端,从cgi_output[1]写入的数据可以从cgi_output[0]读取*/while (read(cgi_output[0], &c, 1) > 0)send(client, &c, 1, 0);// 关闭管道close(cgi_output[0]);close(cgi_input[1]);// 等到子进程退出waitpid(pid, &status, 0);

父进程同样关闭管道一端。如果是post则把客户端的body输入给子进程。然后在read函数阻塞等待子进程的输入。最后两个进程退出。整个服务器的处理过程是,每次来一个请求(假设是cgi)。新开一个处理线程。主线程继续监听。然后新开的处理线程fork出一个进程执行cgi。这时候就相当于有两个进程。父子进程互相通信完成一个客户端的处理,然后退出。