linux C有限状态机解析HTTP请求

2023-05-16

先看main函数执行的功能很简单,前半段是建立TCP连接的常规操作,每次读取4096字节的数据到缓存区,解析这4096个数据,根据解析状态返回相应结果。

int main(int argc, char** argv) 
{
	if (argc <= 2) {
		printf("usage: %s ip_address port_number\n", basename(argv[0]));
		return 1;
	}
	const char* ip = argv[1];         // ip
	int port = atoi(argv[2]);         // 端口
	struct sockaddr_in address;
	bzero(&address, sizeof(address));
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	inet_pton(AF_INET, ip, &address.sin_addr);

	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(listenfd >= 0);

	int reuse = 1;
	int ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

	ret = bind(listenfd, (struct sockaddr*) & address, sizeof(address));
	assert(ret != -1);

	ret = listen(listenfd, 5);
	assert(ret != -1);

	struct sockaddr_in client_address;
	socklen_t client_addrlength = sizeof(client_address);
	int fd = accept(listenfd, (struct sockaddr*) & client_address, &client_addrlength);
	if (fd < 0) printf("errno is: %d\n", errno);
	else {
		char buffer[BUFFER_SIZE];
		memset(buffer, 0, sizeof(buffer));
		int data_read = 0;          // 已经接收的字符数
		int read_index = 0;         // 已经读取了多少字节
		int checked_index = 0;      // 已经分析完了多少字节
		int start_line = 0;         // 行在buffer中的起始位置
		CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;   // 正在分析请求行
		while (1) {
			// 读取BUFFER_SIZE大小的数据到缓存区buffer
			data_read = recv(fd, buffer + read_index, BUFFER_SIZE - read_index, 0);
			if (data_read == -1) {
				printf("reading failed\n");
				break;
			}
			else if (data_read == 0) {
				printf("remote client has closed the connection\n");
				break;
			}
			read_index += data_read;
			// 解析缓存区的数据,获得HTTP状态码
			HTTP_CODE result = parse_content(buffer, checked_index, checkstate, read_index, start_line);
			if (result == NO_REQUEST) continue;
			else if (result == GET_REQUEST) {
				// 如果请求状态正确,发送响应正确的字符串给浏览器
				send(fd, szret[0], strlen(szret[0]), 0);
				break;
			}
			else {
				// 如果请求状态错误,发送响应错误的字符串给浏览器
				send(fd, szret[1], strlen(szret[1]), 0);
				break;
			}
		}
		close(fd);
	}
	close(listenfd);
	return 0;
}

szert是字符串数组,比较简单

static const char* szret[] = { "I get a correct result\n", "Something wrong\n" };

这里的状态定义有

//主状态机的两种状态,当前正在分析请求行和正在分析头部字段
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER };

HTTP状态码有以下几种

//服务器处理HTTP请求的结果:NO_REQUEST表示请求不完整,需要读取客户数据;
//                          GET_REQUEST表示获得了一个完整的客户请求;
//                          BAD_REQUEST表示客户请求有语法错误;
//                          FORBIDDEN_REQUEST表示客户对资源没有足够的访问权限
//                          INTERNAL_ERROR表示服务器内部错误;
//                          CLOSED_CONNECTION表示客户端已经关闭连接。
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, FORBIDDEN_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
HTTP_CODE parse_content(char* buffer, int& checked_index, CHECK_STATE& checkstate, int& read_index, int& start_line) {
	LINE_STATUS linestatus = LINE_OK;              // 从状态机状态
	HTTP_CODE retcode = NO_REQUEST;                // HTTP code
	while ((linestatus = parse_line(buffer, checked_index, read_index)) == LINE_OK) {
		//当解析完一行后
		char* temp = buffer + start_line;
		start_line = checked_index;
		switch (checkstate) {
		case CHECK_STATE_REQUESTLINE: {
			// 正在分析请求行
			retcode = parse_requestline(temp, checkstate);
			if (retcode == BAD_REQUEST) return BAD_REQUEST;
			break;
		}
		case CHECK_STATE_HEADER: {
			// 正在分析头部字段
			retcode = parse_headers(temp);
			if (retcode == BAD_REQUEST) return BAD_REQUEST;
			else if (retcode == GET_REQUEST) return GET_REQUEST;
			break;
		}
		default: {
			return INTERNAL_ERROR;
		}
		}
	}
	if (linestatus == LINE_OPEN) return NO_REQUEST;         // 如果分析成功。
	else return BAD_REQUEST;
}

 

