Back
Featured image of post 简易http客户端(C posix API)

简易http客户端(C posix API)

基础知识

HTTP

实现http客户端程序

基础

HTTP使用TCP连接

HTTP报文:

实现

域名到ip地址转换(dns) 直接调用api进行转换比较简单:

char * host_to_ip(const char* hostname)
{
    struct hostent *host_entry = gethostbyname(hostname);

    if(host_entry)
    {
        return inet_ntoa(*(struct in_addr*)*host_entry -> h_addr_list);
    }
    return NULL;
}

host_entry存储了dns请求的接收,从中取出第一个ip地址并将点分十进制转换为字符串返回

创建TCP套接字(建立连接) posix api创建

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);
    sin.sin_addr.s_addr = inet_addr(ip);

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

    return sockfd;
}

fcntl(sockfd, F_SETFL, O_NONBLOCK);这个函数用于设置该套接字io为非阻塞

通过套接字向目标网站请求资源(select)

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 

	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) {

		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) { // disconnect
				break;
			}

			result = realloc(result, (strlen(result) + len + 1) * sizeof(char));
			strncat(result, buffer, len);
		}

	}

	return result;
}

select部分: 首先根据套接字初始化fread来监听io,如果有消息到来就置为1,调用select函数: select(sockfd, &rset, &wset, *eset, *tv); &rset位置表示读监听io &wset位置表示写监听io &eset位置表示错误监听io(断开或者其他) tv为轮询间隔时间 select函数内部轮询监听这几个io,有置1就说明有信息需要处理,就返回然后处理信息 断开连接的话返回0,所以if (!selection || !FD_ISSET(sockfd, &fdread))可以有效控制连接断开的break 正常时返回收到的结果result

附main函数

int main(char argc, char*argv[])
{
    if(argc <3)
    {
        return -1;
    }
    char *response = http_send_request(argv[1], argv[2]); 
    printf("response: %s\n", response);
    free(response);
    return 1;
}  
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy