基于C语言实现的简易Web服务器开发
一、项目概述
本项目是一个基于C语言实现的多功能简易Web服务器,支持HTTP/1.1协议,能够处理HTML页面、图片文件请求,并实现基本的登录验证功能。
二、项目文件结构
项目目录/
├── 01ser.c # 主服务器程序
├── 01.html # 首页HTML文件
├── 02.html # 测试页面HTML文件
├── 03.html # 登录页面HTML文件
├── 04.html # 登录失败页面HTML文件
├── 1.jpg # 图片资源
└── 2.png # 图片资源(淘宝Logo)
三、核心源代码分析
3.1 主服务器程序 (01ser.c)
3.1.1 头文件和宏定义
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
typedef struct sockaddr*(SA); // 简化socket地址类型
// 文件类型枚举
typedef enum {
FILE_HTML, // HTML文件
FILE_PNG, // PNG图片
FILE_JPG // JPG图片
} FILE_TYPE;
3.1.2 辅助函数
获取文件大小函数:
long file_size(char* file) {
int fd = open(file, O_RDONLY);
if (-1 == fd) {
perror("file size open error");
fprintf(stderr, "filename is %s\n", file);
return 0;
}
long size = lseek(fd, 0, SEEK_END);
return size;
}
发送HTTP响应头函数:
int send_head(int conn, char* file, FILE_TYPE type) {
char buf[256] = {0};
char* http_cmd[7] = {NULL};
// HTTP响应头构造
http_cmd[0] = "HTTP/1.1 200 OK\r\n";
http_cmd[1] = "Date: Wed, 31 Dec 2025 01:58:31 GMT\r\n";
// 根据文件类型设置Content-Type
switch (type) {
case FILE_HTML:
http_cmd[2] = "Content-Type: text/html;charset=utf-8\r\n";
break;
case FILE_PNG:
http_cmd[2] = "Content-Type: image/png\r\n";
break;
case FILE_JPG:
http_cmd[2] = "Content-Type: image/jpeg\r\n";
break;
default:
http_cmd[2] = "Content-Type: text/html;charset=utf-8\r\n";
}
http_cmd[3] = buf;
sprintf(buf, "content-length: %ld\r\n", file_size(file)); // 设置内容长度
http_cmd[4] = "Connection: keep-closed\r\n";
http_cmd[5] = "Server: MYWEBSer\r\n";
http_cmd[6] = "Content-Language: zh-CN\r\n\r\n"; // 空行表示头部结束
// 发送所有HTTP头部信息
int i = 0;
for (i = 0; i < 7; i++) {
send(conn, http_cmd[i], strlen(http_cmd[i]), 0);
}
return 0;
}
发送文件内容函数:
int send_file(int conn, char* filename, FILE_TYPE type) {
send_head(conn, filename, type); // 先发送HTTP头部
int fd = open(filename, O_RDONLY);
if (-1 == fd) {
perror("open");
return 1;
}
// 分块读取并发送文件内容
while (1) {
char buf[4096] = {0};
int rd_ret = read(fd, buf, sizeof(buf));
if (rd_ret <= 0) {
break; // 读取完毕
}
send(conn, buf, rd_ret, 0);
}
close(fd);
return 0;
}
3.1.3 主函数流程
cpp
int main(int argc, char** argv) {
// 1. 创建监听套接字(用于三次握手)
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listfd) {
perror("socket");
return 1;
}
// 2. 绑定服务器地址和端口
struct sockaddr_in ser, cli;
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(80); // 使用HTTP默认端口80
ser.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口
int ret = bind(listfd, (SA)&ser, sizeof(ser));
if (-1 == ret) {
perror("bind");
return 1;
}
// 3. 开始监听连接请求
listen(listfd, 3); // 最大等待队列为3
socklen_t len = sizeof(cli);
// 4. 主循环:接受并处理客户端请求
while (1) {
int conn = accept(listfd, (SA)&cli, &len);
if (-1 == conn) {
perror("accept");
continue;
}
// 接收HTTP请求
char buf[1024] = {0};
int ret = recv(conn, buf, sizeof(buf), 0);
if (ret <= 0) {
close(conn);
continue;
}
printf("收到请求:\n%s", buf); // 打印请求信息
fflush(stdout);
// 解析HTTP请求行
char* method = NULL;
char* url = NULL;
char* ver = NULL;
method = strtok(buf, " ");
url = strtok(NULL, " ");
ver = strtok(NULL, "\r");
// 5. 根据URL分发请求
// 处理根目录请求
if (0 == strcmp(url, "/")) {
send_file(conn, "./03.html", FILE_HTML);
}
// 处理登录请求
else if (0 == strncmp(url, "/login", 6)) {
char* name = NULL;
char* pass = NULL;
char* end = NULL;
// 解析URL中的用户名和密码参数
name = strchr(url, '=');
name += 1;
end = strchr(name, '&');
*end = '\0';
pass = strchr(end + 1, '=');
pass += 1;
// 简单验证(用户名:zhangsan,密码:123)
if (0 == strcmp(name, "zhangsan") && 0 == strcmp(pass, "123")) {
send_file(conn, "./01.html", FILE_HTML); // 登录成功
} else {
send_file(conn, "./04.html", FILE_HTML); // 登录失败
}
}
// 处理图片文件请求
else if (strlen(url) > 4 && 0 == strcmp(&url[strlen(url) - 4], ".jpg")) {
send_file(conn, url + 1, FILE_JPG); // 跳过开头的'/'
}
else if (strlen(url) > 4 && 0 == strcmp(&url[strlen(url) - 4], ".ico")) {
send_file(conn, "1.png", FILE_PNG); // 用1.png替代favicon.ico
}
else if (strlen(url) > 4 && 0 == strcmp(&url[strlen(url) - 4], ".png")) {
send_file(conn, url + 1, FILE_PNG);
}
close(conn); // 关闭当前连接
}
close(listfd); // 关闭监听套接字
return 0;
}
四、HTML页面分析
4.1 主页 (01.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> <!-- 设置中文编码 -->
<title>中文测试。。。。</title>
<style>
body {
background-color: #E6E6FA; /* 淡紫色背景 */
}
</style>
</head>
<body>
<!-- 基础HTML元素演示 -->
这里是测试body测试内容。。。
<h1>h1字体</h1>
<h3 align='center'>h3字体</h3> <!-- 居中对齐 -->
<h6 align='right'>h6字体</h6> <!-- 右对齐 -->
<!-- 段落和换行 -->
<p><h1>展望2026,中国房地产政 策路径已清晰明确...</h1></p>
<hr> <!-- 水平分割线 -->
<p>并首次将"控增量、<br><br>去库存、优供给"协同部署...</p>
<!-- 文本格式化 -->
<b>加粗字体</b>
<i>字体倾斜</i>
<del>删除文字</del>
<u>下划线</u>
<small>超小字体</small>
<br>h<sub>2</sub>0 <!-- 下标 -->
<br>100m<sup>2</sup> <!-- 上标 -->
<!-- 注音和标记 -->
<ruby>二姐 <rt>(er) (jie)<rt></ruby>
<mark>加黄色背景</mark>
<!-- 链接 -->
<a href="http://www.baidu.com">baidu</a>
<a href="02.html">go to 2</a>
<a href="http://www.taobao.com">
<img src="2.png"> <!-- 图片链接 -->
</a>
<!-- 图片 -->
<img src="1.jpg" alt="美女" width="200" height="200">
<!-- 无序列表 -->
<ul>
<li>列表1</li>
<li>列表2</li>
<li>列表3</li>
<li>列表4</li>
</ul>
<!-- 有序列表 -->
<ol>
<li>列表1</li>
<li>列表2</li>
<li>列表3</li>
<li>列表4</li>
</ol>
</body>
</html>
4.2 登录页面 (03.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>登录</title>
</head>
<body>
<!-- 登录表单,提交到/login路径 -->
<form action='login'>
用户名:<input type='text' name='username' required='required' placeholder='请输入qq账号'>
密码:<input type='password' name=' userpass' required='required' placeholder='QQ的密码'>
<input type='submit'> <!-- 提交按钮 -->
</form>
</body>
</html>
五、技术要点总结
5.1 网络编程核心
-
Socket编程流程:
socket()→bind()→listen()→accept()→recv()/send()→close()
-
HTTP协议处理:
-
解析请求行:
GET /path HTTP/1.1 -
构造响应头:状态行 + 头部字段 + 空行 + 正文
-
-
文件传输:
-
分块读取大文件(4096字节缓冲区)
-
根据文件扩展名设置正确的Content-Type
-
5.2 安全性考虑
-
输入验证:
// URL解析时防止缓冲区溢出 if (strlen(url) > 4 && 0 == strcmp(&url[strlen(url) - 4], ".jpg")) -
路径遍历防护:
send_file(conn, url + 1, FILE_JPG); // 跳过'/',防止访问上级目录
5.3 性能优化
-
短连接模式:
-
每个请求处理完毕后立即关闭连接
-
头部设置:
Connection: keep-closed
-
-
错误处理:
-
使用
perror()输出错误信息 -
失败的连接继续处理下一个请求
-
六、编译和运行
# 编译服务器程序
gcc 01ser.c -o webserver
# 以root权限运行(需要绑定80端口)
sudo ./webserver
# 访问测试
# 浏览器访问:http://localhost/
# 或使用curl:curl http://localhost/login?username=zhangsan&userpass=123
七、学习收获
通过本项目,可以学习到:
-
HTTP协议:了解请求/响应格式
-
网络编程:掌握基本的Socket编程
-
C语言实践:文件操作、字符串处理、内存管理
-
Web开发基础:HTML页面设计、表单处理
-
服务器架构:请求分发、资源管理