从状态机的状态

//从状态机的三种可能状态,即行的读取状态:读取到一个完整的行、行出错和行数据暂且不完整
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };

HTTP请求都是\r\n结尾的。

//从状态机,用于解析一行内容
/*
buffer 是缓冲区字符串
checked_index 已经分析完的字符个数
read_index 已经读取字符个数
*/
LINE_STATUS parse_line(char* buffer, int& checked_index, int& read_index) {
	//checked_id_index指向buffer的正在分析的字节,read_index指向buffer中的最后一个字节的下一个字节
	//即从0~checked_index是已分析完毕,checked_index~read_index-1待分析
	char temp;
	for (; checked_index < read_index; ++checked_index) {
		temp = buffer[checked_index];
		//如果当前是回车符,则说明可能读取到了一个完整行
		//如果是'\n',即换行符,也说明可能读取到了一个完整行
		if (temp == '\r') {
			//如果当前是本行最后一个字符,则说明不完整,需要更多数据
			//如果下一个字符是'\n'则说明读取到了完整的行
			//否则说明HTTP请求存在语法问题
			if (checked_index + 1 == read_index) {
				return LINE_OPEN;
			}
			else if (buffer[checked_index + 1] == '\n') {
				buffer[checked_index++] = '\0';
				buffer[checked_index++] = '\0';
				return LINE_OK;
			}
			else return LINE_BAD;
		}
		else if (temp == '\n') {
			if ((checked_index > 1) && (buffer[checked_index - 1] == '\r')) {
				buffer[checked_index - 1] = '\0';
				buffer[checked_index++] = '\0';
				return LINE_OK;
			}
			return LINE_BAD;
		}
	}
	//如果到最后也没有发现'\r'字符,则返回LINE_OPEN表示需要读取更多数据分析
	return LINE_OPEN;
}

分析请求行,关键是解析出来method,url和http协议对应的版本。

//分析请求行
HTTP_CODE parse_requestline(char* temp, CHECK_STATE& checkstate) {
	//如果请求行中没有空格和'\t'字符则说明HTTP请求有问题
	//strpbrk返回前面缓冲区第一个在后面字符集合中的字符位置
	char* url = strpbrk(temp, " \t");
	if (!url) return BAD_REQUEST;
	*url++ = '\0';

	//strcasecmp与strcmp的区别就是不区分大小写
	char* method = temp;
	if (strcasecmp(method, "GET") == 0) printf("The request method is GET\n");
	else return BAD_REQUEST;

	//strspn函数统计缓冲区前面多少个连续字符在字符集合中
	url += strspn(url, "\t");
	char* version = strpbrk(url, " \t");
	if (!version) return BAD_REQUEST;

	*version++ = '\0';
	version += strspn(version, " \t");

	//strchr函数返回缓冲区里第一个后面字符的位置
	if (strcasecmp(version, "HTTP/1.1") != 0) {
		url += 7;
		url = strchr(url, '/');
	}

	if (!url || url[0] != '/') return BAD_REQUEST;
	printf("The request URL is: %s\n", url);
	checkstate = CHECK_STATE_HEADER;
	return NO_REQUEST;
}

分析头部请求行数

//分析头部
HTTP_CODE parse_headers(char* temp) {
	//遇到空行说明得到了一个正确的HTTP请求
	if (temp[0] == '\0') return GET_REQUEST;
	else if (strncasecmp(temp, "Host:", 5) == 0) {
		temp += 5;
		temp += strspn(temp, " \t");
		printf("The request host is: %s\n", temp);
	}
	else printf("I can not handle this header\n");
	return NO_REQUEST;
}

