网络编程第四课 http与websocket

http实现与websocket

预备知识

1.水平触发和边沿触发

水平触发是只要被监听的事件是可读状态,就会触发相应回调函数;

边沿触发是只有新的状态改变,才会触发相应回调函数。

换个方式理解

对于水平触发,epoll监听的状态,如果一直是可读状态,就会一直调用回调函数;如果是边沿触发状态,只有状态改变,才会调用回调函数;

举例:

每次建立一个tcp连接,会有相应的clientfd文件描述符生成,假设此时客户端发送100个字符的数据,服务端的内核会把这100字符存放在buffer缓冲区中。当epoll监听到该clientfd可读时,就会作为事件驱动去调用回调函数,假设回调函数每次只能读取10个字符,那么本次回调函数只能获取前10个字符,结束本次回调。此时内核缓冲区中还剩下90个字符。

进入下一轮循环后,epoll如果是水平触发的状态,那么操作系统内核看到缓冲区还有字符,就会继续作为事件驱动调用回调函数再次读取接下来10个字符。直到循环10次,才能把第一次客户发送的数据读取完毕。

如果是边缘触发状态,即使进入了下一轮,内核不会因为缓冲区有数据而作为事件驱动,所以不会调用回调函数读取缓冲区的数据,只有当再一次客户端发送数据时,此时操作系统内核才会把该事件作为驱动,去调用回调函数,此时虽然客户端发送了两次数据,但是服务器才仅仅把第一次的第11个字符到第20个字符读取出来。

当然,如果使用边缘触发,可以在读取的时候采用while循环,这样就等同于水平触发了。

2.sprintf函数

把格式化的字符串写入到相应的字符串中。

cpp 复制代码
c->wlength = sprintf(c ->wbuffer, 
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html\r\n"
    "Accept-Ranges: bytes\r\n"
    "Content-Length:82\r\n"
    "Date: Tue,30 Apr 2024 13:16:46 GMT\r\n\r\n"
    "<html><head><title>0voice.king</title></head><body><h1>King</h1></body></html>\r\n\r\n");

把后面的字符串写入到wbuffer里面去,代表http响应的数据,相当于客户端是读取这部部分相应数据的。

相应部分的数据包含两部分:数据头和数据体

cpp 复制代码
状态行: HTTP/1.1 200 OK
表示使用 HTTP/1.1 协议,响应状态为 "200 OK",即请求成功。

头部字段:
Content-Type: text/html 表示返回内容类型为 HTML 文档。

Accept-Ranges: bytes 表示支持字节范围请求。客户端可以请求部分文件。

Content-Length: 82 指明响应体的字节长度为82。这个值应该与实际返回内容的长度相对应。

Date: Tue, 30 Apr 2024 13:16:46 GMT 当前时间,表明该响应被生成时的日期和时间。

主体内容:
html
<html><head><title>0voice.king</title></head><body><h1>King</h1></body></html>
包含 HTML 内容,用于在网页上显示"King"标题。

3.stat结构体与stat()函数

stat 结构体用于获取文件的状态信息,例如文件大小、权限、创建时间等。它通常用于系统调用 stat()fstat()lstat() 等,以便于在程序中获取关于文件或目录的详细信息。它们的主要功能是填充一个 stat 结构体,该结构体包含有关文件或目录的详细信息,如大小、权限、时间戳等。以下是对这三个函数的详细解释:

cpp 复制代码
#include<stat.h>

struct stat stat_buff;

//调用fstat函数,把stat_buff结构体的内容填充完成,第一个参数是文件的fd
fstat(filefd, &stat_buff);

//调用stat函数,把stat_buff结构体的内容填充完成,第一个参数是文件的路径
stat("path", &stat_buff);

4.文件操作

open() close() read() write()

fopen() fclose() fread() fwrite()

第一组更倾向于底层的读取,更灵活;第二组更倾向于高层的读写,较为方便。

http

在之前的课程中完成了事件驱动,现在需要在原有逻辑上,实现一个http的事件驱动:如果有http请求,就返回相应内容。

相当于把之前的在回调函数里实现应用层的内容

