IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 从零开始自制实现WebServer(一)---- 万丈高楼平地起 步子得一步一步慢慢走 -> 正文阅读

[系统运维]从零开始自制实现WebServer(一)---- 万丈高楼平地起 步子得一步一步慢慢走


前引


鉴于笔者走的路线是 Linux后端开发 而且现在处于正在起步的阶段
而且发现网上对于 linux后端开发的项目 真的如何从零开始起步的文章太少了 所以笔者打算就从这篇开始 写一个后端刚起步的小白 慢慢一步步成长学习的路吧

当然 这肯定是会写一个系列的 最后的完结目标 是实现一个 底层实现思路类似于陈硕大佬的muduo网络库 基于基层网络库的框架之上 再实现出一个高性能静态http并发的服务器 这个也会是作为之后实习的项目而做的

毕竟后端开发 一个高性能的WebServer 感觉应该是必须要去选择做的项目 这里的话 就先推荐两本书籍吧 也是我由这两本书为基础 + muduo网络库源码 作为之后实现webserver的参考

《Linux高性能服务器编程》- 游双
《Linux多线程服务器编程》(基于muduo C++网络库)- 陈硕

第一本基本上是读完了 第二本目前在笔者写的时候 读到了第八章 muduo网络库设计与实现
很明显 第一本书就更偏向于linux服务器最基础的介绍 作为入门书籍 我觉得最合适不过了 如果一刚开始就来啃这本《linux多线程服务器编程》的话 肯定会是相当痛苦的

正如笔者现在的状态一样 阅读这本书的时候 揣着太多太多的疑惑与不解 如同在冰面上行走一样 稍有不慎便会一下子摔在地上 因为感觉看的时候确实太多疑惑与不解了

所以正是因为这样 笔者才会来写下这篇博客 并由此作为起点来记录 到最后由一个网络库实现出一个高性能静态web服务器


(一) 万丈高楼平地起 步子得一步一步慢慢走


何以取这个标题
哈哈 因为笔者打算先暂时停止看第二本《Linux高性能服务器编程》 而是先去把第一本书中 最后的 具有比较高的耦合性 线程池 + one loop per thread给实现出来 再继续去看那本书了

个人以为 作为初学者 直接上手这本书只会是心中徒有疑惑 原本我的计划是第一本书的源码放着 直接来看第二本书 网络库 不需要有前置的铺垫 直接来就好了 结果发现确实不行

所以这一篇标题取名为 万丈高楼平地起啊 先把一个有着雏形的webserver给写出来 捣鼓捣鼓出来了 整明白了 我们再说 之后建立网络库 套模板 去改良吧

顺带呢 再把第一本书里面的 HTTP 状态转移机给弄懂整出来吧 因为后面反正也用得到 说到状态转移 我就想到了之前 我做正则引擎的时候 最后用跳转表 前面的NFA DFA的状态转移 哈哈 所以这里看到这个词心里面就觉得倍感亲切啊 哈哈

废话也不多说了 我先去把源代码一行行给看懂了 然后凭借着自己的印象多少给捣鼓捣鼓出来再说吧


1、simple echo server 0.01


为什么笔者打算先从一个最简单的能够响应的echo 服务器开始下手呢 说来惭愧 看了看AupeUnp 自己一行代码也没敲过 包括看完第一本推荐的书的时候也是一行代码也没敲过

尽管很多框架零零碎碎的晓得了吧 但是没有自己上手过的东西总是虚的 那不妨先从一个有基本功能的echo server开始写起 然后支持并发啊 各种细节开始扣 然后最后再向http server开始写 毕竟http server呢 也只不过就是多了一个协议报头需要处理 还要一些其他的细节就要处理 高复用性的框架写出来了 那把服务器往里面套就完了


1、熟悉基本工具


所有的项目 东西都是一步一步积累起来的 本来想先写一下进程/线程池的 算了 先把一个最基本 没有协议 能够支持单行发送消息 最简单的发送消息的服务器写出来吧

