Linux平台C语言编写Web服务器的实现方法 (linux c实现web服务器)
随着互联网的不断发展,网络服务器的重要性愈发凸显。Web服务器是网络服务器中非常重要的一种类型,它可以接受网络浏览器发送的HTTP请求,返回相应的HTML页面,从而实现网站的访问。因此,Web服务器一直是计算机科学领域的热门话题。本文将介绍在Linux平台下使用C语言编写Web服务器的实现方法。
1. 网络协议和套接字
Web服务器是一种网络服务器,它与客户端通过网络协议进行通信。HTTP(Hypertext Transfer Protocol,超文本传输协议)是Web服务器和Web浏览器之间广泛使用的协议。在C语言中,可以使用套接字(socket)实现网络通信,套接字是对TCP/IP协议的抽象,它允许不同的进程在网络上进行通信。
2. 基本框架
在C语言中,可以使用多线程技术实现Web服务器的并发处理。服务器程序接受客户端请求后,通过多线程实现并发处理请求。下面是一个基本的服务器框架:
“`
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 1024
#define SERV_PORT 8000
void *doit(void *);
int mn(int argc, char **argv) {
int listenfd, connfd;
pthread_t tid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0); // 创建socket,指定协议和地址族
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 绑定socket和地址
listen(listenfd, 5); // 监听socket
while (1) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); // 接受客户端连接
pthread_create(&tid, NULL, &doit, (void *)(long)connfd); // 创建线程处理客户端请求
}
}
void *doit(void *arg) {
int connfd = (int)(long)arg;
char buff[MAXLINE];
ssize_t n;
while ((n = read(connfd, buff, MAXLINE)) > 0) {
write(connfd, buff, n);
}
close(connfd);
return (NULL);
}
“`
上述代码中,我们创建了一个socket并将其与一个地址绑定,然后调用listen方法来监听来自客户端的连接。在这个while循环中,我们使用accept方法来等待客户端连接。当接受到连接后,我们会为其创建一个线程,然后在该线程中处理客户端请求。在本例中,我们只是简单地读取客户端发送过来的信息并将其返回。
3. 处理HTTP请求
当接收到来自客户端的HTTP请求时,服务器需要解析请求、生成响应、返回数据。下面是一个简单的HTTP请求响应处理函数:
“`
int get_line(int sock, char *buf, int size) {
int i = 0;
char c = ‘\0’;
int n;
while ((i
n = recv(sock, &c, 1, 0);
if (n > 0) {
if (c == ‘\r’) {
n = recv(sock, &c, 1, MSG_PEEK); // 查看下一个字符
if ((n > 0) && (c == ‘\n’))
recv(sock, &c, 1, 0);
else
c = ‘\n’;
}
buf[i] = c;
i++;
} else
c = ‘\n’;
}
buf[i] = ‘\0’;
return (i);
}
void handl_clinet(int fd) {
char buf[1024];
char method[255];
char url[255];
char path[512];
size_t i, j;
struct stat st;
int cgi = 0;
char *query_string = NULL;
get_line(fd, buf, sizeof(buf));
i = 0; j = 0;
while (!isspace(buf[j]) && (i
method[i] = buf[j];
i++; j++;
}
method[i] = ‘\0’;
if (strcasecmp(method, “GET”) && strcasecmp(method, “POST”)) {
unimplemented(fd);
return;
}
if (strcasecmp(method, “POST”) == 0)
cgi = 1;
i = 0;
while (isspace(buf[j]) && (j
j++;
while (!isspace(buf[j]) && (i
url[i] = buf[j];
i++; j++;
}
url[i] = ‘\0’;
if (strcasecmp(method, “GET”) == 0) {
query_string = url;
while ((*query_string != ‘?’) && (*query_string != ‘\0’))
query_string++;
if (*query_string == ‘?’) {
cgi = 1;
*query_string = ‘\0’;
query_string++;
}
}
sprintf(path, “htdocs%s”, url);
if (path[strlen(path) – 1] == ‘/’)
strcat(path, “index.html”);
if (stat(path, &st) == -1) {
while ((get_line(fd, buf, sizeof(buf)) > 0) && strcmp(“\n”, buf))
;
not_found(fd);
} else {
if ((st.st_mode & S_IFMT) == S_IFDIR)
strcat(path, “/index.html”);
if ((st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXOTH))
cgi = 1;
if (!cgi)
serve_file(fd, path);
else
execute_cgi(fd, path, method, query_string);
}
close(fd);
}
“`
在这个函数中,我们首先读取请求行。然后分析请求方法和URL。如果请求方法是GET,我们从URL中解析出查询字符串,如果存在,则设置cgi标志。如果请求方法是POST,则需要执行CGI程序,设置cgi标志为1。接下来,我们检查指定的文件是否存在。如果文件不存在,则向客户端返回404错误页面;如果存在,则服务端会检查读/写/执行权限,如果可执行,则执行CGI程序,否则发送文件内容给客户端。
4. CGI程序
CGI(Common Gateway Interface,通用网关接口)是一种Web服务器与程序之间的通用接口标准,可以让程序动态生成网页或响应请求。在C语言中,我们可以使用cgi程序实现动态页面生成。下面是一个简单的cgi程序实现:
“`
void execute_cgi(int fd, char *path, char *method, char *query_string) {
char buf[1024];
int cgi_output[2];
int cgi_input[2];
pid_t pid;
int status;
int i;
char c;
int numchars = 1;
int content_length = -1;
buf[0] = ‘A’; buf[1] = ‘\0’;
if (strcasecmp(method, “GET”) == 0) {
while ((numchars > 0) && strcmp(“\n”, buf))
numchars = get_line(fd, buf, sizeof(buf));
} else { /* POST */
numchars = get_line(fd, buf, sizeof(buf));
while ((numchars > 0) && strcmp(“\n”, buf)) {
buf[15] = ‘\0’;
if (strcasecmp(buf, “Content-Length:”) == 0) {
content_length = atoi(&(buf[16]));
}
numchars = get_line(fd, buf, sizeof(buf));
}
if (content_length == -1) {
bad_request(fd);
return;
}
}
sprintf(buf, “HTTP/1.0 200 OK\r\n”);
send(fd, buf, strlen(buf), 0);
if (pipe(cgi_output)
cannot_execute(fd);
return;
}
if (pipe(cgi_input)
cannot_execute(fd);
return;
}
if ((pid = fork())
cannot_execute(fd);
return;
}
if (pid == 0) /* 子进程:执行CGI程序 */ {
char meth_env[255];
char query_env[255];
char length_env[255];
dup2(cgi_output[1], STDOUT_FILENO);
dup2(cgi_input[0], STDIN_FILENO);
close(cgi_output[0]);
close(cgi_input[1]);
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);
}
execl(path, path, NULL);
exit(0);
} else { /* 父进程:向CGI程序发送数据 */
close(cgi_output[1]);
close(cgi_input[0]);
if (strcasecmp(method, “POST”) == 0) {
for (i = 0; i
recv(fd, &c, 1, 0);
write(cgi_input[1], &c, 1);
}
}
while (read(cgi_output[0], &c, 1) > 0) {
send(fd, &c, 1, 0);
}
close(cgi_output[0]);
close(cgi_input[1]);
wtpid(pid, &status, 0);
}
}
“`
在本例中,我们使用HTTP GET方法向服务器传递参数,然后从QUERY_STRING环境变量中获取参数值。然后,我们使用HTTP POST方法向服务器传递参数,该函数先读取Content-Length数据,然后读取POST数据并将其发送给子进程。在子进程中,我们将标准输出定向到管道,将标准输入定向到另一个管道,并设置一些环境变量。然后,我们执行CGI程序并在管道中读取输出。父进程从管道中读取输出,并发送响应数据。
5.