【Tomcat与网络11】如何自己实现一个简单的HTTP服务器

在前面我们尝试解释Tomcat的理论,但是呢,很多时候那些复杂的架构和设计会让我们眼花缭乱,以至于忽略了最进本的问题——服务器到底是什么?今天我们就用尽量简单的代码实现一个简易的HTTP服务器。

HTTP启动之后要持续监听,所以我们可以使用NioServer中的Handler就可以了,在修改后的HttpHandler中首先获取到请求报文并打印出报文的头部,包括协议的首行、请求方法的类型、Url和Http版本等,之后将接收到的请求消息(也就是报文信息)封装在一起,最后将这些信息打包成一个报文发送给客户端。

我们这里为了简单,将HttpHandler使用单线程来处理,并且选择SelectionKey的操作类型等都放在Handler中了。

主体代码:

    public static void main(String[] args) throws Exception{//创建ServerSocketChannel,监听8040端口ServerSocketChannel ssc=ServerSocketChannel.open();ssc.socket().bind(new InetSocketAddress(8040));//设置为非阻塞模式ssc.configureBlocking(false);//为ssc注册选择器Selector selector=Selector.open();ssc.register(selector, SelectionKey.OP_ACCEPT);//创建处理器while(true){// 等待请求,每次等待阻塞5s,超过5s后线程继续向下运行// 这里如果传入0或者不传参数将一直阻塞if(selector.select(5000)==0){continue;}// 获取待处理的SelectionKeyIterator<SelectionKey> keyIter=selector.selectedKeys().iterator();while(keyIter.hasNext()){SelectionKey key=keyIter.next();// 启动新线程处理SelectionKeynew Thread(new HttpHandler(key)).run();// 处理完后,从待处理的SelectionKey迭代器中移除当前所使用的keykeyIter.remove();}}}

我们在上面将端口设置为8040了,因为8080有时候会和其他软件冲突,有时候会被浏览器隐藏,所以我们使用一个更可控的。

之后,我们就来写真正需要干活的Hander:

  private static class HttpHandler implements Runnable{private int bufferSize = 2048;private String  localCharset = "UTF-8";private SelectionKey key;public HttpHandler(SelectionKey key){this.key = key;}@Overridepublic void run() {try{// 接收到连接请求时if(key.isAcceptable()){handleAccept();}// 读数据if(key.isReadable()){handleRead();}} catch(IOException ex) {ex.printStackTrace();}}
}

我们使用一个线程来处理清楚,所以我们需要继续实现run()方法,根据选择器的状态来完成接收数据还是读取数据。

先看接收数据:

        public void handleAccept() throws IOException {SocketChannel clientChannel=((ServerSocketChannel)key.channel()).accept();clientChannel.configureBlocking(false);clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));}

这段代码看似简单,其实包含的逻辑并不少,我们可以看到这里是从key获得Socket通信使用了哪个通道(对于HTTP的就是不同端口号),这个key是哪里的呢?是我们再创建HttpHandler的时候传过来的,也就是这一行:

  new Thread(new HttpHandler(key)).run();

 这相当于老板让你干活的时候,给你的锤子。

之后的这一行,就是自己创建了一个channel注册给key。

clientChannel.register(key.selector()...)

这里相当于打仗之前,你去军长那里报道,说你能打,然后军长Key就记住你了。

之后我们看读取数据的处理逻辑:

 public void handleRead() throws IOException {// 获取channelSocketChannel sc=(SocketChannel)key.channel();// 获取buffer并重置ByteBuffer buffer=(ByteBuffer)key.attachment();buffer.clear();// 没有读到内容则关闭if(sc.read(buffer)==-1){sc.close();} else {// 接收请求数据buffer.flip();String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();// 控制台打印请求报文头String[] requestMessage = receivedString.split("\r\n");for(String s: requestMessage){System.out.println(s);// 遇到空行说明报文头已经打印完if(s.isEmpty())break;}// 控制台打印首行信息String[] firstLine = requestMessage[0].split(" ");System.out.println();System.out.println("Method:\t"+firstLine[0]);System.out.println("url:\t"+firstLine[1]);System.out.println("HTTP Version:\t"+firstLine[2]);System.out.println();// 返回客户端StringBuilder sendString = new StringBuilder();sendString.append("HTTP/1.1 200 OK\r\n");//响应报文首行,200表示处理成功sendString.append("Content-Type:text/html;charset=" + localCharset+"\r\n");sendString.append("\r\n");// 报文头结束后加一个空行sendString.append("<html><head><title>显示报文</title></head><body>");sendString.append("接收到请求报文是:<br/>");for(String s: requestMessage){sendString.append(s + "<br/>");}sendString.append("</body></html>");buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));sc.write(buffer);sc.close();}}

这里,我们可以看到执行读的时候,我们使用key里获得channel的。这就相当于司令要求你所在的连队突袭,然后你的军长key就从自己的小本本上找到你,然后让你们行动。我们看一下执行效果:

在浏览器输入http://localhost:8040/

然后在控制台,我们看到http收到的相应如下:


最后附上完整代码:

public class HttpServer {public static void main(String[] args) throws Exception{//创建ServerSocketChannel,监听8040端口ServerSocketChannel ssc=ServerSocketChannel.open();ssc.socket().bind(new InetSocketAddress(8040));//设置为非阻塞模式ssc.configureBlocking(false);//为ssc注册选择器Selector selector=Selector.open();ssc.register(selector, SelectionKey.OP_ACCEPT);//创建处理器while(true){// 等待请求,每次等待阻塞5s,超过5s后线程继续向下运行// 这里如果传入0或者不传参数将一直阻塞if(selector.select(5000)==0){continue;}// 获取待处理的SelectionKeyIterator<SelectionKey> keyIter=selector.selectedKeys().iterator();while(keyIter.hasNext()){SelectionKey key=keyIter.next();// 启动新线程处理SelectionKeynew Thread(new HttpHandler(key)).run();// 处理完后,从待处理的SelectionKey迭代器中移除当前所使用的keykeyIter.remove();}}}private static class HttpHandler implements Runnable{private int bufferSize = 2048;private String  localCharset = "UTF-8";private SelectionKey key;public HttpHandler(SelectionKey key){this.key = key;}public void handleAccept() throws IOException {SocketChannel clientChannel=((ServerSocketChannel)key.channel()).accept();clientChannel.configureBlocking(false);clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));}public void handleRead() throws IOException {// 获取channelSocketChannel sc=(SocketChannel)key.channel();// 获取buffer并重置ByteBuffer buffer=(ByteBuffer)key.attachment();buffer.clear();// 没有读到内容则关闭if(sc.read(buffer)==-1){sc.close();} else {// 接收请求数据buffer.flip();String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();// 控制台打印请求报文头String[] requestMessage = receivedString.split("\r\n");for(String s: requestMessage){System.out.println(s);// 遇到空行说明报文头已经打印完if(s.isEmpty())break;}// 控制台打印首行信息String[] firstLine = requestMessage[0].split(" ");System.out.println();System.out.println("Method:\t"+firstLine[0]);System.out.println("url:\t"+firstLine[1]);System.out.println("HTTP Version:\t"+firstLine[2]);System.out.println();// 返回客户端StringBuilder sendString = new StringBuilder();sendString.append("HTTP/1.1 200 OK\r\n");//响应报文首行,200表示处理成功sendString.append("Content-Type:text/html;charset=" + localCharset+"\r\n");sendString.append("\r\n");// 报文头结束后加一个空行sendString.append("<html><head><title>显示报文</title></head><body>");sendString.append("接收到请求报文是:<br/>");for(String s: requestMessage){sendString.append(s + "<br/>");}sendString.append("</body></html>");buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));sc.write(buffer);sc.close();}}@Overridepublic void run() {try{// 接收到连接请求时if(key.isAcceptable()){handleAccept();}// 读数据if(key.isReadable()){handleRead();}} catch(IOException ex) {ex.printStackTrace();}}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/663075.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

校园网网络规划与设计——计算机网络实践报告

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 目录 一、设计目的 二、软硬件环境 三、理论基础 四、设计方案 五、网络配置步骤 六、设计过程中出现的问题及相应解决办法 八、参考资料 一、设计目的 深入理解网络工程的三层层次设计模型&#xff1b; 掌握网络…

2024年美赛数学建模A题思路分析 - 资源可用性和性别比例

# 1 赛题 问题A&#xff1a;资源可用性和性别比例 虽然一些动物物种存在于通常的雄性或雌性性别之外&#xff0c;但大多数物种实质上是雄性或雌性。虽然许多物种在出生时的性别比例为1&#xff1a;1&#xff0c;但其他物种的性别比例并不均匀。这被称为适应性性别比例的变化。…

【Java】阻塞队列

目录 BlockingQueue BlockingQueue接口 三个主要实现类介绍&#xff1a; ArrayBlockingQueue&#xff1a;有界队列 LinkedBlockingQueue&#xff1a;无界队列 SynchronousQueue:同步队列 队列对比 BlockingQueue 对于Queue而言&#xff0c;BlockingQueue是主要的线程安全…

有深浅入数据分析 - 启发法(凭人类的天性做分析)

在做数据分析的时候&#xff0c;往往最优的方法是艰难耗时间的 凭经验处理&#xff0c;迅速做出决策&#xff0c;确识能够奏效&#xff0c;进行数据分析的重要而必要的技能 领导的要求是&#xff1a; 邋遢集的处理方式是&#xff1a; 计量的方式处理 上图的调查问卷可以…

Axure 动态面板初使用-实现简单的tab切换页面效果

使用工具版本 Axure 9 实现的效果 步骤过程 1、打开Axure 9&#xff0c;默认进入一个空白页&#xff0c;首先从元件库拉一个动态面板到页面中&#xff0c;位置肯定是C位咯~ 2、将面板尺寸调整一下&#xff0c;设置成你喜欢的数字&#xff0c;比如我就喜欢800600 3、然后…

学习日志以及个人总结(13) 指针!

指针 定义 访问内存地址 操控硬件 指针&#xff1a; 指针基本数据据类 指针数组 指针函数 指针指针 1.指针&#xff1a;就是地址-----就是内存的单元的编号 2.指针变量 语法&#xff1a; 基类型* 指针变量名&#xff1b; 基类型-------数据类型//基础数据类型 //数组…

python脚本将照片按时间线整理

说明&#xff1a;有一次自己瞎折腾&#xff0c;然后把服务器相册搞崩了&#xff0c;后来做了备份同步给找了回来&#xff0c;但是相册的时间线全乱了&#xff0c;看起来非常难受。所以就想通过文件夹的形式把照片重新分类&#xff0c;分类后的结构如下(红色字体为文件夹)&#…

《区块链简易速速上手小册》第7章:区块链在其他行业的应用(2024 最新版)

文章目录 7.1 供应链管理7.1.1 供应链管理中区块链的基础7.1.2 主要案例&#xff1a;食品安全追踪7.1.3 拓展案例 1&#xff1a;制药供应链7.1.4 拓展案例 2&#xff1a;汽车行业的零部件追踪 7.2 区块链在医疗保健中的应用7.2.1 医疗保健中区块链的基础7.2.2 主要案例&#xf…

1451A/D/F捷变信号发生器

01 1451A/D/F捷变信号发生器 产品综述&#xff1a; 1451系列捷变信号发生器采用直接数字合成&#xff08;DDS&#xff09;技术和直接模拟合成技术&#xff08;ADS&#xff09;相结合的设计方案&#xff0c;实现覆盖10MHz~3/20/40GHz全频段的频率捷变&#xff0c;捷变时间小于…

分布式事务(四)——TCC补偿模式解决方案

系列目录&#xff1a; 《分布式事务&#xff08;一&#xff09;—— 事务的基本概念》 《分布式事务&#xff08;二&#xff09;—— CAP和Base理论》 《分布式事务&#xff08;三&#xff09;—— 两阶段提交解决方案&#xff08;2PC&#xff09;》 一、常见分布式事务解决…

QML自定义ComboBox组件,支持动态筛选

QtQuick.Controls提供了ComboBox组件&#xff0c;该组件能够满足日常的下拉选择框的需求&#xff0c;但当需要用户在ComboBox中通过输入关键字进行自动匹配时&#xff0c;原生的组件虽然提供了editable属性用于输入关键字&#xff0c;但是匹配内容不弹出下拉框&#xff0c;无法…

03、全文检索 -- Solr -- Solr 身份验证配置(给 Solr 启动身份验证、添加用户、删除用户)

目录 全文检索 -- Solr -- Solr 身份验证配置启用身份验证&#xff1a;添加用户&#xff1a;删除用户&#xff1a; 全文检索 – Solr – Solr 身份验证配置 学习之前需要先启动 Solr 执行如下命令即可启动Solr&#xff1a; solr start -p <端口>如果不指定端口&#xf…

7-2.递归思想代码练习题

例题1.按顺序打印一个数据的每一位 1234 输出 1 2 3 4 解决问题的思路 如何拿到这个数的每一位 1234%104 1234/10123 123%103 123/1012 12%102 12/101 1%101 递推公式&#xff1a;%10 /10 初始条件&#xff1a;1-9之间的数直接打印&#xff08;n<10&#xff09; n>10 进行…

最近nvm安装报错的原因找到了——npm原淘宝镜像正式到期!

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 背景 错误原因 问题排查 淘宝镜像 证书到期 问题解决 结语 背景 我们…

HAL库配置CAN通信

一、CAN总线波特率计算 CAN总线通信的各节点通信时会产生相位差&#xff0c;所以要进行位同步&#xff0c;两个节点保持步调一致。 CAN_SJW&#xff1a;重新同步跳跃宽度(SJW) 。定义了在每位中可以延长或缩短多少个时间单元的上限。其值可以编程为1到4个时间单元。 CAN_BS1&a…

【数据分享】1929-2023年全球站点的逐日最低气温数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01; 之前我们分享过1929-2023年全球气象站…

wsl + vscode 离线配置 ERROR: Faild to download https://update.code.visualstudio.com

内网情况配置 wsl 和 vscode。在wsl中下载不了 vscode 的核心。 报错&#xff1a; ERROR: Faild to download https://update.code.visualstudio.com/commit:8b3775030ed1a69b13e4f4c628c612102e30a681/server-linux-x64/stable 很多情况下是没有现成的VS Code Server程序的&…

三好夫人:真爱战胜一切

真爱如同飞蛾扑火&#xff0c;明知前方是毁灭&#xff0c;却仍心甘情愿地奔赴。如果一个男人真心爱你&#xff0c;他会倾尽所有&#xff0c;不遗余力地满足你的需求。这份爱&#xff0c;会在他的言行举止中流露出来。男人对你的爱&#xff0c;常常表现在他的言语之中。 如果他爱…

桌面显示器应用Type-C接口有什么好处

随着科技的不断发展&#xff0c;桌面显示器作为我们日常工作中不可或缺的设备之一&#xff0c;也在不断更新换代。其中&#xff0c;Type-C接口的应用成为了桌面显示器发展的一个重要趋势。那么&#xff0c;桌面显示器应用Type-C接口究竟有什么好处呢&#xff1f; 首先&#xff…

本地运行面壁智能的“贺岁模型”:MiniCPM 2B

简单聊聊可以在端侧运行的 Mini CPM 2B SFT / DPO 版本的模型。 写在前面 模型是好是坏&#xff0c;其实不用看公众号们的营销&#xff0c;小马过河问题&#xff0c;自己试试就知道了。当然&#xff0c;2B 参数量的模型&#xff0c;适合的场景肯定不是 34B / 70B 所擅长的&am…