其实刚开始尝试开头 我便感觉很多东西已经开始了 写一段这样简单的simple的代码 建议的话呢 别先去copy 每一行代码 每一个命令都先自己根据书里面讲的函数去写 到后面g++ 编译的时候 就可以发现自己哪里写错了 哪里有问题需要改

也是从这里开始 我也打算正式的 以后把vim作为我在Linux上面敲代码正式的编辑器 而不是那种文本编辑器了 在我写完一个simple server时 我才真的明白 就强迫自己去用这些快捷键 用多了之后发现真的很方便很便捷 用完vim后呢 对于客户端 也可以用用nc这样的瑞士军刀 来充当客户端 用用这些实用的工具 不管之前很多东西用的多顺 去尝试用这些实用 熟悉了能够大幅度提高生产力的小工具 还是相当不错的 之后的话 如果服务器出错了 也可以尝试用tcpdump去抓抓包 反正这里写的意义呢 就是仅仅在于希望各位笔者能够自己先开始用这些好用的tools


2、simple echo server 0.01 came out


这里就不多介绍了 也就从这里正式开始 一步步拓宽 一步步开始进阶的改善这个echo server了 最后再测试一下效果

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc,char* argv[])
{
	if (argc <= 2)
	{
		printf( "Usage: %s ip_address portname\n", argv[0] );
		return 0;
	}

	const char* ip = argv[1];
	int port = atoi( argv[2] );
    
	int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
	assert( listenfd >= 1 );

	struct sockaddr_in address;
	memset( &address, 0, sizeof( address ) );
	address.sin_family = AF_INET;
	address.sin_port = htons( port );
	inet_pton( AF_INET, ip, &address.sin_addr );

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

	ret = listen( listenfd, 5 );
	assert( ret != -1 );
	
	struct sockaddr_in client;
	socklen_t client_addrlength = sizeof( client );
	int sockfd = accept( listenfd, (struct sockaddr*)( &address ), &client_addrlength );
				   	  	 
	char buf_size[1024] = {0};
	int recv_size = 0;
	recv_size = recv( sockfd, buf_size, sizeof( buf_size ) , 0);
	
	int send_size = 0;
	send_size = send( sockfd, buf_size , recv_size , 0 );
	
	close( sockfd );
	close( listenfd );

	return 0;
}

3、simple server 0.0.1 test


用一下实用的nc

服务器端 先打开服务器端 然后输入ip地址 选择回流的本地地址 然后端口选择2022
在这里插入图片描述


客户端 由于懒得写 就用nc命令来充当客户端了 用了才发现确实好用 哈哈 有尝试才有收获 光说不做假把式

结果发现 输入完第一条消息 确实回弹了第一条消息 但是客户端没有关闭 服务器由于笔者写的只返回第一条消息 直接关闭服务器 所以后面
在这里插入图片描述


2、simple echo server 0.05


莫想要一步登天 一口气吃成个胖子
一步步熟悉好每个函数 每个作用 一步布把脚步放踏实点 自己走上去才觉得心安嘛 嘿嘿

这里的话 就要引入出 单线程echo server 服务器了 只不过现在可以并发啦 能够处理并发但不是多线程的话 是因为我们用了I/O复用 Epoll函数
这种最基本的模型的话 在第二本书里面介绍过 应该是单线程Reactor了
epoll触发呢 采用的边沿ET触发 而不是LT触发 这一版本较上面一版本的话 肯定还是需要去再扣一下echo server的细节

话也不多说 走起吧


1、simple echo server 0.05 came out


写了一会 代码也都自己分析了一遍 自己开了几个端口
其实也就是用了一下 epoll原本我以为很快就写完了 结果还是花了一点时间 把写错的代码改回来 - - 下面直接放代码了

其实仔细想一下 就会很简单的发现下面代码有可能是有问题的
是因为有可能echo返回的字符串不完整 不是一整行
所以应该是需要前面加个 每一行的字节数 才能确定是否send了一个完整的行 既没有多也没有少

这部分就留到之后再完成吧

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>

