网络学习-利用reactor实现http请求(六)

一、实现HTTP请求

1、印象里面,总有人说C/C++语言不能实现HTTP请求,其实不然。C/C++语言完全可以实现HTTP请求。通过对select,poll,epoll等IO多路复用技术的学习以及reactor模式的学习,完全能够实现HTTP请求。

2、webserver

主要解决两个问题

1、请求数据

2、响应,回发数据

3、简单小测试

c++ 复制代码
/**
 *向服务器发送HTTP请求
 */
int Http_Request(Conne *c)
{
    cout<<"Http_Request:"<<c->rbuffer<<endl;
    return 0;
}

/**
 * 处理HTTP响应
 */
int Http_Response(Conne *c)
{
    cout<<"Http_Response:"<<c->wbuffer<<endl;
    return 0;
}

/*在reactor的那一套回调函数的基础上,添加接收到请求数据后,调用Http_Request,在回发数据之前,调用下Http_Response*/
int Recv_cb(int fd)
{
    int count = recv(fd, conn_poll[fd].rbuffer, BUFFER_SIZE, 0);
    if (count == 0)
    {
        cout << "client close" << endl;
        close(conn_poll[fd].fd);                                // 关闭客户端的连接描述
        epoll_ctl(fd, EPOLL_CTL_DEL, conn_poll[fd].fd, NULL); // 将客户端的连接描述符从epoll实例中删除
        return 0;
    }
    cout << "recv_buffer:" << conn_poll[fd].rbuffer << endl;

    Http_Request(&conn_poll[fd]);           //接收到数据后,进行解析请求数据

    conn_poll[fd].wlen = count;
    memcpy(conn_poll[fd].wbuffer, conn_poll[fd].rbuffer, count);

    SetEvent(fd, EPOLLOUT,0); //监听可写事件

    return count;
}

int Send_cb(int fd)
{
    Http_Response(&conn_poll[fd]);          // 在回发数据之间,响应数据,解析响应数据
    // 返回信息
    int count = send(fd, conn_poll[fd].wbuffer, conn_poll[fd].wlen, 0);

    SetEvent(fd, EPOLLIN,0); //监听可读事件

    return count;
}

客户端连接:

浏览器连接:

4、可以看到连接成功,但浏览器这边空空如也,添加点东西

c++ 复制代码
int Http_Response(Conne *c)
{
    time_t t = time(NULL);
    struct tm *local_time = localtime(&t);

    c->wlen = sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=UTF-8\r\n"
        "Accept-Ranges: bytes\r\n"
        "Content-Length: 82\r\n"
        "Date: %s\r\n"
        "<html><head><title>Hello</title></head><body><h1>LengYa</h1></body></html>\r\n", ctime(&t));
    return 0;
}

5、C/C++里面写标签,太麻烦了,换成html文件,直接读取文件内容。

c++ 复制代码
int Http_Response(Conne *c)
{
    time_t t = time(NULL);
    struct tm *local_time = localtime(&t);

    int filefd = open("index.html", O_RDONLY);

    struct stat stat_buf;
    fstat(filefd, &stat_buf);

    c->wlen = sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=UTF-8\r\n"
        "Accept-Ranges: bytes\r\n"
        "Content-Length: %ld\r\n"
        "Date: %s\r\n", stat_buf.st_size,ctime(&t));
    int count = read(filefd, c->wbuffer + c->wlen, BUFFER_SIZE-c->wlen);
    c->wlen += count;
    close(filefd);
    return 0;
}

6、压力测试

工具准备:wrk

shell 复制代码
#c:连接
#t:线程
#d:持续时间
./wrk -c 10 -t 2 -d 30s http://192.168.127.132:2000/

结果:

在30.07s内,总共发送了168276个请求,总共读取91.47MB数据;平均每秒发送5595.89个请求,平均每秒读取3.04MB数据。

7、小结

通过上面的测试,可以发现,C/C++语言完全可以实现HTTP请求。只不过相对于专门处理web的java,c#,php等语言,在处理HTTP请求上,显得笨拙了些。

毕竟C/C++在处理业务逻辑上,不是强项,在处理底层,性能调优上才是强项。

二、拓展

1、请求图片数据

之前请求的html文本数据,而且数据量不大,这次换下个数据量大的,比如图片。

c++ 复制代码
int filefd = open("test.jpg", O_RDONLY);                        // 打开文件


struct stat stat_buf;
fstat(filefd, &stat_buf);

c->wlen = sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\n"
            "Content-Type: image/jpeg; charset=UTF-8\r\n"       //请求类型
            "Accept-Ranges: bytes\r\n"
            "Content-Length: %ld\r\n"
            "Date: %s\r\n", stat_buf.st_size,ctime(&t));

可以发现,图片数据量很大,基本没加载出来,毕竟代码中写的缓冲区大小就只有1024字节,远远不够。

如果要加载完图片,有两种思路:

1、增大缓冲区大小,让其足够大。

