如果您比较有耐心,建议从头至尾读完这篇文章。如果您只想快速应用 C 语言的 HTTP GET 连接功能,可以直接跳到文末拷贝源代码去使用。
1、HTTP 连接的流程
HTTP 连接都是建立在 TCP 连接之上的。这里我们不讨论 TCP 的三次握手四次挥手过程。我们只单纯地来分析下一个 HTTP 连接的过程应该是怎样的。
首先,我们需要创建一个 TCP 的 Socket 。后面我们的网络连接操作都是基于这个 Socket 来构建的。
其次,我们需要来组装一下 HTTP 请求。就是封装一个 GET 请求,表明一下我们想要连接哪个服务器的哪些资源。当然,其实第 1 步和第 2 步的顺序并不重要。
第三步,我们需要发送 HTTP GET 请求了,将前面封装好的请求信息通过前面创建好的 TCP 通道发送出去。
第四步,读取服务端的返回结果。
2、代码实操
1、创建 Socket
int sockfd; //创建套接字 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { printf("socket failed!!!\n"); exit(0); }
没什么好说的,照着做就好了。
2、封装 HTTP 请求
char request[512] = {0}; memset(request, 0, 512); strcat(request, "GET "); strcat(request, "/index.html"); strcat(request, " HTTP/1.1\n"); strcat(request, "Host: "); strcat(request, "192.168.221.30"); strcat(request, "\nContent-Type: text/html\n"); strcat(request, "Content-Length: 0\n"); strcat(request, "\r\n");
上面加粗标灰底的部分分别是要访问的资源路径以及服务器地址。其中服务器地址并不是很重要,但是上面的资源路径一定不能错!
3、发起 HTTP 请求
struct sockaddr_in servaddr; int writeRet; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(80); if (inet_pton(AF_INET, "192.168.221.30", &servaddr.sin_addr) <= 0 ){ printf("inet_pton error!\n"); exit(0); } if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){ printf("connect error!\n"); exit(0); } writeRet = write(sockfd, request, strlen(request)); if (writeRet < 0) { printf("make http error:%d,%s\n", errno, strerror(errno)); exit(0); }
上面加粗标灰底的地址就很重要了,一定不能填错,而且只能填 IP 地址。那个部分的 inet_pton 函数的作用是将字符串形式的 IPV4 地址转换成二进制形式的。如果是 IPV6 地址,则要用 inet_ntop 函数。
4、读取返回结果
struct timeval tv; int selectRet = 0; fd_set t_set1; sleep(2); tv.tv_sec= 0; tv.tv_usec= 0; FD_ZERO(&t_set1); FD_SET(sockfd, &t_set1); selectRet = select(sockfd + 1, &t_set1, NULL, NULL, &tv); if (selectRet < 0) { close(sockfd); printf("select failed!\n"); return; } if (selectRet > 0){ char buf[4096] = {0}; int readLen = 0; memset(buf, 0, 4096); readLen = read(sockfd, buf, 4095); // read once only! printf("readLen:%d\n", readLen); printf("\n\n%s\n\n", buf); }
上示代码最后会将读取的结果保存到 buf 数组中并打印出来。
5、关闭 Socket
close(sockfd); printf("Bye!\n");
最后,用完 HTTP 通信以后,一定不要忘记关闭刚才打开的资源。
笔者这边通过 nginx 搭建了一个模拟服务器,与这份代码调试,一切正常。可以在控制台上得到如下回复
hello world-------------GET /index.html HTTP/1.1Host: 192.168.221.30Content-Type: text/html Content-Length: 0>>>>>>> success with 90 byte(s) <<<<<<<readLen:850HTTP/1.1 200 OK Server: nginx/1.16.0Date: Wed, 08 May 2019 06:13:27 GMT Content-Type: text/html Content-Length: 612Last-Modified: Tue, 23 Apr 2019 13:09:07 GMT Connection: keep-alive ETag: "5cbf0e73-264"Accept-Ranges: bytes<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; }</style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to<a href="http://nginx.org/">nginx.org</a>.<br/>Commercial support is available at<a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>Bye!
再接下来,就是根据自己的实际需要,去做业务层的处理了。
3、完整代码
#include <stdio.h>#include <stdlib.h>#include <sys/socket.h>#include <string.h>#include <netinet/in.h>#include <errno.h>#include "http.h"void main() { printf("hello world\n"); // step 1 , create socket int sockfd; //创建套接字 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { printf("socket failed!!!\n"); exit(0); } // step 2, package the http request char request[512] = {0}; memset(request, 0, 512); strcat(request, "GET "); strcat(request, "/index.htm"); strcat(request, " HTTP/1.1\n"); strcat(request, "Host: "); strcat(request, "www.baidu.com"); strcat(request, "\nContent-Type: text/html\n"); strcat(request, "Content-Length: 0\n"); strcat(request, "\r\n"); printf("-------------\n%s\n",request); // step 3, connect and send http request. struct sockaddr_in servaddr; int writeRet; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(80); if (inet_pton(AF_INET, "192.168.221.30", &servaddr.sin_addr) <= 0 ){ printf("inet_pton error!\n"); exit(0); } if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){ printf("connect error!\n"); exit(0); } writeRet = write(sockfd, request, strlen(request)); if (writeRet < 0) { printf("make http error:%d,%s\n", errno, strerror(errno)); exit(0); } printf(">>>>>>> success with %d byte(s) <<<<<<<\n", writeRet); // step 4, read response struct timeval tv; int selectRet = 0; fd_set t_set1; sleep(2); tv.tv_sec= 0; tv.tv_usec= 0; FD_ZERO(&t_set1); FD_SET(sockfd, &t_set1); selectRet = select(sockfd + 1, &t_set1, NULL, NULL, &tv); if (selectRet < 0) { close(sockfd); printf("select failed!\n"); return; } if (selectRet > 0){ char buf[4096] = {0}; int readLen = 0; memset(buf, 0, 4096); readLen = read(sockfd, buf, 4095); // read once only! printf("readLen:%d\n", readLen); printf("\n\n%s\n\n", buf); } close(sockfd); printf("Bye!\n"); }