新随笔  :: 订阅 订阅  :: 管理

1 首先认识网络模型

如果你读过计算机专业,或者学习过网络通信,那你一定听说过 OSI 模型,它曾无数次让你头大。OSI 是 Open System Interconnection 的缩写,译为“开放式系统互联”。
OSI 模型把网络通信的工作分为 7 层,从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
OSI 只是存在于概念和理论上的一种模型,它的缺点是分层太多,增加了网络工作的复杂性,所以没有大规模应用。后来人们对 OSI 进行了简化,合并了一些层,最终只保留了 4 层,从下到上分别是接口层、网络层、传输层和应用层,这就是大名鼎鼎的 TCP/IP 模型。

我们平常使用的程序(或者说软件)一般都是通过应用层来访问网络的,程序产生的数据会一层一层地往下传输,直到最后的网络接口层,就通过网线发送到互联网上去了。数据每往下走一层,就会被这一层的协议增加一层包装,等到发送到互联网上时,已经比原始数据多了四层包装。整个数据封装的过程就像俄罗斯套娃。
当另一台计算机接收到数据包时,会从网络接口层再一层一层往上传输,每传输一层就拆开一层包装,直到最后的应用层,就得到了最原始的数据,这才是程序要使用的数据。
给数据加包装的过程,实际上就是在数据的头部增加一个标志(一个数据块),表示数据经过了这一层,我已经处理过了。给数据拆包装的过程正好相反,就是去掉数据头部的标志,让它逐渐现出原形。
你看,在互联网上传输一份数据是多么地复杂啊,而我们却感受不到,这就是网络模型的厉害之处。我们只需要在代码中调用一个函数,就能让下面的所有网络层为我们工作。

两台计算机进行通信时,必须遵守以下原则:
必须是同一层次进行通信,比如,A 计算机的应用层和 B 计算机的传输层就不能通信,因为它们不在一个层次,数据的拆包会遇到问题。
每一层的功能都必须相同,也就是拥有完全相同的网络模型。如果网络模型都不同,那不就乱套了,谁都不认识谁。
数据只能逐层传输,不能跃层。
每一层可以使用下层提供的服务,并向上层提供服务。

建立相关的概念, 比如什么是OSI七层网络模型 什么是TCP/IP四层概念模型 tcp ,udp协议, http协议 socket 等区别?

1.1 网络模型

概念上 ,他们是OSI七层网络模型与TCP/IP四层网络模型中的协议集
图1-1

OSI七层网络模型

TCP/IP四层概念模型  

对应网络协议

应用层(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

和OSI网络模型类似的,它代表四层网络模型, 而所谓TCP/IP协议指的就是网络模型中的一整套协议.(见图1-1).
TCP/IP 模型包含了 TCP、IP、UDP、Telnet、FTP、SMTP 等上百个互为关联的协议,其中 TCP 和 IP 是最常用的两种底层协议,所以把它们统称为“TCP/IP 协议族”。也就是说,“TCP/IP模型”中所涉及到的协议称为“TCP/IP协议族”,你可以区分这两个概念,也可以认为它们是等价的,随便你怎么想。 
  • 1.2.2 tcp/udp

是传输层的协议.
延伸的概念有:  tcp/upd的区别, tcp创建和断开等.
---------------------引用开始----------------------------
TCP是面向连接的一种底层传输控制协议。TCP连接之后,客户端和服务器可以互相发送和接收消息,在客户端或者服务器没有主动断开之前,连接一直存在,故称为长连接。特点:连接有耗时,传输数据无大小限制,准确可靠,先发先至
延伸阅读: TCP的连接: 三次握手(创建tcp连接)和四次挥手(关闭tcp连接)
(建立起一个TCP连接需要经过“三次握手”:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。握 手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连 接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写 了,就是服务器和客户端交互,最终确定断开)

UDP是无连接的用户数据报协议,所谓的无连接就是在传输数据之前不需要交换信息没有握手建立连接的过程,只需要直接将对应的数据发送到指定的地址和端口就行(无法确认消息是否接收到)。故UDP的特点是不稳定,速度快,可广播,一般数据包限定64KB之内,先发未必先至。 (QQ等IM通讯一般可以使用UDP传输)
 附表:tcp协议和udp协议的差别
 TCPUDP
是否连接面向连接面向非连接
传输可靠性可靠不可靠
应用场合传输大量的数据,对可靠性要求较高的场合传送少量数据、对可靠性要求不高的场景
速度
---------------------引用结束----------------------------

  • 1.2.3  HTTP

应用层协议.定义的是传输数据的内容的规范。而在传输层, HTTP基于的是TCP协议.

全称是Hypertext Transfer Protocol,即超文本传输协议。从名字上可以看出该协议用于规定客户端与服务端之间的传输规则,所传输的内容不局限于文本(HTML 文件, 图片文件, 查询结果等)。

1053533-7152b9157998397d.png


HTTP协议中的数据是利用TCP协议传输的,所以支持HTTP也就一定支持TCP. 因为HTTP是基于TCP协议的应用,请求时需先建立TCP连接. 
延伸阅读:
---------------------引用开始----------------------------
  • HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
  • HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
更多内容  如(HTTP 消息结构 HTTP请求方法 HTTP 响应头信息 HTTP状态码 HTTP content-type等) 
见 https://www.runoob.com/http/http-messages.html 
---------------------引用结束----------------------------

  • 1.2.4 Socket

socket只是一种连接模式,不是协议,socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),socket本质是编程接口(API) .
通过Socket,我们才能使用TCP/IP协议。tcp、udp,简单的说(虽然不准确)是两个最基本的协议,
很多其它协议都是基于这两个协议如,http就是基于tcp的,.用socket可以创建tcp连接,也可以创建udp连接.
java C++ .net都有自己的socket编程实现.