在之前的课程中,实现了事件的驱动,即当有fd可读或可写状态时,直接调用相关的回调函数。在此基础上,通过应用层,以http为基础,实现一个webserver。当在浏览器输入地址敲回车后,服务器检测到有可读事件,调用

request()函数。当服务器内核检测到有可写事件时,调用response()函数。实现了简单的webserver,如下图:

代码如下:

cpp 复制代码
int send_cb(int fd) {
	http_response(&conn_list[fd]);

    //http头和体一起发送
	int count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
    //发送完直接修改下一轮的监听状态
	set_event(fd, EPOLLIN, 0);

	return count;
}

新版本代码的逻辑在于,服务器epoll检测到可读后调用request,随后把监听事件event设为可写事件,当下一轮循环时监听到她可写,就执行response函数。在执行response时,对于http而言,需要发送两个内容,一个是HTTP头,一个是http体。但是在实际开发过程中,往往需要将这两个写入的事件分开,即服务器需要连着进行两次写操作,第一次写完成后,在下一轮监听之前,还是监听其epollout状态,所以上一版代码需要改进。

于是引入了状态机的概念,状态机就是在event_rigister结构体中,添加一个整型变量,用不同的值代表不同的状态,决定下一轮开始监听之前,相应的事件是哪种事件:

修改后的代码如下:

cpp 复制代码
int sent_cb(int fd){
    http_response(&conn_list[fd]);

    int count = 0;
    if(conn_list[fd].status == 1){
        count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
        set_event(fd, EPOLLOUT, 0);
    }
    else if(conn_list[fd].status == 2){
        set_event(fd, EPOLLOUT, 0);      //保持下一轮继续监听EPOLLOUT
    }
    else if(conn_list[fd].status == 0){
        if(conn_list[fd].wlength != 0){
            count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
        }
        set_event(fd, EPOLLIN, 0);       //当HTTP头和体都发送完后,再将监听状态改为EPOLLIN
    }

    return count;
}

web_socket

以上所用的代码实现了一个webserver,基于此,可以设计出一个websocket。

什么是websocket?

WebSocket 是一种网络通信协议,旨在为客户端和服务器之间提供全双工、实时的通信通道。它是在 HTML5 规范中引入的,可以让浏览器与服务器进行持久化连接,以便实现低延迟的数据交换。

WebSocket 的特点:

  1. 全双工通信:客户端和服务器可以同时发送和接收消息,而不必等待对方完成操作。
  2. 轻量级:相较于传统的 HTTP 协议,WebSocket 头部信息更小,这减少了网络开销。
  3. 持久连接:一旦建立连接,双方可以一直保持这个连接,直到主动关闭。这样避免了频繁建立和关闭连接带来的性能损耗。
  4. 实时性:适合需要即时数据更新的应用,如在线聊天、游戏、股票行情等

如何实现?

新建一个文件,里面写两个函数用来替代之前webserver中的requeset 和 reponse即可。

课程地址:0voice · GitHub

一些心得

从webserver和websocket两个小项目出发,体会到,通过reactor的方式,可以很好的将网络管理和业务开发区分开。使用reactor框架,然后单独实现一个业务逻辑,只需要在reactor框架下把相关需要实现的回调函数进行修改就可以了。

相关推荐
小蜗牛慢慢爬行34 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
MARIN_shen40 分钟前
Marin说PCB之POC电路layout设计仿真案例---06
网络·单片机·嵌入式硬件·硬件工程·pcb工艺
m0_748240021 小时前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome
終不似少年遊*1 小时前
华为云计算HCIE笔记05
网络·华为云·云计算·学习笔记·hcie·认证·hcs
青灯文案12 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
蜜獾云2 小时前
docker 安装雷池WAF防火墙 守护Web服务器
linux·运维·服务器·网络·网络安全·docker·容器
小林熬夜学编程3 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
Hacker_Fuchen3 小时前
天融信网络架构安全实践
网络·安全·架构
上海运维Q先生3 小时前
面试题整理15----K8s常见的网络插件有哪些
运维·网络·kubernetes
ProtonBase3 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构