一、理论部分






我们看到的一个网页界面实际上可能涉及多个HTTP请求





返回的html文件就放到响应的正文部分

二、代码实现部分
xserver_http.h
① 定义HTML文档的存放路径(使用的是相对路径)
② http服务器创建并初始化函数
③ 运行http服务器为客户端提供服务的函数
cpp
#ifndef XSERVER_HTTP_H
#define XSERVER_HTTP_H
#include"../xnet_tiny/xnet_tiny.h"
#define XHTTP_DOC_DIR "htdocs"
xnet_err_t xserver_http_create(uint16_t port);
void xserver_http_run(void);
#endif // !XSERVER_HTTP_H
app.c
cpp
#include <stdio.h>
#include "xnet_tiny.h"
#include"xnet_app/xserver_datetime.h"
#include"xnet_app/xserver_http.h"
int main (void)
{
xnet_init();
xserver_datetime_create(13);
xserver_http_create(80);
printf("xnet running\n");
while (1)
{
xserver_http_run();
xnet_poll();
}
return 0;
}
其中xserver_http_run函数实现:
cpp
void xserver_http_run(void)
{
xtcp_t* tcp;
while ((tcp = http_fifo_out(&http_fifo)) != (xtcp_t*)0)
{
int i;
char* c = rx_buffer;
if (get_line(tcp, rx_buffer, sizeof(rx_buffer)) <= 0)
{
close_http(tcp);
continue;
}
while (*c == ' ')c++;
if (strncmp(rx_buffer, "GET", 3) != 0)
{
close_http(tcp);
continue;
}
while (*c != ' ')c++;
while (*c == ' ')c++;
for (i = 0; i < sizeof(url_path); i++)
{
if (*c == ' ')break;
url_path[i] = *c++;
}
url_path[i] = '\0';
if (url_path[strlen(url_path) - 1] == '/')
{
strcat(url_path, "index.html");
}
send_file(tcp, url_path);
close_http(tcp);
}
}
简单起见,所有的TCP连接缓存到FIFO队列中,依次由HTTP回调函数逐个处理
FIFO队列使用循环队列数据结构实现,必要时可进行回绕
cpp
static uint8_t rx_buffer[1024], tx_buffer[1024];
#define XTCP_FIFO_SIZE 40
typedef struct _xhttp_fifo_t
{
xtcp_t* buffer[XTCP_FIFO_SIZE];
uint8_t front, tail, count;
}xhttp_fifo_t;
static xhttp_fifo_t http_fifo;
xhttp_fifo_out函数实现
功能:从fifo队列当中取出tcp连接,并交由上层进一步处理
cpp
static xtcp_t* xhttp_fifo_out(xhttp_fifo_t* fifo)
{
xtcp_t* tcp;
if (fifo->count == 0)
{
return (xtcp_t*)0;
}
tcp = fifo->buffer[fifo->tail++];
if (fifo->tail >= XTCP_FIFO_SIZE)
{
fifo->tail = 0;
}
fifo->count--;
return tcp;
}
使用get_line函数来读取用户请求
根据HTTP请求格式,只读取非\r\n字符,并且遇到\n就完成对客户端请求部分的读取
cpp
static int get_line(xtcp_t* tcp, char* buf, int size)
{
int i = 0;
while (i < size)
{
char c;
if (xtcp_read(tcp, (uint8_t*)&c, 1) > 0)
{
if ((c != '\n') && (c != '\r'))
{
buf[i++] = c;
}
else if (c == '\n')
{
break;
}
}
xnet_poll();
}
buf[i] = '\0';
return i;
}
close_http函数实现:
cpp
static void close_http(xtcp_t* tcp)
{
xtcp_close(tcp);
printf("http closed.\n");
}
添加部分数据结构,例如对方请求文件的路径
cpp
#define MAX_PATH 255
#define XTCP_FIFO_SIZE 40
#define BUF_SIZE 1024
static char rx_buffer[BUF_SIZE], tx_buffer[BUF_SIZE];
static char url_path[MAX_PATH], file_path[MAX_PATH]
将用户请求的网页文件发送给对方
send_file函数核心功能:
① 读取用户希望访问的文件路径(这里跳过/)
② 先返回响应头,包含状态码以及数据部分的大小
③ 根据网页文件大小,循环向客户端进行文件传输
cpp
static void send_file(xtcp_t* tcp, const char* url)
{
FILE* file;
uint32_t size;
const char* content_type = "text/html";
int i;
while (*url == '/')url++;
sprintf(file_path, "%s/%s", XHTTP_DOC_DIR, url);
file = fopen(file_path, "rb");
if (file == NULL)
{
return;
}
fseek(file, 0, SEEK_END);
size = ftell(file);
fseek(file, 0, SEEK_SET);
sprintf(tx_buffer,
"HTTP/1.0 200 OK\r\n"
"Content-Length:%d\r\n\r\n",
(int)size);
http_send(tcp, tx_buffer, strlen(tx_buffer));
while (!feof(file))
{
size = fread(tx_buffer, 1, sizeof(tx_buffer), file);
if (http_send(tcp, tx_buffer, size) <= 0)
{
fclose(file);
return;
}
}
fclose(file);
}
http_send函数实现:
底层是把网页数据写到对应tcp控制块的tx_buffer中,然后再加上头部写入packet当中,最终调用ip_out函数将网络数据包发送出去
cpp
static int http_send(xtcp_t* tcp, char* buf, int size)
{
int sended_size = 0;
while (size > 0)
{
int curr_size = xtcp_write(tcp, (uint8_t*)buf, (uint16_t)size);
if (curr_size < 0)break;
size -= curr_size;
buf += curr_size;
sended_size += curr_size;
xnet_poll();
}
return sended_size;
}
现在我们来完成http服务器的初始化工作:
在http服务器创建的时候完成初始化
cpp
xnet_err_t xserver_http_create(uint16_t port)
{
xnet_err_t err;
xtcp_t* tcp = xtcp_open(http_handler);
if (!tcp) return XNET_ERR_MEM;
err = xtcp_bind(tcp, port);
if (err < 0)return err;
xhttp_fifo_init(&http_fifo);
return xtcp_listen(tcp);;
}
xhttp_fifo_init函数实现:
cpp
static void xhttp_fifo_init(xhttp_fifo_t* fifo)
{
fifo->count = 0;
fifo->front = fifo->tail = 0;
}
http_handler函数更新:
cpp
static xnet_err_t http_handler(xtcp_t* tcp, xtcp_conn_state_t state)
{
if (state == XTCP_CONN_CONNECTED)
{
http_fifo_in(&http_fifo, tcp);
printf("http connected.\n");
}
else if (state == XTCP_CONN_CLOSED)
{
printf("http closed.\n");
}
return XNET_ERR_OK;
}
其中http_fifo_in函数实现:
cpp
static xnet_err_t http_fifo_in(xhttp_fifo_t* fifo, xtcp_t* tcp)
{
if (fifo->count >= XTCP_FIFO_SIZE)
{
return XNET_ERR_MEM;
}
fifo->buffer[fifo->count++] = tcp;
if (fifo->front >= XTCP_FIFO_SIZE)
{
fifo->front = 0;
}
fifo->count++;
return XNET_ERR_OK;
}
由于htdocs文件夹在sln的上一级目录,我们调整VS的工程目录(使用相对路径)

三、代码测试部分
TCP协议栈


至此我们就实现了TCP/IP协议栈的全部功能啦!!
完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
向优秀的同学们致敬,同学们辛苦啦!🌹🌹🌹