实现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读取完毕打破循环退出。

相关推荐
2501_915106329 小时前
抓包抓到乱码或压缩字节怎么解析,TraceEagle五种查看模式和一键解码
网络协议·计算机网络·网络安全·ios·adb·https·udp
情 九9 小时前
MCP 实战 06:把 HTTP 接口接成工具,让 AI 帮你查数据
人工智能·网络协议·http·mcp
Da Da 泓11 小时前
五子棋 Web 端测试用例报告
前端·网络·websocket·网络协议·测试用例·项目
发光小北13 小时前
EtherNet/IP 转 ModbusTCP 网关现场应用中会遇到什么问题?
服务器·网络·tcp/ip
开源必胜15 小时前
Socket、异步非阻塞 I/O 与 NGINX、Netty 模型
网络·tcp/ip·nginx
沫璃染墨15 小时前
从零入门计算机网络系列(十一):HTTP协议进阶——从重定向原理到GET/POST与Fiddler抓包
linux·服务器·开发语言·网络·c++·tcp/ip·html
箓维17 小时前
TCP Socket的进阶实现
网络·笔记·tcp/ip
星恒随风18 小时前
Linux网络编程:应用层协议究竟是什么?从网络计算器到TCP Socket抽象基类完整实现
linux·网络·笔记·tcp/ip
万联WANFLOW18 小时前
异地组网速度忽快忽慢到底怎么回事?直连与中继的判断方法和处理步骤
运维·网络·网络协议·p2p
步行cgn20 小时前
HTTP 请求报文结构详解:请求行、请求头、请求体各自的作用
网络·网络协议·http