手写TCP/IP协议栈——HTTP协议实现(完结篇)

一、理论部分

我们看到的一个网页界面实际上可能涉及多个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协议栈的全部功能啦!!

完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

向优秀的同学们致敬,同学们辛苦啦!🌹🌹🌹

相关推荐
yangSnowy2 小时前
webSocket 通信详解
网络·websocket·网络协议
大榕树信息科技2 小时前
当实现动环监控系统的集中管理时,如何提升机房运维的响应速度?
网络·物联网·机房运维·机房管理系统·动环监控系统
丁总学Java3 小时前
微信小程序上传揭秘:http://tmp 临时文件是如何“飞”到后端的?
http·微信小程序·小程序
终端行者3 小时前
Nginx limit_conn_zone 模块详解 Nginx如何限流 防止CC攻击
网络·nginx
white-persist3 小时前
轻松抓包微信小程序:Proxifier+Burp Suite教程
前端·网络·安全·网络安全·微信小程序·小程序·notepad++
LaoZhangGong1233 小时前
学习TCP/IP的第6步:断开连接
网络·学习·tcp/ip·以太网
上海云盾安全满满3 小时前
如何隐藏业务的IP
网络·网络协议·tcp/ip
发光小北3 小时前
MS-F155 特点与功能介绍
网络
进击的小头4 小时前
常用数字滤波器的特性与适用场景
c语言·算法