Socket的大致位置如下: 为了便于编程而设想出来的API/编程模型




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 必须是同一层次进行通信
数据只能逐层传输,不能跃层
每一层可以使用下层提供的服务,并向上层提供服务.
Tomcat/Apache/NG之类的属于应用层, 传输层的请求由操作系统来处理. 由操作系统在传输层处理后交给应用层(Tomcat)处理.
Tomcat处理后将响应反馈给操作系统, 再通过路由等方式反馈给请求方. (如果是LVS集群,内部会修改请求方的地址. tomcat将请求反馈给lvs或者NG, 再由LVS或者NG反馈给外部的请求方)
Tomcat反馈数据的过程也必然是一个应用层-->传输层(操作系统)的过程.

3  长连接和短连接

3.1 HTTP, 长连接,短连接

HTTP是应用层协议.定义的是传输数据的内容的规范。
全称是Hypertext Transfer Protocol,即超文本传输协议。从名字上可以看出该协议用于规定客户端与服务端之间的传输规则,所传输的内容不局限于文本(HTML 文件, 图片文件, 查询结果等)。
HTTP对应在传输层使用的还是TCP协议进行通信.
  • HTTP基于请求响应模型。
客户端向服务器发送一个请求,请求头包含请求的方法、URI、协议版本、以及包含请求修饰符、客户端信息和内容的类似MIME的消息结果。服务器则返回一个状态行作为响应,内容包括消息协议的版本、成功或失败编码加上包含服务器信息、实体元信息以及可能的实体内容。
  • HTTP是无连接的
虽然HTTP是基于TCP的,但是HTTP本身是无连接的。客户端和服务器的链接是基于一种请求应答模式。及客户端和服务器建立一个链接,客户端提交一个请求,服务器端收到请求后返回一个响应,然后二者就断开链接。(默认最初HTTP协议就是这样使用的)
  • HTTP协议是无状态的协议
即每一次请求都是互相独立的。(无状态指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。这个识别用户的解决方案: 在浏览器一方就是使用tomcat给session在本地记录为cookie (Cookie与Session机制) 而对于APP来说就是APP在每次请求时都带上登录时服务器给与的token)

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版本简单分为三类:1.1之前,1.1,2.0 , 3.0
HTTP 1.1之前
  • 支持持久连接。一旦服务器对客户端发出响应就立即断开TCP连接 (即每次http请求都是短连接,每次请求需要先创建tcp连接(三次握手)后才能发送http请求,得到响应后通过四次握手关闭tcp连接
  • 无请求头跟响应头
  • 客户端的前后请求是同步的。下一个请求必须等上一个请求从服务端拿到响应后才能发出,有点类似多线程的同步机制。
HTTP  1.1(主流版本)
  • 与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
然后当服务器收到请求,作出回应的时候,它也添加一个头在响应中:
Connection: Keep-Alive
这样做,连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接。 这一直继续到客户端或服务器端认为会话已经结束,其中一方中断连接。

通过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 长连接和心跳那些事




4 Socket编程

我们知道网络传输的协议是TCP/UDP. 我们的请求响应交互其实对应就是client/server.
对于编程的语言的学习,我们通常需要掌握Socket编程.
Socket的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”.
socket是什么?
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); //出错,则打印出错信息

      }
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);

        //出错,打印出错信息

      }

    }

在这种编程模式中, 双方可以互为client/server 这样就实现了类似QQ这类IM的功能.
如果server方遇到的请求压力很大, 这部分的延伸就是[Java线程池的分析和使用]如https://ifeve.com/java-threadpool/ 
而 socket的相关工具框架 见 mina, netty.
---------------------引用开始----------------------------
在java 原生的Socket编程中, 实际上Socket编程就是所谓的网络编程也就是基于TCP/UDP网络层协议进行编程,就拿Java和TCP来说:
对于BIO,TCP编程就是ServerSocket/Socket
对于NIO,TCP编程就是ServerSocketChannel/SocketChannel
对于AIO,TCP编程就是AsynchronousServerSocketChannel/AsynchronousSocketChanne
---------------------引用结束----------------------------