#define MAX_EVENTS_NUMBER 5

int set_non_blocking( int fd )
{
	int old_state = fcntl( fd, F_GETFL );
	int new_state = old_state | O_NONBLOCK;
	fcntl( fd, F_SETFL, new_state );

	return old_state;	
}

void addfd( int epollfd , int fd )
{
	epoll_event event;
	event.events = EPOLLIN | EPOLLET;
    event.data.fd = fd;
	epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
	set_non_blocking( fd );	
}


int main( int argc , char* argv[] )
{
	if (argc <= 2)
	{
		printf( "Usage: %s ip_address portname\n", argv[0] );
		return 0;
	}

	const char* ip = argv[1];
	int port = atoi( argv[2] );
    
	int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
	assert( listenfd >= 1 );

	struct sockaddr_in address;
	memset( &address, 0, sizeof( address ) );
	address.sin_family = AF_INET;
	address.sin_port = htons( port );
	inet_pton( AF_INET, ip, &address.sin_addr );

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

	ret = listen( listenfd, 5 );
	assert( ret != -1 );
	
	epoll_event events[ MAX_EVENTS_NUMBER ];
    int epollfd = epoll_create( 5 );
	assert( epollfd != -1);
	addfd( epollfd, listenfd );

	while(1)
	{
		int number = epoll_wait( epollfd, events, MAX_EVENTS_NUMBER, -1 );
		if( number < 0 )
		{
			printf( "epoll_wait failed\n" );
			return -1;
		}

		for( int i = 0; i < number; ++i )
		{
			const auto& event = events[i];
			const auto eventfd = event.data.fd;

			if( eventfd == listenfd )
			{
				struct sockaddr_in client;
				socklen_t client_addrlength = sizeof( client );
				int sockfd = accept( listenfd, ( struct sockaddr* )( &address ),
							   	     &client_addrlength );					
				addfd( epollfd, sockfd );			 
			}
			else if( event.events & EPOLLIN )
			{
				char buf[1024] = {0};
				while(1)
				{
					memset( buf, '\0', sizeof( buf ) );
					int recv_size  = recv( eventfd, buf, sizeof( buf ), 0 );
					if( recv_size < 0 )
					{
						if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
						{
							break;
						}
						printf(" sockfd %d,recv msg failed\n", eventfd );
						close( eventfd );
						break;
					}
					else if( recv_size == 0)
					{
					   	close( eventfd );
						break;	
					}
					else
					{
						send( eventfd, buf, recv_size, 0 );
					}	
				}
			}
		}	
	}

	close( listenfd );

	return 0;
}

2、simple server 0.0.5 test


这里就简单的放一张图吧
下面就是两个client 一起发送消息的截图 我设置的是10秒后自动断连
所以在10秒后回到了shell

这里算是简单的测试了一下 想要继续测试的大家可以自己去尝试啦~

在这里插入图片描述


结束语


这部分算是最基础最基础的一些api的使用了
当然 这也是我第一次在c++/c环境下用这些api 也是第一次用epoll
感觉使用起来还是非常的简单的 哈哈

第一次感觉到了那句话 入门简单 精通难
确实是这样的 但第一篇作为入门的目的已经做到了
第一篇中 我们还是最后尝试去用了 单线程Reactor的I/O复用

在这里呢 我打算第二篇就开始前往 线程池 和 陈硕介绍的
one loop per thread 方向走了 其实这个one loop per thread呢 就是在第一本书中介绍的 那个高效的 半同步/半异步I/O模型

下面还是贴一下图吧
遗憾的是什么呢 用了one loop per thread 模型的代码 是用的进程池
用了线程池的代码呢 是用的全局队列的方式 也就是每个线程在一个全局队列中争抢业务

但是作为第一篇 个人感觉还是开了一个不错的好头的 至少让人感觉到 初初摸到了门路 哈哈 那这一篇就先写到这里 我们下一篇见了

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-03-16 22:58:21  更:2022-03-16 23:00:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 1:16:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码