从状态机的状态分析

字符串解析过程

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux C有限状态机解析HTTP请求 的相关文章

  • iOS WKWebView 处理文件下载

    我面临以下问题 在 Web 界面中 文件下载是通过锚标记触发的 如下所示 a href bla blabla a 虽然 Safari 浏览器可以处理此请求并打开一个对话框来处理文件 但 WKWebView 将此视为普通链接并且不对其执行任何
  • QFileDialog::getSaveFileName 和默认的 selectedFilter

    我有 getSaveFileName 和一些过滤器 我希望当用户打开 保存 对话框时选择其中之一 Qt 文档说明如下 可以通过将 selectedFilter 设置为所需的值来选择默认过滤器 我尝试以下变体 QString selFilte
  • ansible 重新启动 2.1.1.0 失败

    我一直在尝试创建一个非常简单的 Ansible 剧本 它将重新启动服务器并等待它回来 我过去在 Ansible 1 9 上有一个可以运行的 但我最近升级到 2 1 1 0 并且失败了 我正在重新启动的主机名为 idm IP 为 192 16
  • Locale.getDefault() 始终返回 en

    unix 机器上的服务器始终使用 en 作为默认区域设置 以下是区域设置输出 LANG en US LC CTYPE C LC NUMERIC C LC TIME C LC COLLATE C LC MONETARY C LC MESSAG
  • 修改linux下的路径

    虽然我认为我已经接近 Linux 专业人士 但显然我仍然是一个初学者 当我登录服务器时 我需要使用最新版本的R 统计软件 R 安装在 2 个地方 当我运行以下命令时 which R I get usr bin R 进而 R version
  • 使用 find - 删除除任何一个之外的所有文件/目录(在 Linux 中)

    如果我们想删除我们使用的所有文件和目录 rm rf 但是 如果我希望一次性删除除一个特定文件之外的所有文件和目录怎么办 有什么命令可以做到这一点吗 rm rf 可以轻松地一次性删除 甚至可以删除我最喜欢的文件 目录 提前致谢 find ht
  • 创建 jar 文件 - 保留文件权限

    我想知道如何创建一个保留其内容的文件权限的 jar 文件 我将源代码和可执行文件打包在一个 jar 文件中 该文件将在使用前提取 人们应该能够通过运行批处理 shell 脚本文件立即运行示例和演示 然后他们应该能够修改源代码并重新编译所有内
  • 无法加载 JavaHL 库。- linux/eclipse

    在尝试安装 Subversion 插件时 当 Eclipse 启动时出现此错误 Failed to load JavaHL Library These are the errors that were encountered no libs
  • 抑制 makefile 中命令调用的回显?

    我为一个作业编写了一个程序 该程序应该将其输出打印到标准输出 分配规范需要创建一个 Makefile 当调用它时make run gt outputFile应该运行该程序并将输出写入一个文件 该文件的 SHA1 指纹与规范中给出的指纹相同
  • 如何检测并找出程序是否陷入死锁?

    这是一道面试题 如何检测并确定程序是否陷入死锁 是否有一些工具可用于在 Linux Unix 系统上执行此操作 我的想法 如果程序没有任何进展并且其状态为运行 则为死锁 但是 其他原因也可能导致此问题 开源工具有valgrind halgr
  • 通过特定分隔符删除字符串

    我的文件中有几列 其中第二列有 分隔符 我想删除第二列中的第一个 第三个和第四个字符串 并将第二个字符串留在该列中 但我有正常的分隔符空间 所以我不知道 input 22 16050075 A G 16050075 A G 22 16050
  • 如何在bash中使用jq从变量中包含的json中提取值

    我正在编写一个 bash 脚本 其中存储了一个 json 值 现在我想使用 Jq 提取该 json 中的值 使用的代码是 json val code lyz1To6ZTWClDHSiaeXyxg redirect to http examp
  • 如何在 shell 脚本中并行运行多个实例以提高时间效率[重复]

    这个问题在这里已经有答案了 我正在使用 shell 脚本 它读取 16000 行的输入文件 运行该脚本需要8个多小时 我需要减少它 所以我将其划分为 8 个实例并读取数据 其中我使用 for 循环迭代 8 个文件 并在其中使用 while
  • Flutter http请求上传mp3文件

    我使用这个 api 上传 mp3 文件 使用这种方法 Future
  • Linux中的CONFIG_OF是什么?

    我看到它在很多地方被广泛使用 但不明白在什么场景下我需要使用它 What is 配置 OF OF 的全名是什么 打开固件 这是很久以前发明的 当时苹果公司正在生产基于 PowerPC CPU 的笔记本电脑 而 Sun Microsystem
  • 我可以从命令行打印 html 文件(带有图像、css)吗?

    我想从脚本中打印带有图像的样式化 html 页面 谁能建议一个开源解决方案 我使用的是 Linux Ubuntu 8 04 但也对其他操作系统的解决方案感兴趣 你可以给html2ps http user it uu se jan html2
  • 在哪里可以找到并安装 pygame 的依赖项?

    我对 Linux 比较陌生 正在尝试安装 python 的 pygame 开发环境 当我运行 setup py 时 它说我需要安装以下依赖项 我找到并安装了其中之一 SDL 然而 其他人则更加难以捉摸 Hunting dependencie
  • 如何有效截断文件头?

    大家都知道truncate file size 函数 通过截断文件尾部将文件大小更改为给定大小 但是如何做同样的事情 只截断文件的尾部和头部呢 通常 您必须重写整个文件 最简单的方法是跳过前几个字节 将其他所有内容复制到临时文件中 并在完成
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • 尝试安装 LESS 时出现“请尝试以 root/管理员身份再次运行此命令”错误

    我正在尝试在我的计算机上安装 LESS 并且已经安装了节点 但是 当我输入 node install g less 时 出现以下错误 并且不知道该怎么办 FPaulMAC bin paul npm install g less npm ER