但多少才算是足够大呢,每次发现不够,需要重新修改代码,内测倒是可以,上线的话就麻烦了。

所以这个方法,不推荐。

2、分段发送,每次只发一小部分。

每次只发送一小部分,直到全部发送完毕。

c++ 复制代码
/*
原来设置1024的缓冲区大小,如果数据量为10*1024字节,可以设置缓冲区大小为10*1024
也可以不必变更原来的大小,循环10次,每次发送1024字节,也能达到同样的效果
*/
c++ 复制代码
/*
accept_cb----->Recv_cb----->Send_cb----->recv_cb----->Send_cb---->...
IO连接成功----->接收部分数据----->回发部分数据---->接收部分数据----->回发部分数据---->...---->数据全部接收完毕--->全部数据发送完毕
如何让其自动循环接收,发送数据,可以使用循环,通过计算文件大小,除以缓冲区大小,计算出需要循环的次数。
也可以设置状态,让其自动循环接收,发送数据。
*/
c++ 复制代码
int status;       //0--发送头,1--发送body,2--关闭连接

//Http请求中初始化状态
int Http_Request(Conne *c)
{
    cout<<"Http_Request:"<<c->rbuffer<<endl;

    memset(c->rbuffer, 0, BUFFER_SIZE);
    c->wlen = 0;
    c->status = 0;

    return 0;
}

//Http响应中,根据状态机,分段发送数据
int Http_Response(Conne *c)
{
    time_t t = time(NULL);
    struct tm *local_time = localtime(&t);

    int filefd = open("test.jpg", O_RDONLY);

    struct stat stat_buf;
    fstat(filefd, &stat_buf);

    if(c->status == 0){
        c->wlen = sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\n"
            "Content-Type: image/jpeg; charset=UTF-8\r\n"
            "Accept-Ranges: bytes\r\n"
            "Content-Length: %ld\r\n"
            "Date: %s\r\n", stat_buf.st_size,ctime(&t));
        c->status = 1;
    }else if(c->status == 1){
        int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);  //数据拷贝
        if(ret < 0){                //出错处理
            cout << "sendfile error:" << strerror(errno) << endl;
            return -1;
        }
        c->status = 2; //发送完成,不再继续发送文件内容(防止重复发送
    }else if(c->status == 2){
        c->wlen = 0;
        memset(c->wbuffer, 0, BUFFER_SIZE); //清空缓冲区,防止重复发送
        c->status = 0; //发送完成,重置状态机
    }

    close(filefd);
    return 0;
}
int Send_cb(int fd)
{
    Http_Response(&conn_poll[fd]);
    // 返回信息
    int count = 0;
    if (conn_poll[fd].status == 1)
    {
        count = send(fd, conn_poll[fd].wbuffer, conn_poll[fd].wlen, 0);

        SetEvent(fd, EPOLLOUT, 0); // 监听可写事件
    }
    else if (conn_poll[fd].status == 2)
    {
        SetEvent(fd, EPOLLOUT, 0); // 监听可写事件
    }
    else if (conn_poll[fd].status == 0)
    {
        SetEvent(fd, EPOLLIN, 0); // 监听可读事件
    }

    return count;
}

2、视频流

可惜视频流失败,大体思路也是分段,但不可和文本、图片的资源一样看待,后续有时间再研究。

三、总结

1、C/C++可以实现HTTP请求,但相对于专门处理web的java,c#,php等语言,显得笨拙。

2、如果要实现高性能的服务器,C/C++是首选。

3、对于频繁接收部分数据,发送部分数据的场景,分段处理是个不错的选择;状态机应该优先考虑。

4、状态机使得代码逻辑更加清晰,便于扩展,更容易处理错误。

5、循环不易于错误处理,且代码会变得更加复杂和难以理解。

Code:
代码链接

相关推荐
兔子坨坨37 分钟前
IDEA连接github(上传项目)
java·git·学习·github
Brookty1 小时前
【MySQL】数据库约束
数据库·后端·学习·mysql
小石(努力版)1 小时前
嵌入式STM32学习——串口USART 2.0(printf重定义及串口发送)
stm32·嵌入式硬件·学习
mpr0xy1 小时前
在离线 OpenEuler-22.03 服务器上升级 OpenSSH 的完整指南
运维·服务器·网络·openeuler·openssh
虾球xz2 小时前
游戏引擎学习第302天:使用精灵边界进行排序
c++·学习·算法·游戏引擎
虾球xz2 小时前
游戏引擎学习第297天:将实体分离到Z层中
c++·人工智能·学习·游戏引擎
奈何不吃鱼3 小时前
【Redis】二、Redis常用数据类型命令学习
java·redis·学习
夏季疯3 小时前
学习笔记:黑马程序员JavaWeb开发教程(2025.4.9)
java·笔记·学习
光头小小强0073 小时前
BurpSuite学习安装
学习
长勺3 小时前
【JVM】学习笔记
jvm·笔记·学习