实现TCP发送HTTP请求

HTTP协议是最被广泛使用的网络传输协议,所有www文件都必须遵守这个标准,并且,HTTP基于TCP/IP通信协议来传递数据,本文的主要内容是介绍HTTP请求的的格式,并且使用C语言以TCP的方式来实现请求的发送。

创建套接字

HTTP协议是应用层的协议,而其传输接口层的实现需要通过创建套接字。

c 复制代码
int http_create_socket(char *ip) {

  int sockfd = socket(AF_INET, SOCK_STREAM, 0);

  struct sockaddr_in sin = {0};
  sin.sin_family = AF_INET;
  sin.sin_port = htons(80);//http协议默认是80端口
  sin.sin_addr.s_addr = inet_addr(ip);//将char*转化为无符号int

  if (0 != connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
    return -1;
  }

  fcntl(sockfd, F_SETFL, O_NONBLOCK);//设置为非阻塞
  
  return sockfd;
}

关键技术阐述:上述代码的第3行调用socket函数用以创建套接字描述符,其中第一个参数指定使用IPv4, 第二个参数SOCK_STREAM指定后,默认使用TCP(而不是UDP)协议进行传输,第三个参数一般是具体协议选择,设置为0让系统自行决定即可。

5~12行为相对固定的编程套路,填充好套接字地址sockaddr_in这个结构体的相关内容,与本文相关唯一值得注意的点是,HTTP默认使用80端口。

第14行使用fcntl(file control函数)将连接设置为非阻塞。如果socket是阻塞的,read()的时候整个程序挂起,等待io数据到来,反之则不会。使用到的时候查询下这个函数的参数如何设置即可,不需要特别记忆。

HTTP请求报文的一般格式

发送http请求的函数实现如下
c 复制代码
char* http_send_request(const char *hostname, const char *resource) {

  char *ip = host_to_ip(hostname);
  int sockfd = http_create_socket(ip);

  char buffer[BUFFER_SIZE] = {0};
  sprintf(buffer,
	  "GET %s %s\r\n\
	  Host: %s\r\n\
	  %s\r\n\
	  \r\n",
	  resource, HTTP_VERSION,
	  hostname,
	  CONNECTION_TYPE
	  );

  send(sockfd, buffer, strlen(buffer), 0);

  //select负责检测网络io里面有没有可读的数据
  fd_set fdread;
  FD_ZERO(&fdread); //描述符集置空
  FD_SET(sockfd, &fdread);

  struct timeval tv;
  tv.tv_sec = 5;
  tv.tv_usec = 0;

  char *result = malloc(sizeof(int));
  memset(result, 0, sizeof(int));
  
  while (1) {
    //用法:select(maxfd+1, &rset, &wset, &eset, NULL);
    int selection = select(sockfd+1, &fdread, NULL, NULL, &tv);
    if (!selection || !FD_ISSET((sockfd), &fdread)) {
      break;
    } else {
      memset(buffer, 0, BUFFER_SIZE);
      int len = recv(sockfd, buffer, BUFFER_SIZE, 0);
      if (len == 0) {
	break;
      }

      result = realloc(result, (strlen(result) + len + 1));
      strncat(result, buffer, len);
    }
  }
  return result;
}

关键技术阐述:第6~15行:像TCP这种协议,数据的传输是一串一串的发送和到来的,更准确的描述是数据流,所以为了应对数据流断断续续到达的情况,需要使用到缓冲区buffer。而http请求内容符合我们上面那张图的格式即可。这里我们为了可读性和可维护性,使用了两个define:

复制代码
#define HTTP_VERSION "HTTP/1.1"
#define CONNECTION_TYPE "Connection: close\r\n"

19~22行,之前讲过,本文中采用的是非阻塞的实现(read的时候不会挂起,系统继续其他的工作),所以我们可以搭配使用select函数(用于监听socket描述符)实现事件驱动,避免了忙轮询占用大量系统资源。有关细节可以看我的另一篇文章:CSAPP网络编程章节实验:Proxylab

第24~26行设置了一个超时的时间为5s,超过这个时间断开连接。避免无意义的等待。

第31~41行用while(1)循环反复读取服务器响应的内容,直到len等于0读取完毕打破循环退出。

相关推荐
发光小北6 小时前
四路can转WIFI在现场应用中有什么问题?
网络协议
天启HTTP8 小时前
爬虫防封核心原理:IP轮换机制与风控规避逻辑
linux·服务器·网络·爬虫·tcp/ip
长按助力退休13 小时前
HTTP缓存从强缓存到协商缓存,前端性能优化第一步
http
深圳恒讯14 小时前
海外服务器延迟高怎么解决?从线路到TCP协议的6层排查清单
运维·服务器·tcp/ip
盛世宏博北京17 小时前
工业环境稳定采集:配电室 IP 温湿度变送器 TCP/UDP 通信部署指南
单片机·tcp/ip·udp
smile00217 小时前
Modbus RTU/TCP通信测试怎么做?从协议要点到自动化回归的完整实践
tcp/ip·回归·自动化
刘马想放假17 小时前
网络链路备份与高可用技术详解:从 Dual-WAN、LACP 到 SD-WAN 与 MPTCP
网络协议
Zelman18 小时前
第07章-数据中心网络
后端·网络协议
马六六i20 小时前
市面上专业的IP驱动产业新场景新工具哪家好
网络·人工智能·python·tcp/ip
网络小江21 小时前
上网第四十八课:抓包实战入门——Wireshark 读懂第一个包
网络协议