总述
本项目实现了Linux系统下一个简易Web服务器要求:
- 当一个客户(浏览器)连接时,创建一个连接套接字;
- 从这个连接套接字接收 HTTP 请求;
- 解释该请求以确定所请求的特定文件;
- 从服务器的文件系统获得请求的文件;
- 创建一个由请求的文件组成的 HTTP 响应报文,报文前面有首部行;
- 经 TCP 连接向请求浏览器发送响应;
- 如果浏览器请求一个在该服务器中不存在的文件,服务器应当返回一个“404 Not Found”差错报文。
只支持http1.0和get方法
技术实现
网页访问流程
启动服务器,监听特定端口,接收客户端http请求,解析客户端http请求,根据url返回特定资源。
socket流程
首先自然是创建一个服务器套接字,使其能够接收客户端的请求
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#define PORT 8880
#define BUFFER_SIZE 5120
#define MAX_CLIENTS 100
#define FILE_SIZE 4096
int main(int argc, char *argv[]){
int server_sock, client_sock, *new_sock;
struct sockaddr_in server, client;
socklen_t client_len = sizeof(client);
server_sock = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock == -1){
perror("Could not create socket");
return 1;
}
server.sin_family = AF_INET;
server.sinaddr.s_addr = INADDR_ANY;
server.sinport = htons(PORT);
if(bind(server_sock, (struct sockaddr *)&server, sizeof(server)) < 0){
perror("Bind failed");
return 1;
}
listen(server_sock, MAX_CLIENTS);
//...
}
AF.INET表示能够接收ipv4,INADDR_ANY表示接收任何地址的客户端连接,htons则将端口号转换成网络字节,然后用bind函数将服务器套接字和端口绑定起来,再调用listen,使系统开始监听服务器套接字的端口,等待客户端的连接。
接收客户端连接,解析http请求并返回资源
while (1) {
client_sock = accept(server_sock, (struct sockaddr *)&client, &client_len);
if (client_sock < 0) {
perror("Accept failed");
return 1;
}
pthread_t thread_id;
new_sock = malloc(1);
*new_sock = client_sock;
if (pthread_create(&thread_id, NULL, connection_handler, (void*) new_sock) < 0) {
perror("Could not create thread");
return 1;
}
}
服务器每接收到一个客户端,则分配到客户端套接字client_sock中,然后使用pthread创建一个线程处理来处理请求。将client_sock分配到一个新的套接字new_sock中,使得client_sock可以继续接收其他客户端的请求。
pthread_t 定义了一个新的线程id,pthread_create则利用这个线程id创建一个新的线程,而第三个参数则是该线程执行的函数,第四个参数就是执行函数的参数。pthread编程可以参考下面这篇文章:
Linux多线程编程:pthread线程创建、退出、回收、分离、取消
connection_handler函数
void handle_http_request(int sock, char* buffer);
void *connection_handler(void *socket_desc) {
int sock = *(int*)socket_desc;
char buffer[BUFFER_SIZE];
memset(buffer, 0, sizeof(buffer));//初始化缓冲区
ssize_t n = read(sock, buffer, sizeof(buffer) - 1);//读取套接字到缓冲区
if (n <= 0) {
perror("Read failed");
return 0;
}
puts(buffer);//输出到控制台
handle_http_request(sock, buffer);//处理缓冲区中的数据
write(sock, buffer, strlen(buffer));//将缓存区数据返回给客户端
close(sock);
free(socket_desc);
return 0;
}
void handle_http_request(int sock, char* buffer) {
// 解析请求方法和请求路径
char method[50], path[255];
sscanf(buffer, "%[^ ] %[^ ]", method, path);//按空格匹配方法,url字符串
// 处理GET请求
if (strcmp(method, "GET") == 0) {
if (strcmp(path, "/") == 0) {//如果只有"/"表示访问默认页面,我设置为index文件
FILE *file = fopen("index", "r");//直接在当前文件夹打开index文件
if (file == NULL) {
sprintf(buffer, "HTTP/1.0 404 Not Found\r\n\r\n<h1>404 Not Found</h1>\n");
} else {
char file_buffer[FILE_SIZE];
fread(file_buffer, sizeof(char), BUFFER_SIZE, file);
sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n%s", file_buffer);
fclose(file);
}
}else{
FILE *file = fopen(path + 1, "r");
if (file == NULL) {
sprintf(buffer, "HTTP/1.0 404 Not Found\r\n\r\n<h1>404 Not Found</h1>\n");
} else {
char file_buffer[FILE_SIZE];
fread(file_buffer, sizeof(char), BUFFER_SIZE, file);
sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n%s", file_buffer);//读取文件内容,放入缓冲区
fclose(file);
}
}
}
// 不支持的请求方法
else {
const char *response = "HTTP/1.0 405 Method Not Allowed\r\n\r\n";
write(sock, response, strlen(response));
}
}
最后,由于最大连接数不超过100,需要设置临界资源的互斥使用
在main函数中初始化锁lock,然后在while循环中加锁,判断临界资源client_count是否达到100,没有达到100再分配线程,将client_count++,每次connection_handle处理完一个连接就将client_count--,还要释放资源sock。具体代码见源码。
运行效果
项目结构
运行示例
访问不存在的页面
至于访问的文件内容,大家可以自己写html代码试试。
源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#define PORT 8880
#define BUFFER_SIZE 5120
#define MAX_CLIENTS 100
#define FILE_SIZE 4096
int client_count = 0;
pthread_mutex_t lock;
void handle_http_request(int sock, char* buffer);
void *connection_handler(void *socket_desc) {
int sock = *(int*)socket_desc;
char buffer[BUFFER_SIZE];
memset(buffer, 0, sizeof(buffer));
ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
if (n <= 0) {
perror("Read failed");
return 0;
}
puts(buffer);
handle_http_request(sock, buffer);
write(sock, buffer, strlen(buffer));
close(sock);
free(socket_desc);
pthread_mutex_lock(&lock);
client_count--;
pthread_mutex_unlock(&lock);
return 0;
}
void handle_http_request(int sock, char* buffer) {
// 解析请求方法和请求路径
char method[50], path[255];
sscanf(buffer, "%[^ ] %[^ ]", method, path);
// 处理GET请求
if (strcmp(method, "GET") == 0) {
if (strcmp(path, "/") == 0) {
FILE *file = fopen("index", "r");
if (file == NULL) {
sprintf(buffer, "HTTP/1.0 404 Not Found\r\n\r\n<h1>404 Not Found</h1>\n");
} else {
char file_buffer[FILE_SIZE];
fread(file_buffer, sizeof(char), BUFFER_SIZE, file);
sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n%s", file_buffer);
fclose(file);
}
}else{
FILE *file = fopen(path + 1, "r");
if (file == NULL) {
sprintf(buffer, "HTTP/1.0 404 Not Found\r\n\r\n<h1>404 Not Found</h1>\n");
} else {
char file_buffer[FILE_SIZE];
fread(file_buffer, sizeof(char), BUFFER_SIZE, file);
sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n%s", file_buffer);
fclose(file);
}
}
}
// 不支持的请求方法
else {
const char *response = "HTTP/1.0 405 Method Not Allowed\r\n\r\n";
write(sock, response, strlen(response));
}
}
int main(int argc, char *argv[]) {
int server_sock, client_sock, *new_sock;
struct sockaddr_in server, client;
socklen_t client_len = sizeof(client);
pthread_mutex_init(&lock, NULL);
server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock == -1) {
perror("Could not create socket");
return 1;
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(PORT);
if (bind(server_sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror("Bind failed");
return 1;
}
listen(server_sock, MAX_CLIENTS);
while (1) {
pthread_mutex_lock(&lock);
if (client_count >= MAX_CLIENTS) {
pthread_mutex_unlock(&lock);
continue;
}
pthread_mutex_unlock(&lock);
client_sock = accept(server_sock, (struct sockaddr *)&client, &client_len);
if (client_sock < 0) {
perror("Accept failed");
return 1;
}
pthread_t thread_id;
new_sock = malloc(1);
*new_sock = client_sock;
pthread_mutex_lock(&lock);
client_count++;
pthread_mutex_unlock(&lock);
if (pthread_create(&thread_id, NULL, connection_handler, (void*) new_sock) < 0) {
perror("Could not create thread");
return 1;
}
}
pthread_mutex_destroy(&lock);
return 0;
}