[总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置
1 首先认识网络模型
如果你读过计算机专业,或者学习过网络通信,那你一定听说过 OSI 模型,它曾无数次让你头大。OSI 是 Open System Interconnection 的缩写,译为“开放式系统互联”。
OSI 模型把网络通信的工作分为 7 层,从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
OSI 只是存在于概念和理论上的一种模型,它的缺点是分层太多,增加了网络工作的复杂性,所以没有大规模应用。后来人们对 OSI 进行了简化,合并了一些层,最终只保留了 4 层,从下到上分别是接口层、网络层、传输层和应用层,这就是大名鼎鼎的 TCP/IP 模型。
我们平常使用的程序(或者说软件)一般都是通过应用层来访问网络的,程序产生的数据会 一层一层 地往下传输,直到最后的网络接口层,就通过网线发送到互联网上去了。数据每往下走一层,就会被这一层的协议增加一层包装,等到发送到互联网上时,已经比原始数据多了四层包装。整个数据封装的过程就像俄罗斯套娃。
当另一台计算机接收到数据包时,会从网络接口层再一层一层往上传输,每传输一层就拆开一层包装,直到最后的应用层,就得到了最原始的数据,这才是程序要使用的数据。 给数据加包装的过程,实际上就是在数据的头部增加一个标志(一个数据块),表示数据经过了这一层,我已经处理过了。给数据拆包装的过程正好相反,就是去掉数据头部的标志,让它逐渐现出原形。 你看,在互联网上传输一份数据是多么地复杂啊,而我们却感受不到,这就是网络模型的厉害之处。我们只需要在代码中调用一个函数,就能让下面的所有网络层为我们工作。
两台计算机进行通信时,必须遵守以下原则: 必须是同一层次进行通信,比如,A 计算机的应用层和 B 计算机的传输层就不能通信,因为它们不在一个层次,数据的拆包会遇到问题。 每一层的功能都必须相同,也就是拥有完全相同的网络模型。如果网络模型都不同,那不就乱套了,谁都不认识谁。 数据只能逐层传输,不能跃层。 每一层可以使用下层提供的服务,并向上层提供服务。
建立相关的概念, 比如什么是OSI七层网络模型 什么是 TCP/IP四层概念模型 tcp ,udp协议, http协议 socket 等区别? 详见TCP/IP ? TCP、UDP和HTTP, socket, websocket详解
1.1 网络模型
概念上 ,他们是OSI七层网络模型与TCP/IP四层网络模型中的协议集 图1-1OSI七层网络模型 |
TCP/IP四层概念模型 SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。 |
对应网络协议 |
应用层(Application) |
应用层 |
HTTP、TFTP, FTP, NFS, WAIS、SMTP |
表示层(Presentation) |
Telnet, Rlogin, SNMP, Gopher |
|
会话层(Session) |
SMTP, DNS |
|
传输层(Transport) |
传输层 |
TCP, UDP |
网络层(Network) |
网络层 |
IP, ICMP, ARP, RARP, AKP, UUCP |
数据链路层(Data Link) |
数据链路层 |
FDDI, Ethernet, Arpanet, PDN, SLIP, PPP |
物理层(Physical) |
IEEE 802.1A, IEEE 802.2到IEEE 802.11 |
- 物理层 (对应网卡,网线,集线器,中继器,调制解调器)
- 数据链路层 (对应网桥,交换机)
- 网络层 (对应路由器)
- 传输层(OSI下3层的主要任务是数据通信,上3层的任务是数据处理。而传输层(Transport Layer)是OSI模型的第4层。因此该层是通信子网和资源子网的接口和桥梁,起到承上启下的作用.传输层的作用是向高层屏蔽下层数据通信的细节,即向用户透明地传送报文。)
- 会车层 略..
- 表示层 略..
- 应用层(是计算机用户,以及各种应用程序和网络之间的接口,其功能是直接向用户提供服务,完成用户希望在网络上完成的各种工作。它在其他6层工作的基础上)
1.2 其他概念
1.2.1 TCP/IP
1.2.2 tcp/udp
UDP是无连接的用户数据报协议,所谓的无连接就是在传输数据之前不需要交换信息,没有握手建立连接的过程,只需要直接将对应的数据发送到指定的地址和端口就行(无法确认消息是否接收到)。故UDP的特点是不稳定,速度快,可广播,一般数据包限定64KB之内,先发未必先至。 (QQ等IM通讯一般可以使用UDP传输) 附表:tcp协议和udp协议的差别
TCP | UDP | |
是否连接 | 面向连接 | 面向非连接 |
传输可靠性 | 可靠 | 不可靠 |
应用场合 | 传输大量的数据,对可靠性要求较高的场合 | 传送少量数据、对可靠性要求不高的场景 |
速度 | 慢 | 快 |
1.2.3 HTTP
全称是Hypertext Transfer Protocol,即超文本传输协议。从名字上可以看出该协议用于规定客户端与服务端之间的传输规则,所传输的内容不局限于文本(HTML 文件, 图片文件, 查询结果等)。
HTTP协议中的数据是利用TCP协议传输的,所以支持HTTP也就一定支持TCP. 因为HTTP是基于TCP协议的应用,请求时需先建立TCP连接. 延伸阅读: ---------------------引用开始----------------------------
- HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
- HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
1.2.4 Socket
通过Socket,我们才能使用TCP/IP协议。tcp、udp,简单的说(虽然不准确)是两个最基本的协议,
很多其它协议都是基于这两个协议如,http就是基于tcp的,.用socket可以创建tcp连接,也可以创建udp连接. java C++ .net都有自己的socket编程实现.
Socket的大致位置如下: 为了便于编程而设想出来的API/编程模型
<img src="https://pic1.zhimg.com/50/v2-c5c1adef314050e1a524b4612e9d7c98_hd.jpg" data-size="normal" data-rawwidth="785" data-rawheight="653" class="origin_image zh-lightbox-thumb" width="785" data-original="https://pic1.zhimg.com/v2-c5c1adef314050e1a524b4612e9d7c98_r.jpg" />![[总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第2张 [总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第2张](https://www.liuyixiang.com/zb_users/theme/Lucky/style/image/grey.gif)
2 网络请求过程
以通过浏览器上网为例: 假设我们点击了某网页上的一个链接(HTTP),指向清华大学院系设置,其URL是:http://www.tsinghua.edu.cn/chn/yxsz/index.html。我们来分析一下整个过程: 1 浏览器分析链接指向页面的URL 2 浏览器向DNS请求解析www.tsinghua.edu.cn的IP地址 3 DNS系统解析出清华大学服务器的地址是166.111.4.100 4 浏览器与服务器建立TCP连接 5 浏览器发出取文件命令:GET /chn/yxsz/index.html 6 服务器www.tsinghua.edu.cn给出响应,把文件index.html返回给浏览器 7 释放TCP连接 8 浏览器解析并显示“清华大学院系设置”文件index.html中的内容对应到网络, 对照OSI模型是从请求方服务器物理层 到 应答方服务器物理层-> 应答方 XX-> 应答方 传输层(tcp/udp协议)-> 应答方 XXX-> 应答方 应用层(如http协议) 比如DNS服务器, 网卡等是属于其他网络模型分层的内容. 其中最主要, 我们会涉及到的就是传输层和应用层. 其他层涉及对应的硬件和协议一般开发不会涉及.
外部请求通过路由网关等将请求发送到应答方的服务器后: 1 建立TCP 或者UDP连接. 如果是TCP连接的建立,需要三次握手则可建立连接形成一条TCP通道. 随后即可进行数据的传输通信(如以http协议为标准传输通信). 如TCP连接断开, 则需要一个四次握手的过程. 如果UDP协议的连接则不用建立连接可直接通信. 这部分的处理应该是由应答方操作系统负责. 以传输层以TCP协议通信为例, 建立TCP通道后的数据请求由应答方的操作系统对应转给监听的应用.
2 数据通信 建立TCP连接(TCP通道)后, 后续的数据请求由操作系统放到 accept队列中. 由应用层的具体应用如TOMCAT来获取请求并处理并应答. 在TCP/UDP连接后, 事实上请求方服务器和响应方服务器即可通信. 如开发人员可通过socket(封装tcp/udp的api)便捷的通信. 如qq或者发送和接受gps数据. 双方都通过socket来实现 client 和server即可. 这部分socket的框架还能用mina , netty进一步开发. 对于普通web服务器而言, 浏览器请求解析的协议约定为HTTP协议, 所以请求方(一般为浏览器或者httpclinet)通过和响应方服务器建立TCP连接后, 即可在该TCP信道上以HTTP协议的方式进行数据通信(HTTP是传输数据的内容的规范, HTTP在传输层的传输实际还是用TCP协议通信). 而响应方一般是用NG或者TOMCAT服务器进行对应的HTTP请求的处理和响应.
延伸, Tomcat的角色 我们可知: 网络通信 1 必须是同一层次进行通信
2 数据只能逐层传输,不能跃层 3 每一层可以使用下层提供的服务,并向上层提供服务. Tomcat/Apache/NG之类的属于应用层, 传输层的请求由操作系统来处理. 由操作系统在传输层处理后交给应用层( Tomcat )处理. Tomcat处理后将响应反馈给操作系统, 再通过路由等方式反馈给请求方. (如果是LVS集群,内部会修改请求方的地址. tomcat将请求反馈给lvs或者NG, 再由LVS或者NG反馈给外部的请求方) Tomcat反馈数据的过程也必然是一个应用层-->传输层(操作系统)的过程.
![[总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第3张 [总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第3张](https://www.liuyixiang.com/zb_users/theme/Lucky/style/image/grey.gif)
3 长连接和短连接
3.1 HTTP, 长连接,短连接
HTTP是应用层协议.定义的是传输数据的内容的规范。
全称是Hypertext Transfer Protocol,即超文本传输协议。从名字上可以看出该协议用于规定客户端与服务端之间的传输规则,所传输的内容不局限于文本(HTML 文件, 图片文件, 查询结果等)。
HTTP对应在传输层使用的还是TCP协议进行通信.
3 3 1
HTTP是应用层协议.定义的是传输数据的内容的规范。
2
全称是Hypertext Transfer Protocol,即超文本传输协议。从名字上可以看出该协议用于规定客户端与服务端之间的传输规则,所传输的内容不局限于文本(HTML 文件, 图片文件, 查询结果等)。
3
HTTP对应在传输层使用的还是TCP协议进行通信.
- HTTP基于请求响应模型。
- HTTP是无连接的
- HTTP协议是无状态的协议
HTTP协议的最初实现是,每一个http请求都会打开一个tcp socket连接,当交互完毕后会关闭这个连接(TCP建立连接与断开连接是要经过三次握手与四次挥手的)。 显然在这种设计中,每次发送Http请求都会消耗很多的额外资源,即连接的建立与销毁。 在HTTP最初的使用中(HTTP 1.1之前的版本, 默认每次HTTP请求创建TCP连接, 建立TCP通道后 再基于TCP使用HTTP协议传输数据. 一次HTTP请求(包含请求+响应或者超时) 后 即HTTP请求结束, 同时关闭TCP连接. 整个过程最大的开销其实是创建TCP连接的过程. 于是,HTTP协议的也进行了发展,通过持久连接的方法来进行socket连接复用。 建立一次TCP连接(TCP通道)后, 只通信一次HTTP就关闭TCP连接. 这种被成为HTTP 短连接. 建立一次TCP连接(TCP通道)后, 可多次进行HTTP通信, 保留一定的时间或者HTTP请求次数后才关闭TCP连接. 这种被成为HTTP 长连接或者持久连接.
总之 TCP本身没有什么长连接/短连接. 只是HTTP创建TCP连接后, 一次通信结束后是否立刻调用 socket.close()关闭TCP连接. 如是则我们称呼为HTTP短连接. 如果HTTP创建TCP连接后, 可多次利用TCP通道进行HTTP协议的通信,而非一次通信就立刻关闭TCP连接, 这种使用方式我们称呼为HTTP长连接/持久连接. 长连接的好处是省去了创建连接的耗时。
从图中可以看到:
在串行连接中,每次交互都要打开关闭连接
在持久连接中,第一次交互会打开连接,交互结束后连接并不关闭,下次交互就省去了建立连接的过程。
3.2 HTTP的版本
以下参考来自 【Http】TCP连接、Http连接与Socket连接的区别 HTTP版本简单分为三类:1.1之前,1.1,2.0 , 3.0 HTTP 1.1之前
- 不支持持久连接。一旦服务器对客户端发出响应就立即断开TCP连接 (即每次http请求都是短连接,每次请求需要先创建tcp连接(三次握手)后才能发送http请求,得到响应后通过四次握手关闭tcp连接)
- 无请求头跟响应头
- 客户端的前后请求是同步的。下一个请求必须等上一个请求从服务端拿到响应后才能发出,有点类似多线程的同步机制。
- 与1.1之前的版本相比,做了以下性能上的提升
- 增加请求头跟响应头
- 支持持久连接。客户端通过请求头中指定Connection为keep-alive告知服务端不要在完成响应后立即释放连接。HTTP是基于TCP的,在HTTP 1.1中一次TCP连接可以处理多次HTTP请求
- 客户端不同请求之间是异步的。下一个请求不必等到上一个请求回来后再发出,而可以连续发出请求,有点类似多线程的异步处理。
HTTP 2.0
本着向下兼容的原则,1.1版本有的特性2.0都具备,也使用相同的API。但是 2.0将只用于https网址。 由于2.0的普及还需要比较长的一段时间,这里不展开 我们重点关注一下当前1.1版本所做几点改变。HTTP1.1 支持持久连接有什么好处呢?HTTP是基于TCP连接的,如果连接被频繁地启动然后断开就会花费很多资源在TCP三次握手以及四次挥手上,效率低下。以请求一个网页为例, 我们知道,一个html网页上的图片资源并不是直接嵌入在网页上,而只是提供url,图片仍需要额外发 HTTP 请求去下载。一个网页从请求到最终加载到本地往往需要经过多个HTTP请求。在1.1版本之前请求一个网页就需要发生多次"握手-挥手"的过程,每次连接之间相互独立(即通过http短连接, 每次都请求都需要和web应用所在的操作系统通过三次握手建立tcp连接,而建立tcp连接很浪费时间和资源。); 而1.1及之后的版本最少只需要一次就够。
HTTP各版本的改进说明详见 [写的很不错]HTTP/2.0 相比1.0有哪些重大改进? https://www.zhihu.com/question/34074946
3.3 长连接/短连接
3.3.1 概念
如上可知, 长连接(持久连接) 和短连接只是HTTP协议中使用TCP方式的不同而已(是否多次使用同一个TCP通道). 短连接和长连接的优势,分别是对方的劣势。想要图简单,不追求高性能,使用短连接合适,这样我们就不需要操心连接状态的管理;想要追求性能,使用长连接.在 HTTP/1.0中,默认 使用的是 短连接 。也就是说,浏览器和服务器每进行一次HTTP操作,就 建立一次连接,但任务结束就中断连接 。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。这样一个网页有很多图片或者文件的情况下, 每次请求都要创建一个TCP连接非常耗时.(同时浏览器同域名请求, 是有的最大并发数限制的. 比如chrome在HTTP/1.0协议中同一个域名请求的最大并发是6. 如果使用chrome 以短连接的方式来请求同一个域名, 那么chrome最多是6个并发同时请求, 分别获取网页的资源文件(css,jsp,img 等等). 而且每次都要创建TCP连接. 大大影响网页打开速度) 但从 HTTP/1.1起,默认使用长连接(持久连接),用以保持连接特性。(HTTP持久连接(HTTP persistent connection,也称作HTTP keep-alive或HTTP connection reuse)是使用同一个TCP连接来发送和接收多个HTTP请求/应答, 而不是为每一个新的请求/应答打开新的连接的方法。) 使用长连接的HTTP协议,会在响应头有加入这行代码: Connection:keep-alive 服务器和客户端都要设置 在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
3.3.2 HTTP/1.0+的Keep-Alive
从1996年开始,很多HTTP/1.0浏览器与服务器都对协议进行了扩展,那就是“keep-alive”扩展协议。注意,这个扩展协议是作为1.0的补充的“实验型持久连接”出现的。keep-alive已经不再使用了,最新的HTTP/1.1规范中也没有对它进行说明,只是很多应用延续了下来。 在 HTTP 1.0 中, 没有官方的 keepalive 的操作。通常是在现有协议上添加一个指数。 如果浏览器支持 keep-alive,它会在请求的包头中添加:Connection: Keep-Alive
1 1 1
Connection: Keep-Alive
然后当服务器收到请求,作出回应的时候,它也添加一个头在响应中:
Connection: Keep-Alive
1 1 1
Connection: Keep-Alive
这样做,连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接。 这一直继续到客户端或服务器端认为会话已经结束,其中一方中断连接。
![[总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第5张 [总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第5张](https://www.liuyixiang.com/zb_users/theme/Lucky/style/image/grey.gif)
通过keep-alive补充协议,客户端与服务器之间完成了持久连接,然而仍然存在着一些问题: 在HTTP/1.0中keep-alive不是标准协议,客户端必须发送Connection:Keep-Alive来激活keep-alive连接。 代理服务器(apache之类)可能无法支持keep-alive,因为一些代理是"盲中继",无法理解首部的含义,只是将首部逐跳转发。所以可能造成客户端与服务端都保持了连接,但是代理不接受该连接上的数据。
3.3.3 HTTP/1.1的持久连接
HTTP/1.1采取持久连接的方式替代了Keep-Alive。 HTTP 1.1 中 所有的连接默认都是持续连接,除非特殊声明不支持。如果要显式关闭,需要在报文中加上Connection:Close首部。即在HTTP/1.1中,所有的连接都进行了复用。然而如同Keep-Alive一样,空闲的持久连接也可以随时被客户端与服务端关闭。不发送Connection:Close不意味着服务器承诺连接永远保持打开。然而,Apache 2.0 httpd 的默认连接过期时间是仅仅15秒,对于 Apache 2.2 只有5秒。 短的过期时间的优点是能够快速的传输多个web页组件,而不会绑定多个服务器进程或线程太长时间。
![[总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第6张 [总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第6张](https://www.liuyixiang.com/zb_users/theme/Lucky/style/image/grey.gif)
长连接短连接的其他内容见 聊聊 TCP 长连接和心跳那些事 TCP长连接和短连接的区别
4 Socket编程
我们知道网络传输的协议是TCP/UDP. 我们的请求响应交互其实对应就是client/server. 对于编程的语言的学习,我们通常需要掌握Socket编程. Socket的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”. http://c.biancheng.net/view/2126.html![[总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第7张 [总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第7张](https://www.liuyixiang.com/zb_users/theme/Lucky/style/image/grey.gif)
Socket封装了复杂的底层协议, 是对外的提供简单的API, 以便编程. 所以我们所说的 socket 编程,是站在传输层的基础上,所以可以使用 TCP/UDP 协议,但是不能干「访问网页」这样的事情,因为访问网页所需要的 http 协议位于应用层。 Socket通讯的过程
Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。 对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤: (1) 创建Socket; (2) 打开连接到Socket的输入/出流; (3) 按照一定的协议对Socket进行读/写操作; (4) 关闭Socket.
java实现参考如下, 可跳过 client.java
Socket socket=new Socket("127.0.0.1",4700);
//向本机的4700端口发出客户请求
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//由系统标准输入设备构造BufferedReader对象
PrintWriter os=new PrintWriter(socket.getOutputStream());
//由Socket对象得到输出流,并构造PrintWriter对象
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//由Socket对象得到输入流,并构造相应的BufferedReader对象
String readline;
readline=sin.readLine(); //从系统标准输入读入一字符串
while(!readline.equals("bye")){
//若从标准输入读入的字符串为 "bye"则停止循环
os.println(readline);
//将从系统标准输入读入的字符串输出到Server
os.flush();
//刷新输出流,使Server马上收到该字符串
System.out.println("Client:"+readline);
//在系统标准输出上打印读入的字符串
System.out.println("Server:"+is.readLine());
//从Server读入一字符串,并打印到标准输出上
readline=sin.readLine(); //从系统标准输入读入一字符串
} //继续循环
os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
}catch(Exception e) {
System.out.println("Error"+e); //出错,则打印出错信息
}
56 56 1
Socket socket=new Socket("127.0.0.1",4700);
2
3
//向本机的4700端口发出客户请求
4
5
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
6
7
//由系统标准输入设备构造BufferedReader对象
8
9
PrintWriter os=new PrintWriter(socket.getOutputStream());
10
11
//由Socket对象得到输出流,并构造PrintWriter对象
12
13
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
14
15
//由Socket对象得到输入流,并构造相应的BufferedReader对象
16
17
String readline;
18
19
readline=sin.readLine(); //从系统标准输入读入一字符串
20
21
while(!readline.equals("bye")){
22
23
//若从标准输入读入的字符串为 "bye"则停止循环
24
25
os.println(readline);
26
27
//将从系统标准输入读入的字符串输出到Server
28
29
os.flush();
30
31
//刷新输出流,使Server马上收到该字符串
32
33
System.out.println("Client:"+readline);
34
35
//在系统标准输出上打印读入的字符串
36
37
System.out.println("Server:"+is.readLine());
38
39
//从Server读入一字符串,并打印到标准输出上
40
41
readline=sin.readLine(); //从系统标准输入读入一字符串
42
43
} //继续循环
44
45
os.close(); //关闭Socket输出流
46
47
is.close(); //关闭Socket输入流
48
49
socket.close(); //关闭Socket
50
51
}catch(Exception e) {
52
53
System.out.println("Error"+e); //出错,则打印出错信息
54
55
}
56
server.java
try{
ServerSocket server=null;
try{
server=new ServerSocket(4700);
//创建一个ServerSocket在端口4700监听客户请求
}catch(Exception e) {
System.out.println("can not listen to:"+e);
//出错,打印出错信息
}
Socket socket=null;
try{
socket=server.accept();
//使用accept()阻塞等待客户请求,有客户
//请求到来则产生一个Socket对象,并继续执行
}catch(Exception e) {
System.out.println("Error."+e);
//出错,打印出错信息
}
String line;
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//由Socket对象得到输入流,并构造相应的BufferedReader对象
PrintWriter os=newPrintWriter(socket.getOutputStream());
//由Socket对象得到输出流,并构造PrintWriter对象
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//由系统标准输入设备构造BufferedReader对象
System.out.println("Client:"+is.readLine());
//在标准输出上打印从客户端读入的字符串
line=sin.readLine();
//从标准输入读入一字符串
while(!line.equals("bye")){
//如果该字符串为 "bye",则停止循环
os.println(line);
//向客户端输出该字符串
os.flush();
//刷新输出流,使Client马上收到该字符串
System.out.println("Server:"+line);
//在系统标准输出上打印读入的字符串
System.out.println("Client:"+is.readLine());
//从Client读入一字符串,并打印到标准输出上
line=sin.readLine();
//从系统标准输入读入一字符串
} //继续循环
os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
server.close(); //关闭ServerSocket
}catch(Exception e){
System.out.println("Error:"+e);
//出错,打印出错信息
}
}
101 101 1
try{
2
3
ServerSocket server=null;
4
5
try{
6
7
server=new ServerSocket(4700);
8
9
//创建一个ServerSocket在端口4700监听客户请求
10
11
}catch(Exception e) {
12
13
System.out.println("can not listen to:"+e);
14
15
//出错,打印出错信息
16
17
}
18
19
Socket socket=null;
20
21
try{
22
23
socket=server.accept();
24
25
//使用accept()阻塞等待客户请求,有客户
26
27
//请求到来则产生一个Socket对象,并继续执行
28
29
}catch(Exception e) {
30
31
System.out.println("Error."+e);
32
33
//出错,打印出错信息
34
35
}
36
37
String line;
38
39
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
40
41
//由Socket对象得到输入流,并构造相应的BufferedReader对象
42
43
PrintWriter os=newPrintWriter(socket.getOutputStream());
44
45
//由Socket对象得到输出流,并构造PrintWriter对象
46
47
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
48
49
//由系统标准输入设备构造BufferedReader对象
50
51
System.out.println("Client:"+is.readLine());
52
53
//在标准输出上打印从客户端读入的字符串
54
55
line=sin.readLine();
56
57
//从标准输入读入一字符串
58
59
while(!line.equals("bye")){
60
61
//如果该字符串为 "bye",则停止循环
62
63
os.println(line);
64
65
//向客户端输出该字符串
66
67
os.flush();
68
69
//刷新输出流,使Client马上收到该字符串
70
71
System.out.println("Server:"+line);
72
73
//在系统标准输出上打印读入的字符串
74
75
System.out.println("Client:"+is.readLine());
76
77
//从Client读入一字符串,并打印到标准输出上
78
79
line=sin.readLine();
80
81
//从系统标准输入读入一字符串
82
83
} //继续循环
84
85
os.close(); //关闭Socket输出流
86
87
is.close(); //关闭Socket输入流
88
89
socket.close(); //关闭Socket
90
91
server.close(); //关闭ServerSocket
92
93
}catch(Exception e){
94
95
System.out.println("Error:"+e);
96
97
//出错,打印出错信息
98
99
}
100
101
}
在这种编程模式中, 双方可以互为client/server 这样就实现了类似QQ这类IM的功能. 如果server方遇到的请求压力很大, 这部分的延伸就是 [Java线程池的分析和使用]如https://ifeve.com/java-threadpool/ 而 socket的相关工具框架 见 mina, netty. ---------------------引用开始---------------------------- 延伸阅读: 摘自 https://blog.csdn.net/qq_32331073/article/details/82665419 在java 原生的Socket编程中, 实际上Socket编程就是所谓的网络编程也就是基于TCP/UDP网络层协议进行编程,就拿Java和TCP来说: 对于BIO,TCP编程就是ServerSocket/Socket 对于NIO,TCP编程就是ServerSocketChannel/SocketChannel 对于AIO,TCP编程就是AsynchronousServerSocketChannel/AsynchronousSocketChanne ---------------------引用结束----------------------------
5 HTTP编程和连接池
摘自 https://juejin.im/post/5aefc08af265da0b78686ca55.1 HttpComponents
对于浏览器的请求, 协议自然是基于HTTP协议. client端HTTP协议请求的编程( 比如我方系统对外部系统发起HTTP请求获取JSON之类) 解决方案一般是使用apache的 HttpClinet 工具(已经改名为Apache HttpComponents). 通过 HttpComponents可以在Java中实现httpclinet 和httpserver的编程.5.2 HTTP连接池
Http连接需要的三次握手开销很大,这一开销对于比较小的http消息来说更大。但是如果我们直接使用已经建立好的http连接,这样花费就比较小,吞吐率更大。 传统的HttpURLConnection并不支持连接池,如果要实现连接池的机制,还需要自己来管理连接对象。除了HttpURLConnection,大家肯定还知道HttpClient。一般情况下,普通使用HttpClient已经能满足我们的需求,不过有时候,在我们需要高并发大量的请求网络的时候,还是用“连接池”这样的概念能提升吞吐量。 比如org.apache.httpcomponents.httpclient(版本4.4)提供的连接池(PoolingHttpClientConnectionManager)来实现我们的高并发网络请求。HttpClient如何生成持久连接 HttpClien中使用了连接池来管理持有连接,同一条TCP链路上,连接是可以复用的。HttpClient通过连接池的方式进行连接持久化。 其实“池”技术是一种通用的设计,其设计思想并不复杂:
- 当有连接第一次使用的时候建立连接
- 结束时对应连接不关闭,归还到池中
- 下次同个目的的连接可从池中获取一个可用连接
- 定期清理过期连接
5.2.1 使用连接池的好处
1、降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗,别小看这几次握手,本人经过测试发现,基本上3倍的时间延迟 2、支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接5.2.2 简单连接管理器
BasicHttpClientConnectionManager
是个简单的连接管理器,它一次只能管理一个连接。尽管这个类是线程安全的,它在同一时间也只能被一个线程使用。
BasicHttpClientConnectionManager
会尽量重用旧的连接来发送后续的请求,并且使用相同的路由。如果后续请求的路由和旧连接中的路由不匹配,
BasicHttpClientConnectionManager
就会关闭当前连接,使用请求中的路由重新建立连接。如果当前的连接正在被占用,会抛出
java.lang.IllegalStateException
异常。
5.2.3 连接池管理器
相对BasicHttpClientConnectionManager来说,PoolingHttpClientConnectionManager是个更复杂的类,它管理着连接池,可以同时为很多线程提供http连接请求。Connections are pooled on a per route basis.当请求一个新的连接时,如果连接池有有可用的持久连接,连接管理器就会使用其中的一个,而不是再创建一个新的连接。PoolingHttpClientConnectionManager维护的连接数在每个路由基础和总数上都有限制。默认,每个路由基础上的连接不超过2个,总连接数不能超过20。在实际应用中,这个限制可能会太小了,尤其是当服务器也使用Http协议时。 下面的例子演示了如果调整连接池的参数: PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 将最大连接数增加到200
cm.setMaxTotal(200);
// 将每个路由基础的连接增加到20
cm.setDefaultMaxPerRoute(20);
//将目标主机的最大连接数增加到50
HttpHost localhost = new HttpHost("www.yeetrack.com", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
13 13 1
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
2
// 将最大连接数增加到200
3
cm.setMaxTotal(200);
4
// 将每个路由基础的连接增加到20
5
cm.setDefaultMaxPerRoute(20);
6
//将目标主机的最大连接数增加到50
7
HttpHost localhost = new HttpHost("www.yeetrack.com", 80);
8
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
9
10
CloseableHttpClient httpClient = HttpClients.custom()
11
.setConnectionManager(cm)
12
.build();
13
5.2.4 关闭连接管理器
当一个HttpClient的实例不在使用,或者已经脱离它的作用范围,我们需要关掉它的连接管理器,来关闭掉所有的连接,释放掉这些连接占用的系统资源。 CloseableHttpClient httpClient = <...>
httpClient.close();
2 2 1
CloseableHttpClient httpClient = <...>
2
httpClient.close();
5.2.5 连接回收策略
经典阻塞I/O模型的一个主要缺点就是只有当组侧I/O时,socket才能对I/O事件做出反应。当连接被管理器收回后,这个连接仍然存活,但是却无法监控socket的状态,也无法对I/O事件做出反馈。如果连接被服务器端关闭了,客户端监测不到连接的状态变化(也就无法根据连接状态的变化,关闭本地的socket)。
HttpClient为了缓解这一问题造成的影响,会在使用某个连接前,监测这个连接是否已经过时,如果服务器端关闭了连接,那么连接就会失效。这种过时检查并不是100%有效,并且会给每个请求增加10到30毫秒额外开销。唯一一个可行的,且does not involve a one thread per socket model for idle connections的解决办法,是建立一个监控线程,来专门回收由于长时间不活动而被判定为失效的连接。这个监控线程可以周期性的调用ClientConnectionManager
类的closeExpiredConnections()
方法来关闭过期的连接,回收连接池中被关闭的连接。它也可以选择性的调用ClientConnectionManager
类的closeIdleConnections()
方法来关闭一段时间内不活动的连接。
5.2.6 连接存活策略
Http规范没有规定一个持久连接应该保持存活多久。有些Http服务器使用非标准的Keep-Alive头消息和客户端进行交互,服务器端会保持数秒时间内保持连接。HttpClient也会利用这个头消息。如果服务器返回的响应中没有包含Keep-Alive头消息,HttpClient会认为这个连接可以永远保持。然而,很多服务器都会在不通知客户端的情况下,关闭一定时间内不活动的连接,来节省服务器资源。6 Tomcat 处理请求过程
详细参考 Tomcat的acceptCount与maxConnections 图6-1
![[总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第8张 [总结] 网络概念性问题(TCP/UDP, HTTP, 长连接/短连接) TOMCAT处理请求/参数配置 随笔 第8张](https://www.liuyixiang.com/zb_users/theme/Lucky/style/image/grey.gif)
外部请求传输层由操作系统来处理(如TCP连接创建) (操作系统的网络内核对TCP连接的本身也有限制. 比如Linux网络内核对本地端口号范围有限制(Linux内核的TCP/IP协议实现模块对系统中所有的客户端TCP连接对应的本地端口号的范围进行了限制) 或者比如Linux网络内核的IP_TABLE防火墙对最大跟踪的TCP连接数有限制. 等等. ) TCP创建后操作系统内核会把连接从syn队列中取出,再把这个连接放到accept队列中. 最后应用服务器(Tomcat)调用accept系统调用从accept队列中获取已经建立成功的连接套接字.
6.1 Tomcat的相关配置
6.1.1 配置文件server.xml
见 详解Tomcat 配置文件server.xml 或 http://www.cnblogs.com/kismetv/p/7228274.html6.1.2 认识Connector
( 详解tomcat的连接数与线程池 ) Tomcat Connector 的主要功能,是接收连接请求,创建Request和Response对象用于和请求端交换数据;然后分配线程让Engine(也就是Servlet容器)来处理这个请求,并把产生的Request和Response对象传给Engine。当Engine处理完请求后,也会通过Connector将响应返回给客户端。可以说,Servlet容器处理请求,是需要Connector进行调度和控制的,Connector是Tomcat处理请求的主干,因此Connector的配置和使用对Tomcat的性能有着重要的影响。 Connector在处理HTTP请求时,会使用不同的protocol。不同的Tomcat版本支持的protocol不同,其中最典型的protocol包括BIO、NIO和APR(Tomcat7中支持这3种,Tomcat8增加了对NIO2的支持,而到了Tomcat8.5和Tomcat9.0,则去掉了对BIO的支持)。 BIO是Blocking IO,顾名思义是阻塞的IO;NIO是Non-blocking IO,则是非阻塞的IO。而APR是Apache Portable Runtime,是Apache可移植运行库,利用本地库可以实现高可扩展性、高性能;Apr是在Tomcat上运行高并发应用的首选模式,但是需要安装apr、apr-utils、tomcat-native等包。 如果没有指定protocol,则使用默认值HTTP/1.1,其含义如下:在Tomcat7中,自动选取使用BIO或APR(如果找到APR需要的本地库,则使用APR,否则使用BIO);在Tomcat8中,自动选取使用NIO或APR(如果找到APR需要的本地库,则使用APR,否则使用NIO)6.1.3 BIO/NIO有何不同
无论是BIO,还是NIO,Connector处理请求的大致流程是一样的: 在accept队列中接收连接(当客户端向服务器发送请求时,如果客户端与操作系统完成三次握手建立了连接,则操作系统将该连接放入accept队列);在连接中获取请求的数据,生成request;调用servlet容器处理请求;返回response。为了便于后面的说明,首先明确一下连接与请求的关系:连接是TCP层面的(传输层),对应socket(tcp/ip协议组的api封装,方便调用);请求是HTTP层面的(应用层),必须依赖于TCP的连接实现;一个TCP连接中可能传输多个HTTP请求。 在BIO 实现的Connector中,处理请求的主要实体是JIoEndpoint对象。JIoEndpoint维护了Acceptor和Worker: Acceptor接收socket,然后从Worker工作线程池中找出空闲的线程处理socket,如果worker线程池没有空闲线程,则Acceptor将阻塞。其中Worker是Tomcat自带的线程池,如果通过<Executor>配置了其他线程池,原理与Worker类似。在NIO实现的Connector中,处理请求的主要实体是NIoEndpoint对象。NIoEndpoint中除了包含Acceptor和Worker外,还使用了Poller,处理流程如下图所示(图片来源:http://gearever.iteye.com/blog/1844203)。
Acceptor接收socket后,不是直接使用Worker中的线程处理请求,而是先将请求发送给了Poller,而Poller是实现NIO的关键。Acceptor向Poller发送请求通过队列实现,使用了典型的生产者-消费者模式。在Poller中,维护了一个Selector对象;当Poller从队列中取出socket后,注册到该Selector中;然后通过遍历Selector,找出其中可读的socket,并使用Worker中的线程处理相应请求。与BIO类似,Worker也可以被自定义的线程池代替。
通过上述过程可以看出,在NIoEndpoint处理请求的过程中,无论是Acceptor接收socket,还是线程处理请求,使用的仍然是阻塞方式;但在“读取socket并交给Worker中的线程”的这个过程中,使用非阻塞的NIO实现,这是NIO模式与BIO模式的最主要区别(其他区别对性能影响较小,暂时略去不提)。而这个区别,在并发量较大的情形下可以带来Tomcat效率的显著提升:
目前大多数HTTP请求使用的是长连接(HTTP/1.1默认keep-alive为true),而长连接意味着,一个TCP的socket在当前请求结束后,如果没有新的请求到来,socket不会立马释放,而是等timeout后再释放。如果使用BIO,“读取socket并交给Worker中的线程”这个过程是阻塞的,也就意味着在socket等待下一个请求或等待释放的过程中,处理这个socket的工作线程会一直被占用,无法释放;因此Tomcat可以同时处理的socket数目不能超过最大线程数,性能受到了极大限制。而使用NIO,“读取socket并交给Worker中的线程”这个过程是非阻塞的,当socket在等待下一个请求或等待释放时,并不会占用工作线程,因此Tomcat可以同时处理的socket数目远大于最大线程数,并发性能大大提高。
6.1.4 参数说明
个人理解, 可能有误.
如图6-1所示, Tomcat在accept队列中接收连接,
1 正常情况网络请求从accpet队列进入Tomcat, 占用Tomcat的连接数(maxConnections为可用连接的上限.) 获得Tomcat连接后就交由cpu线程来处理(tomcat能用的线程受硬件和maxThreads参数限制) 请求处理后Tomcat连接释放, 对外进行数据响应. 2 如网络请求进入Tomcat时, 占用Tomcat的连接数已达上限(maxConnections ). 则请求进入accept队列等待(队列的长度收到tomcat的参数 acceptCount限制, 底层更受到操作系统的限制. acceptCount参数是tomcat server在操作系统的tcp accept队列的大小限制设置的基础上,在tomcat级别又再多做了一层限制。 TCP层面有两个队列:半连接队列(syn队列)与完全连接队列(accept队列)。syn队列的大小取决于max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog);accept队列的大小取决于min(backlog, somaxconn),somaxconn是一个os级别的系统参数,而backlog的值可以由我们的应用程序(tomcat)去定义。Tomcat的acceptCount参数对应被映射成backlog
)
如操作系统将请求进入accept队列时发现队列已满, 则请求直接被拒绝.
所以, maxConnections表示有多少个socket连接到tomcat上。NIO模式下默认是10000。 而maxThreads则是woker线程并发处理 请求的最大数.
Tomcat(应用服务器代表)的acceptor线程则负责从accept队列中取出该Connection,然后交给工作线程去处理(读取请求参数、处理逻辑、返回响应等等。如果该连接不是keep alived的话,即请求为短连接,则关闭该连接,然后该工作线程释放回线程池;如果是keep alived的话(长连接),则等待下一个数据包的到来直到keepAliveTimeout,然后关闭该连接释放回线程池),然后自己接着去accept队列取Connection。
虽然tomcat同时可以处理的连接数目是maxConnections,但服务器中可以同时接收的连接数为maxConnections+acceptCount
- acceptCount(最大排队数)
- maxConnections(最大连接数)
- maxThreads(处理线程的最大数量)
延伸阅读 Tomcat调优总结(Tomcat自身优化、Linux内核优化、JVM优化) https://www.cnblogs.com/whx7762/p/9290242.html
其他
延伸阅读: 偏流程性质的说明,如下:
“天龙八步”细说浏览器输入URL后发生了什么 - 雪融无痕 - 博客园
【干货】十分钟读懂浏览器渲染流程 - 雪融无痕 - 博客园
相关说明并未深入os,tomcat是如何处理请求,以及http长短连接的问题。 网站架构从0起步系列文章总目录 http://www.cnblogs.com/f-ck-need-u/p/7576137.html#tomcat
参考
http://c.biancheng.net/view/2126.html1 建立最主要的整体概念 通过 Tomcat的acceptCount与maxConnections 2 简单的对 acceptCount、maxConnections、maxThreads这几个概念的简单再说明 通过 tomcat的acceptCount与maxConnections
3 详细的完整补充: 见 详解tomcat的连接数与线程池 对Nio、Bio、APR, acceptCount、maxConnections、maxThreads, 线程池Executor都有论述.
https://segmentfault.com/q/1010000016846975
来自为知笔记(Wiz)