随机推荐

  • 微擎系统安装流程

    虚拟主机安装微擎系统流程 推荐环境 linux 43 php5 6及以上 43 mysql5 6及以上 如果您购买的主机开通的是windows系统 请先更换成linux系统 如果是虚拟主机 在主机面板 php版本页面选择php7 0 从官网
  • 部署https后浏览器提示不安全,不出现绿色小锁?

    网站部署https后 浏览器提示不安全 不出现绿色小锁图标 访问网站后 按f12 浏览器提示 Mixed Content The page at 39 https www xxx com 39 39 was loaded over HTTP
  • linux系统变为只读出现提示Read-only file system的解决办法

    问题描述 linux系统变为只读 出现提示Read only file system 如图所示 问题原因 系统没有正常关机 导致虚拟磁盘出现文件系统错误 说明 此修复可能会导致个别文件及数据丢失 修复之前建议做好文件备份工作 解决方法 使用
  • 小程序接入微信视频号配置指南

    一 填写视频号品牌信息和视频号类目资质 xff08 需要在平台发布模式后再进行填写 xff09 如图 xff1a 二 如之前已是平台发布模式的 xff0c 还需要操作 更新授权 xff1b 如之前是自主发布的 xff0c 切换到平台发布模式
  • 微信小程序如何使用视频组件

    小程序中可以使用视频组件来播放自己想要展示的视频 xff0c 具体步骤如下 一 添加视频组件 1 点击 店铺 装修店铺 编辑 xff0c 进入小程序页面编辑 2 点击 组件库 视频 xff0c 页面会添加视频组件 xff0c 点击视频组件的
  • 小程序地理位置接口申请

    申请接口理由 xff1a wx chooseAddress 获取用户收货地址提交理由 xff1a 快速定位用户当前位置 xff0c 获取省市区等地址信息 xff0c 方便用户提交地址等信息 wx chooseLocation打开地图选择位置
  • 远程桌面时出现“身份验证错误,要求的函数不受支持”解决办法

    远程桌面时 出现身份验证错误 xff0c 要求的函数不受支持 的错误 xff0c 如图所示 xff1a 这是由于本地客户端或者服务器端一方更新了CVE 2018 0886 的 CredSSP 补丁 xff0c 而另外一方未安装更新的原因导致
  • macbook苹果电脑系统使用“终端”远程登录linux主机

    登录mac系统后 xff0c 依次打开顶部菜单 xff0c 前往 gt 应用程序 gt 实用工具 gt 终端 xff0c 如下图 xff1a 在打开的终端页面 xff0c 输入如下代码 xff1a ssh root 64 服务器IP地址 注
  • wdcp面板无法打开

    打开wdcp面板提示 34 无法连接mysql xff0c 请检查mysql是否已启动及用户密码是否设置正确 34 按http faq myhostadmin net faq listagent asp unid 61 417先ssh方式远
  • java主机禁止某些IP访问的方法

    JAVA主机用的是tomcat来处理数据 xff0c 所以不支持以前apche使用的 htacess方式 xff0c 一般有两种禁止方法可以选择 xff0c 一是在server xml文件里面禁止 xff0c 或者是修改程序代码 1 禁止I
  • 用K-Means和DBSCAN算法对西瓜数据集4.0进行聚类分析

    文章目录 数据集K MeansDBSCAN 用K Means和DBSCAN算法对西瓜数据集4 0进行聚类分析 数据集 density sugercontent span class token number 1 span span clas
  • FSS对象存储挂载到windows云服务器操作方法

    FSS对象存储可以挂载到云主机中用于存储视频 备份等不需要 经常读写的大文件 不适合存放数据库等对IO需求较高 经常读写的场景 1 远程登陆服务器 xff0c 打开控制面板 xff0c 然后点击 打开或关闭windows功能 windows
  • nohup: failed to run command java: No such file or directory解决

    程序里远程执行shell命令 xff08 nohup java jar xff09 的执行 xff0c 后台日志报错如下 xff1a nohup failed to run command 96 java 39 No such file o
  • 七大顶级编程学习网站

    1 B站 xff1a https www bilibili com 不要以为b站只有二次元和游戏 xff0c 很多高质量的 34 开源 34 教程应有尽有 xff0c 如果大家想学实践性较强的知识 xff0c 如java的juc xff0c
  • SpringBoot测试失败并报错: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration

    情况一 xff1a 该测试类在测试包test下的包名和类路径java下的包名不一致导致的 xff0c 修改包名一致即可 由于包名自动生成的缘故导致这两个包名不一致 xff0c 引发以下报错 java lang IllegalStateExc
  • bootstrap个人简历毕业作品模板

    简介 xff1a 一款bootstrap框架个人简历模板 xff0c 可以作为前端毕业作品模板 xff0c 模板非常简单 网盘下载地址 xff1a https zijieyunpan cn vOnXW43exgr 图片 xff1a
  • linux进阶20——GDB(六):查看变量命令(print和display)

    一段c语言程序 include lt stdio h gt int main int num result 61 0 i 61 0 scanf 34 d 34 amp num while i lt 61 num result 43 61 i
  • 开发常用的抓包工具

    前端开发常用的抓包工具 浏览器开发者工具 浏览器开发者工具就是开发中常用的F12 xff0c 或者点击右键 gt 检查 可以看到单个请求的时间 xff0c 并可过滤 筛选请求 vConsole vConsole是前端手机开发的调试利器 xf
  • Linux Debian9安装Go

    介绍 Go xff0c 也称为golang xff0c 是由Google开发的一种现代开源编程语言 Go在许多应用程序中越来越受欢迎 xff0c 它采用极简主义的开发方法 xff0c 帮助您构建可靠 xff0c 高效的软件 本教程将指导您下
  • linux C有限状态机解析HTTP请求

    先看main函数执行的功能很简单 xff0c 前半段是建立TCP连接的常规操作 xff0c 每次读取4096字节的数据到缓存区 xff0c 解析这4096个数据 xff0c 根据解析状态返回相应结果 int main int argc ch