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 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> HTTP长连接解析 -> 正文阅读

[网络协议]HTTP长连接解析

1.先回顾一下网络四层模型

2.TCP的三次握手和四次挥手

这是三次握手和四次挥手的主要流程,其中我们可以看到 对于已经建立的TCP连接,一定是有一方主动进行关闭发送Fin连接才会关闭,那我们就探究一下是不是这样的

3.使用一个java的socket进行测试

这个测试是在tcp层玩,没有引入http层概念

//服务器
public class server {
 ? ?public static void main(String[] args) {
 ? ? ? ?ServerSocket serverSocket = null;
 ? ? ? ?Socket socket = null;
 ? ? ? ?ByteArrayOutputStream baos = null;
 ? ? ? ?InputStream is = null;
 ? ? ? ?try {
 ? ? ? ? ? ?//1.我要有一个地址
 ? ? ? ? ? ?serverSocket = new ServerSocket(9999);
 ? ? ? ? ? ?//2.等待客户端连接过来  监听
 ? ? ? ? ? ?//Socket accept() 监听要对这个套接字作出的连接并接受它 
 ? ? ? ? ? ?socket = serverSocket.accept();
        
 ? ? ? ? ? ?// 在这里进行睡眠,保证程序不退出,连接一直不主动关闭
 ? ? ? ? ? ?Thread.sleep(100000);
?
 ? ? ? ? ? ?//3.读取客户端的消息
 ? ? ? ? ? ?is = socket.getInputStream();
 ? ? ? ? ? ?//管道流
 ? ? ? ? ? ?baos = new ByteArrayOutputStream();
 ? ? ? ? ? ?byte[] buffer =new byte[1024];
 ? ? ? ? ? ?int len;
 ? ? ? ? ? ?while ((len=is.read(buffer))!=-1){
 ? ? ? ? ? ? ? ?baos.write(buffer,0,len);
 ? ? ? ? ?  }
 ? ? ? ? ? ?System.out.println(baos.toString());
?
 ? ? ?  } catch (Exception e) {
 ? ? ? ? ? ?e.printStackTrace();
 ? ? ?  }finally {
 ? ? ? ? ? ?//关闭资源
 ? ? ? ? ? ?if (baos!=null){
 ? ? ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ? ? ?baos.close();
 ? ? ? ? ? ? ?  } catch (IOException e) {
 ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ? ? ? ?if (is!=null) {
 ? ? ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ? ? ?is.close();
 ? ? ? ? ? ? ?  } catch (IOException e) {
 ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ? ? ? ?if (socket!=null) {
 ? ? ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ? ? ?socket.close();
 ? ? ? ? ? ? ?  } catch (IOException e) {
 ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ? ? ? ?if (serverSocket!=null) {
 ? ? ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ? ? ?serverSocket.close();
 ? ? ? ? ? ? ?  } catch (IOException e) {
 ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }
}
//客户端
public class client {
 ? ?public static void main(String[] args) {
 ? ? ? ?Socket socket = null;
 ? ? ? ?OutputStream os = null;
 ? ? ? ?try {
 ? ? ? ? ? ?//1.要知道服务器的地址,端口号
 ? ? ? ? ? ?//想要去连接的ip和端口
 ? ? ? ? ? ?InetAddress serviceIP = InetAddress.getByName("192.168.76.92");
 ? ? ? ? ? ?int port = 9999;
 ? ? ? ? ? ?//2.创建一个ServerSocket连接 ? 做了好多件事
 ? ? ? ? ? ?socket = new Socket(serviceIP,port);
 ? ? ? ? ? ?
 ? ? ? ? ? ?// 在这里进行睡眠,保证程序不退出,连接一直不主动关闭
 ? ? ? ? ? ?Thread.sleep(1000000);
 ? ? ? ? ? ?//3.发送消息  IO流
 ? ? ? ? ? ?//要通过 socket 向绑定 9999 端口的客服端传出去数据,所以用 socket.getOutputStream()。
 ? ? ? ? ? ?//socket.getOutputStream()得到的就是流对象,只需赋值给os就可以
 ? ? ? ? ? ?os = socket.getOutputStream();
 ? ? ? ? ? ?os.write("你好Java".getBytes());
 ? ? ?  } catch (Exception e) {
 ? ? ? ? ? ?e.printStackTrace();
 ? ? ?  }finally {
 ? ? ? ? ? ?if (os!=null) {
 ? ? ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ? ? ?os.close();
 ? ? ? ? ? ? ?  } catch (IOException e) {
 ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ? ? ? ?if (socket!=null) {
 ? ? ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ? ? ?socket.close();
 ? ? ? ? ? ? ?  } catch (IOException e) {
 ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }
}
通过Linux的netstat -natp可以发现如下结果:

?

因此得出结论,在使用 网络进行通信的时候,TCP层并不会自动帮我们关闭连接,而是维护  ESTABLISHED状态

4.修改client代码,主动关闭再次测试

//客户端
public class client {
 ? ?public static void main(String[] args) {
 ? ? ? ?Socket socket = null;
 ? ? ? ?OutputStream os = null;
 ? ? ? ?try {
 ? ? ? ? ? ?//1.要知道服务器的地址,端口号
 ? ? ? ? ? ?//想要去连接的ip和端口
 ? ? ? ? ? ?InetAddress serviceIP = InetAddress.getByName("192.168.76.92");
 ? ? ? ? ? ?int port = 9999;
 ? ? ? ? ? ?//2.创建一个ServerSocket连接 ? 做了好多件事
 ? ? ? ? ? ?socket = new Socket(serviceIP,port);
 ? ? ? ? ? ?
 ? ? ? ? ? ?Thread.sleep(5000);
 ? ? ? ? ? ?// client 端主动进行关闭 
 ? ? ? ? ? ?socket.close();
 ? ? ? ? ? ?// 在这里进行睡眠,保证程序不退出,连接一直不主动关闭 
 ? ? ? ? ? ?Thread.sleep(1000000);
 ? ? ? ? ? ?//3.发送消息  IO流
 ? ? ? ? ? ?//要通过 socket 向绑定 9999 端口的客服端传出去数据,所以用 socket.getOutputStream()。
 ? ? ? ? ? ?//socket.getOutputStream()得到的就是流对象,只需赋值给os就可以
 ? ? ? ? ? ?Thread.sleep(1000000);
 ? ? ? ? ? ?os = socket.getOutputStream();
 ? ? ? ? ? ?os.write("你好Java".getBytes());
 ? ? ?  } catch (Exception e) {
 ? ? ? ? ? ?e.printStackTrace();
 ? ? ?  }finally {
 ? ? ? ? ? ?if (os!=null) {
 ? ? ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ? ? ?os.close();
 ? ? ? ? ? ? ?  } catch (IOException e) {
 ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ? ? ? ?if (socket!=null) {
 ? ? ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ? ? ?socket.close();
 ? ? ? ? ? ? ?  } catch (IOException e) {
 ? ? ? ? ? ? ? ? ? ?e.printStackTrace();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }
}
在这种情况下,我们server端还是保证睡眠,一直不关闭,而client主动进行关闭了。
通过Linux的netstat -natp分别观察两边的情况:

?

由此可见,socket.close();才是进行连接关闭的主要行为,并且双发都要主动调用socket.close();否则资源还是开启的

5.经过一段时间再查看

?

?

?

发现 client端在维持一段 FIN_WAIT 2 状态后资源主动进行关闭 FIN_WAIT 2消失,而server的CLOSE_WAIT状态将一直维持不会自动消失,这就表明如果Server是一个web服务,不主动关闭那么资源会一直占用,导致服务器承受巨大的压力。

6.推理

因此,对于HTTP的长短连接行为 在于HTTP协议的使用者(应用层)是否主动调用tcp层的关闭行为。
?
对于Tomcat服务器,在它的源码中会对http请求进行解析
1. 解析http版本
    1.0版本不支持,收到请求处理完,数据写回直接调用close关闭
    1.1版本,由Connection请求参数控制
2. 解析Connection参数
    如果false,和1.0一样,收到请求处理完,数据写回直接调用close关闭
    如果keepAlive,通过线程监控keepAliveTimeout,到时间调用close关闭

7.查看Tomcat源码

1.创建协议处理对象

//AbstractHttp11Protocol类中
    @Override
 ? ?protected Processor createProcessor() {
 ? ? ? ?// 创建对象
 ? ? ? ?Http11Processor processor = new Http11Processor(this, getEndpoint());
 ? ? ? ?processor.setAdapter(getAdapter());
 ? ? ? ?processor.setMaxKeepAliveRequests(getMaxKeepAliveRequests());
 ? ? ? ?processor.setConnectionUploadTimeout(getConnectionUploadTimeout());
 ? ? ? ?processor.setDisableUploadTimeout(getDisableUploadTimeout());
 ? ? ? ?processor.setRestrictedUserAgents(getRestrictedUserAgents());
 ? ? ? ?processor.setMaxSavePostSize(getMaxSavePostSize());
 ? ? ? ?return processor;
 ?  }

2.通过处理类进行解析

//Http11Processor类中
public SocketState service(SocketWrapperBase<?> socketWrapper) throws IOException {
 ? ?//这里代码太多了挑点核心的
 ? ? ? ?keepAlive = true;
 ? ? ? ?comet = false;
 ? ? ? ?openSocket = false;
 ? ? ? ?sendfileInProgress = false;
 ? ? ? ?readComplete = true;
 ? ? ? ?// NioEndpoint返回true, Bio返回false
 ? ? ? ?if (endpoint.getUsePolling()) {
 ? ? ? ? ? ?keptAlive = false;
 ? ? ?  } else {
 ? ? ? ? ? ?keptAlive = socketWrapper.isKeptAlive();
 ? ? ?  }
?
 ? ? ? ?// 如果当前活跃的线程数占线程池最大线程数的比例大于75%,那么则关闭KeepAlive,不再支持长连接
 ? ? ? ?if (disableKeepAlive()) {
 ? ? ? ? ? ?socketWrapper.setKeepAliveLeft(0);
 ? ? ?  }
?
 ? ? ? ?// keepAlive默认为true,它的值会从请求Request Headers中读取
 ?      // 因此这是个Loop循环方法,表示持续执行下面的重复或者数据过程
 ?      // 直到这个表达式中结果为false,不再进行
 ? ? ? ?while (!getErrorState().isError() && keepAlive && !comet && !isAsync() &&
 ? ? ? ? ? ? ? ?upgradeInbound == null &&
 ? ? ? ? ? ? ? ?httpUpgradeHandler == null && !endpoint.isPaused()) {
 ? ? ? ? ? ?// keepAlive如果为true,接下来需要从socket中不停的获取http请求
?
 ? ? ? ? ? ?// Parsing the request header
 ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ?// 第一次从socket中读取数据,并设置socket的读取数据的超时时间
 ? ? ? ? ? ? ? ?// 对于BIO,一个socket连接建立好后,不一定马上就被Tomcat处理了,其中需要线程池的调度,所以这段等待的时间要算在socket读取数据的时间内
 ? ? ? ? ? ? ? ?// 而对于NIO而言,没有阻塞
 ? ? ? ? ? ? ? ?setRequestLineReadTimeout();
?
 ? ? ? ? ? ? ? ?// 解析请求行
 ? ? ? ? ? ? ? ?if (!getInputBuffer().parseRequestLine(keptAlive)) {
 ? ? ? ? ? ? ? ? ? ?// 下面这个方法在NIO时有用,比如在解析请求行时,如果没有从操作系统读到数据,则上面的方法会返回false
 ? ? ? ? ? ? ? ? ? ?// 而下面这个方法会返回true,从而退出while,表示此处read事件处理结束
 ? ? ? ? ? ? ? ? ? ?// 到下一次read事件发生了,就会从小进入到while中
 ? ? ? ? ? ? ? ? ? ?if (handleIncompleteRequestLineRead()) {
 ? ? ? ? ? ? ? ? ? ? ? ?break;
 ? ? ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ?  }
...
 if (maxKeepAliveRequests == 1) {
 ? ? ? ? ? ? ? ?// 如果最大的活跃http请求数量仅仅只能为1的话,那么设置keepAlive为false,则不会继续从socket中获取Http请求了
 ? ? ? ? ? ? ? ?keepAlive = false;
 ? ? ? ? ?  } else if (maxKeepAliveRequests > 0 &&
 ? ? ? ? ? ? ? ? ? ?socketWrapper.decrementKeepAlive() <= 0) {
 ? ? ? ? ? ? ? ?// 如果已经达到了keepAlive的最大限制,也设置为false,则不会继续从socket中获取Http请求了
 ? ? ? ? ? ? ? ?keepAlive = false;
 ? ? ?  }
 ? ? ? ?// 到这里是while(XXX) 循环结束了,不管是怎么结束的(判断好复杂我也没细看),反正是数据处理完了,要关闭连接也就是tcp层close了 ? ? 
 ? ? ? ? ? ? ? ?
 ? ? ? ?if (getErrorState().isError() || (endpoint.isPaused() && !isAsync())) {
 ? ? ? ? ? ?return SocketState.CLOSED;
 ? ? ?  } else if (isAsync()) {
 ? ? ? ? ? ?return SocketState.LONG;
 ? ? ?  } else if (isUpgrade()) {
 ? ? ? ? ? ?return SocketState.UPGRADING;
 ? ? ?  } else {
 ? ? ? ? ? ?if (sendfileState == SendfileState.PENDING) {
 ? ? ? ? ? ? ? ?return SocketState.SENDFILE;
 ? ? ? ? ?  } else {
 ? ? ? ? ? ? ? ?if (openSocket) {
 ? ? ? ? ? ? ? ? ? ?if (readComplete) {
 ? ? ? ? ? ? ? ? ? ? ? ?return SocketState.OPEN;
 ? ? ? ? ? ? ? ? ?  } else {
 ? ? ? ? ? ? ? ? ? ? ? ?return SocketState.LONG;
 ? ? ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ?  } else {
 ? ? ? ? ? ? ? ? ? ?return SocketState.CLOSED;
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
 ? ? ? // 反正一堆close,但是这个close没干活,他是一个枚举类相当于一个标志位!
}

3.随便找一个对枚举标志位的处理

这里随便找到,大体的关闭逻辑应该是差不多的
//Nio2Endpoint类中
?
if (state == SocketState.CLOSED) {
// Close socket and pool
socketWrapper.close();
?
// SocketWrapperBase
 ? ?public void close() {
 ? ? ? ?if (closed.compareAndSet(false, true)) {
 ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ?getEndpoint().getHandler().release(this);
 ? ? ? ? ?  } catch (Throwable e) {
 ? ? ? ? ? ? ? ?ExceptionUtils.handleThrowable(e);
 ? ? ? ? ? ? ? ?if (log.isDebugEnabled()) {
 ? ? ? ? ? ? ? ? ? ?log.error(sm.getString("endpoint.debug.handlerRelease"), e);
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  } finally {
 ? ? ? ? ? ? ? ?getEndpoint().countDownConnection();
 ? ? ? ? ? ? ? ?doClose();
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }
 ? ?
 ? ?
// Nio2Endpoint类的 ? ? 我把影响体验的删一删
 ? ? ? ?@Override
 ? ? ? ?protected void doClose() {
 ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ?getEndpoint().connections.remove(getSocket().getIOChannel());
 ? ? ? ? ? ? ? ?// 获取到socket ? ? 调用socket.close
 ? ? ? ? ? ? ? ?// 这不就和  标题4.中修改client代码,主动关闭再次测试 一样的道理吗
 ? ? ? ? ? ? ? ?if (getSocket().isOpen()) {
 ? ? ? ? ? ? ? ? ? ?getSocket().close(true);
 ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ? ?if (getEndpoint().running) {
 ? ? ? ? ? ? ? ? ? ?if (nioChannels == null || !nioChannels.push(getSocket())) {
 ? ? ? ? ? ? ? ? ? ? ? ?getSocket().free();
 ? ? ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  } catch (Throwable e) {
 ? ? ? ? ? ? ? ?ExceptionUtils.handleThrowable(e);
 ? ? ? ? ? ? ? ?if (log.isDebugEnabled()) {
 ? ? ? ? ? ? ? ? ? ?log.error(sm.getString("endpoint.debug.channelCloseFail"), e);
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  } finally {
 ? ? ? ? ? ? ? ?socketBufferHandler = SocketBufferHandler.EMPTY;
 ? ? ? ? ? ? ? ?nonBlockingWriteBuffer.clear();
 ? ? ? ? ? ? ? ?reset(Nio2Channel.CLOSED_NIO2_CHANNEL);
 ? ? ? ? ?  }
 ? ? ?  }   

8.总结

java中的socket是通过调用的操作系统(Linux,Windows)的socket接口进行通信的,socket帮我们完成了 底层通信,同时给我们提供了连接关闭的系统调用,我们在http层可以通过判断http协议传递过来的数据解析,根据keepalive参数判断是否调用连接关闭的系统调用。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-07-17 16:57:20  更:2022-07-17 16:59:17 
 
开发: 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/25 23:01:21-

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