三:send_head报文头
给浏览器一个校准的头,不依靠浏览器的自身纠错能力
头部信息基本都是固定的,关注报文的响应报文,而不是请求报文
#include <asm-generic/socket.h>
#include <errno.h>
#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/stat.h>
#include <sys/types.h> /* See NOTES */
#include <time.h>
#include <unistd.h>
typedef struct sockaddr*(SA);
typedef enum
{
FILE_HTML,
FILE_PNG,
FILE_JPG
} FILE_TYPE;
int send_head(int conn, char* filename, FILE_TYPE type)
{
struct stat st;//用于获取文件大小
int ret = stat(filename, &st);
if (-1 == ret)
{
// perror("");
fprintf(stderr, "stat error , %s ,filename:%s\n", strerror(errno),
filename);
return 1;
}
char* httm_cmd[6] = {NULL};
char typebuf[100] = {0};
char lengbuf[50] = {0};
//响应行 表是可以正常响应
httm_cmd[0] = "HTTP/1.1 200 OK\r\n"; //200正常响应,
//状态码(Status-Code)都是三位数字的,分为5大类共33种。
//例如,1xx表示通知信息的,如请求收到了或正在进行处理。
//2xx表示成功,如接受或知道了。
//3xx表示重定向,如要完成请求还必须采取进一步的行动。 如304 比如照片无法正常缓存
//4xx表示客户的差错,如请求中有错误的语法或不能完成。 如404 :意思是找不到
//5xx表示服务器的差错,如服务器失效无法完成请求。
httm_cmd[1] = "Server: Tengine\r\n"; // web server名字
httm_cmd[2] = typebuf; // 告知对方浏览器,发送的文件的类型
switch (type)
{
case FILE_HTML://对应是那种类型,是活的
sprintf(typebuf, "Content-Type: text/html; charset=UTF-8\r\n");
break;
case FILE_JPG:
sprintf(typebuf, "content-type: image/jpeg\r\n");
break;
case FILE_PNG:
sprintf(typebuf, "Content-Type: image/png\r\n");
break;
}
// 告知对方浏览器,资源的大小
httm_cmd[3] = lengbuf;
sprintf(lengbuf, "Content-Length: %lu\r\n", st.st_size);
//连接方式 长连接 closed(短链接)
httm_cmd[4] = "Connection: keep-alive\r\n";
//资源的过期时间
httm_cmd[5] = "Date: Wed, 30 Jul 2025 03:21:39 GMT\r\n\r\n";
for (int i = 0; i < 6; ++i)
{
send(conn, httm_cmd[i], strlen(httm_cmd[i]), 0);
}
return 0;
}
int send_file(int conn, char* filename, FILE_TYPE type)
{
send_head(conn, filename, type);
int fd = open(filename, O_RDONLY);
if (-1 == fd)
{
perror("open");
return 1;
}
while (1)
{
char buf[1024] = {0};
int ret = read(fd, buf, sizeof(buf));
if (ret <= 0)
{
break;
}
send(conn, buf, ret, 0);
}
close(fd);
return 0;
}
int main(int argc, char** argv)
{
//监听套接字 功能检测是否有客户端 连连接服务器
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listfd)
{
perror("socket");
return 1;
}
int on = 1;
// man 7 socket 地址和断开可以多次绑定,这两个选项仅在测试时开启。
setsockopt(listfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
setsockopt(listfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
// man 7 ip
struct sockaddr_in ser, cli;
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
ser.sin_family = AF_INET;
// sudo ./s
ser.sin_port = htons(80);
// 代表本机地址 外部客户端可以连接到服务器
ser.sin_addr.s_addr = INADDR_ANY;
int ret = bind(listfd, (SA)&ser, sizeof(ser));
if (-1 == ret)
{
perror("bind");
return 1;
}
// 同一时刻可以服务器建立连接的排队数
listen(listfd, 3);
socklen_t len = sizeof(cli);
while (1)
{
// 1 建立连接
int conn = accept(listfd, (SA)&cli, &len);
if (-1 == conn)
{
perror("accept");
close(conn);
continue;
}
char buf[1024] = {0};
// 2接收客户端的请求
int rec_ret = recv(conn, buf, sizeof(buf), 0);
if (rec_ret <= 0)
{
close(conn);
continue;
}
printf("%s\n", buf);
// 3 处理请求+回应
// GET / HTTP/1.1\r\n
char* method = strtok(buf, " ");
//统一资源定为符
char* url = strtok(NULL, " ");
char* ver = strtok(NULL, "\r");
if (0 == strcmp(url, "/"))
{
send_file(conn, "./02.html",FILE_HTML); // login
}
else if (0 == strcmp(url, "/favicon.ico"))
{
send_file(conn, "./2.png",FILE_PNG); // login
}
// /login?username=zhangsan&userpw=123
else if (0 == strncmp(url, "/login", strlen("/login")))
{
char* name = strchr(url, '=');
name += 1;
char* end = strchr(name, '&');
*end = '\0';
char* pw = strchr(end + 1, '=');
pw += 1;
if (0 == strcmp(name, "zhangsan") && 0 == strcmp(pw, "123"))
{
//账号 ,密码正确 发送功能页面
send_file(conn, "./01.html",FILE_HTML);
}
else
{
//账号密码错误, 发送密码错误页面
send_file(conn, "./03.html",FILE_HTML);
}
}
else if (strlen(url) > 4 &&
0 == strcmp(&url[strlen(url) - 3], "png")) // /100.png
{
send_file(conn, url + 1,FILE_PNG);
}
else if (strlen(url) > 4 &&
0 == strcmp(&url[strlen(url) - 3], "jpg")) // /100.png
{
send_file(conn, url + 1,FILE_JPG);
}
// 4 断开连接
close(conn);
}
close(listfd);
// system("pause");
return 0;
}
五:关于项目中fprintf的一些说明
明白了!你的问题核心是:
fprintf(fp, " <td>%s</td>\n", result[i]);中,%s是怎么和result[i]对应起来的?
它为什么不需要你手动逐字符扫过去(像fgetc()那样)?是否可以详细解释一下fprintf()的工作原理?
一:fprintf() 函数详解
什么是 fprintf()
fprintf()是 C 语言中的格式化输出函数,用于将格式化的数据写入指定文件。- 它的工作原理和
printf()类似,但输出目标不同:printf()输出到标准输出(屏幕);fprintf(fp, ...)输出到文件指针fp指定的文件中。
c
复制代码
int fprintf(FILE *stream, const char *format, ...);
stream:文件指针,如fp;format:格式字符串;...:对应格式字符串中%占位符的参数。
二:fprintf() 的工作原理
格式字符串解析
fprintf() 会逐字符解析格式字符串:
c
复制代码
fprintf(fp, " <td>%s</td>\n", result[i]);
-
遇到普通字符,如空格、
<,t,d等,会直接写入文件。 -
遇到
%s,会自动用result[i]的内容替换。 -
整行内容最终写入文件,如:
css 复制代码 <td>hello</td>
自动处理字符串输出
%s表示输出一个字符串,fprintf()会从传入的char*地址开始,直到遇到\0结束。- 无需手动逐字符写入或格式拼接。
三:与其他写入函数的对比
| 函数 | 用途 | 优点 | 缺点 |
|---|---|---|---|
fprintf() | 格式化写入文件 | 自动格式化、最灵活 | 相对底层控制力弱 |
fputs() | 写入字符串 | 直接写字符串 | 不支持格式替换 |
fputc() | 写入单字符 | 精确控制 | 需要逐字符拼接 |
fgetc() | 读取单字符 | 用于读取,不写入 | 与写操作无关 |
四:常用格式符号
| 格式符 | 含义 | 示例值 |
|---|---|---|
%s | 字符串 | "hello" |
%d | 十进制整数 | 123 |
%f | 浮点数 | 3.14 |
%c | 单个字符 | 'A' |
%% | 百分号本身 | % |
1290

被折叠的 条评论
为什么被折叠?