HTTP编程和连接池

5.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通过连接池的方式进行连接持久化。
 其实“池”技术是一种通用的设计,其设计思想并不复杂:
  • 当有连接第一次使用的时候建立连接
  • 结束时对应连接不关闭,归还到池中
  • 下次同个目的的连接可从池中获取一个可用连接
  • 定期清理过期连接
相关httpclient的解读可见 http://www.cnblogs.com/kingszelda/p/8988505.html 

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();

5.2.4 关闭连接管理器

当一个HttpClient的实例不在使用,或者已经脱离它的作用范围,我们需要关掉它的连接管理器,来关闭掉所有的连接,释放掉这些连接占用的系统资源。 
 CloseableHttpClient httpClient = <...>
    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 处理请求过程


图6-1

外部请求传输层由操作系统来处理(如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


6.1.2 认识Connector

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)。

 图6-2

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(最大排队数)
accept队列的长度;当accept队列中连接的个数达到acceptCount时,队列满,进来的请求一律被拒绝。默认值是100。
acceptCount的大小要视情况而定。如果设得太小,请求量上来的时候Client会收到read timeout或connection reset by peer;如果设得太大,请求会积压在accept队列得不到及时的处理,同样会因为等待超时导致Client端返回read timeout。
  • maxConnections(最大连接数)
maxConnections表示有多少个socket连接到tomcat上(接收和处理的最大连接数)。对于BIO模式,一个线程只能处理一个链接,一般maxConnections取值与maxThreads相同(短连接请求完毕后会释放tomcat连接, 长连接在请求处理后不占工作线程,仍然占用tomcat连接一段时间.)
否则Client的socket即使连接上了,但是都在tomcat的task queue里头,等待worker线程处理返回响应;对于NIO模式,一个线程同时处理多个链接,maxConnections应该配置得比maxThreads要大的多,默认情况下是10000。
当Tomcat接收的连接数达到maxConnections时,Acceptor线程不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到Tomcat接收的连接数小于maxConnections。如果设置为-1,则连接数不受限制。
默认值与连接器使用的协议有关:NIO的默认值是10000,APR/native的默认值是8192,而BIO的默认值为maxThreads(如果配置了Executor,则默认值是Executor的maxThreads)。
在windows下,APR/native的maxConnections值会自动调整为设置值以下最大的1024的整数倍;如设置为2000,则最大值实际是1024。

  • maxThreads(处理线程的最大数量)
maxThreads指定Tomcat最大并发线程数量,即同一时刻Tomcat最多maxThreads个线程在处理客户端的请求. 默认值是200(Tomcat7和8都是的)。如果该Connector绑定了Executor,这个值会被忽略,因为该Connector将使用绑定的Executor,而不是内置的线程池来执行任务。
maxThreads规定的是最大的线程数目,并不是实际running的CPU数量;实际上,maxThreads的大小比CPU核心数量要大得多。这是因为,处理请求的线程真正用于计算的时间可能很少,大多数时间可能在阻塞,如等待数据库返回数据、等待硬盘读写数据等。因此,在某一时刻,只有少数的线程真正的在使用物理CPU,大多数线程都在等待;因此线程数远大于物理核心数才是合理的。
换句话说,Tomcat通过使用比CPU核心数量多得多的线程数,可以使CPU忙碌起来,大大提高CPU的利用率。
maxThreads的设置与应用的特点有关,也与服务器的CPU核心数量有关。对于计算密集型应用,maxThreads应该尽可能设得小一些,让线程占用更多的CPU时间去处理逻辑;而对于IO密集型应用,maxThreads可以设得稍大些,让应用可以处理更大的并发


延伸阅读
Tomcat调优总结(Tomcat自身优化、Linux内核优化、JVM优化)

其他

延伸阅读: 偏流程性质的说明,如下:
相关说明并未深入os,tomcat是如何处理请求,以及http长短连接的问题。
网站架构从0起步系列文章总目录

参考

1 建立最主要的整体概念 通过Tomcat的acceptCount与maxConnections 
2 简单的对acceptCount、maxConnections、maxThreads这几个概念的简单再说明 通过 tomcat的acceptCount与maxConnections 
3 详细的完整补充: 见详解tomcat的连接数与线程池  对Nio、Bio、APR, acceptCount、maxConnections、maxThreads, 线程池Executor都有论述.
https://segmentfault.com/q/1010000016846975