前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简单的 c 语言实现 http 请求

简单的 c 语言实现 http 请求

原创
作者头像
ge3m0r
发布2024-04-28 23:14:23
950
发布2024-04-28 23:14:23

http 协议

http 协议基本算是网络的基础了,因此长话短说,直接上代码。

首先 http 协议一般需要 dns 协议的配合向服务端发送请求,因此首先需要解析 IP 地址。c 语言中其实有专门的解析函数。

代码实现

代码语言:c
复制
#include <netdb.h>
#include <arpa/inet.h>

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[0]);
    }
    return NULL;
}

特意加上了头文件,其中 gethostbyname 这个函数是头文件 netdb.h 中的函数。他返回了一个结构体,具体结构体代码如下:

代码语言:c
复制
struct hostent {
	char	*h_name;	/* official name of host */
	char	**h_aliases;	/* alias list */
	int	h_addrtype;	/* host address type */
	int	h_length;	/* length of address */
	char	**h_addr_list;	/* list of addresses from name server */
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
#define	h_addr	h_addr_list[0]	/* address, for backward compatibility */
#endif /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
};

其中 h_addr_list 是保存着 IP 地址,只不过这个地址不是我们常见的那种 192.168.1.1 之类的地址,所以我们需要 inet_ntoa 函数进行一个转换。

然后就是一个常规的 http 请求发送,然后返回 response,不过在这之前我们为了缩减代码先使用一个生成 socket 的函数

代码语言:c
复制
#include <fcntl.h>

int http_create_socket(char* ip)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);  //tcp socket
    
    struct sockaddr_in sin = {0};
    sin.sin_family = AF_INET;
    sin.sin_port = 80;
    sin.sin_addr.s_addr = inet_addr(ip);  //配置信息
    
    if(0 != connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr)))// 连接服务器
    {
        return -1;
    }
    fcntl(sockfd,  F_SETFL, O_NONBLOCK); //非阻塞
    
    return sockfd;
    
}

这里有一个阻塞的概念,阻塞简单就是当我们的线程进行活动需要一些资源,如果当前资源不满足那么就有两种方式,一种是我等着,等条件满足了,我再进一步执行,一般是像加锁之类的,另一种就是条件不行,我直接报错,一分钟也不等了,这就是非阻塞,这里我们的业务简单直接非阻塞。

最后就是我们的最后内容,发送请求。

代码语言:c
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/select.h>

#define BUF_SIZE 4096
#define HTTP_VERSION "HTTP/1.1"
#define CONNECTION_TYPE "Connection: close\r\n"

char* http_send_request(const char* hostname, const char* resourse)
{
    char* ip = host_to_ip(hostname);  //通过域名解析ip
    int sockfd = http_create_socket(ip); //创建socket
    char buffer[BUF_SIZE] = {0};
    sprintf(buffer,
"GET %s %s\r\n\
Host: %s\r\n\
%s", resourse, HTTP_VERSION, hostname, CONNECTION_TYPE); //将协议头写入buffer
    send(sockfd, buffer, strlen(buffer), 0);  //发送
    
    //多路复用 收集多个文件描述符
    fd_set fdread;         //描述符集合
    FD_ZERO(&fdread);//设置为 0
    FD_SET(sockfd, &fdread); //将打开的描述符加入集合中
    
    struct timeval tv;
    tv.tv_sec = 5;     //设置多路复用的超时时间 秒级别
    tv.tv_usec = 0;  //微秒级别
    
    char* result = (char *)malloc(sizeof(int));  //开始四个自己的result
    
    while(1)
    {
        int selection = select(sockfd + 1, &fdread, NULL, NULL, &tv);  //使用select多路复用
        if(!selection || !FD_ISSET(sockfd, &fdread)) //设置多个fd进程
        {
            break;
        }else{
            memset(buffer, 0, BUF_SIZE);  设置buffer
            int len = (int)recv(sockfd, buffer, BUF_SIZE, 0); //接受字节
            if(len == 0){
                break;
            }
            
            result = realloc(result, (strlen(result) + len + 1) * sizeof(char)); //重新分配result
            strncat(result, buffer, len); //将接受内容加入result
        }
    }
    return result;
}

这里其他部分都比较简单,最大不同就是使用了 select I/O多路复用。我们知道 I/O 多路复用有 select, poll, epoll 三种类型,基本也是面试必考类型。

这里简单介绍一下,多路复用就是让一个进程可以处理多个发生事件,防止我们发生一件事情就创建一个进程,然后事件完了之后我们销毁,这种对我们系统性能损耗太大,其实之前的线程池也有类似作用。

线程池是系统创建的进程集中起来,来了一个事件之后我们就取出一个线程处理,而多路复用是我们把事件集中起来,然后我们通过一个线程挨个处理这一堆事情。

select 就是最简单多路复用,就是将 sockfd 也就是一个个的 socket 或者文件描述符集中在一起处理,每个请求来了之后,我们去处理。

poll 跟 select 原理一样,不过就是原来用位图存储文件描述符改成了链表,位图我们知道受计算机的位数限制,文件描述符可以存更多了。

epoll 相对来说提升更多,各种存储结构变化了。我们在应用层要使用可以这样写

代码语言:c
复制
int main() {
    ...//创建socket之类
    int epfd = epoll_create(...);  //创建一个epfd
   epoll_ctl(epfd, ...);  //将请求的描述符添加到 epfd
   while(1){
       nfds = epoll_wait(epoll_fd, ...); //等待
       for(...){ //寻找发生时间的fd

       }
   }

跟 select 和 poll 不同的是,epoll 使用的是红黑树来保存请求描述符,同时有时间发生的时候,会通过回调函数将事件发送到链表,方便了查找。在这方面后边可以进一步探究,今天就到这里了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • http 协议
  • 代码实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档