报文头 和fprint的说明 day45

三: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]);
  1. 遇到普通字符,如空格、<, t, d 等,会直接写入文件。

  2. 遇到 %s,会自动用 result[i] 的内容替换。

  3. 整行内容最终写入文件,如:

    css
    
    
    复制代码
    <td>hello</td>
    

自动处理字符串输出

  • %s 表示输出一个字符串,fprintf() 会从传入的 char* 地址开始,直到遇到 \0 结束。
  • 无需手动逐字符写入或格式拼接。

三:与其他写入函数的对比

函数用途优点缺点
fprintf()格式化写入文件自动格式化、最灵活相对底层控制力弱
fputs()写入字符串直接写字符串不支持格式替换
fputc()写入单字符精确控制需要逐字符拼接
fgetc()读取单字符用于读取,不写入与写操作无关

四:常用格式符号

格式符含义示例值
%s字符串"hello"
%d十进制整数123
%f浮点数3.14
%c单个字符'A'
%%百分号本身%
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值