一、TCP连接与端口
web服务器只开启了一个端口,他是如何为多用户服务的?
问题描述
不同主机之间通讯,必须依赖套接字,而端口号是套接字的标识(开始是这样认为的),那么假设web服务器进程,开启了80端口号(即监听80端口号),接着客户端浏览器,打开任意端口,发起TCP连接请求,服务器80端口监听到请求,建立TCP连接,最后通过客户端套接字和服务器套接字进行通信,那么其他用户怎么办? 80端口也被占用,改如何建立TCP连接?现实中大家发送http请求好像都可以使用一样的端口,如80。
问题解决
首先,明确几点:
1、TCP套接字的唯一标识是一个四元组(源IP地址,源端口号,目的IP地址,目的端口号)
2、TCP创建连接,会进行“三次握手”,
我们这里暂且把TCP建立连接的报文段称为:称为TCP连接
把TCP承载请求数据的报文段称为:TCP请求
客户端浏览器和web服务器的通信过程是这样的:
在建立连接阶段:
浏览器进程打开任意端口,所有的浏览器都是将TCP连接报文,发送给服务器进程监听的端口(如:80),服务器接受到请求,为每个请求创建新的套接字(依据请求报文的源ip,源端口号和自己的ip、端口号)
在发送http请求阶段:
此时连接已经建立,承载了 Http请求信息的TCP请求报文会发送到对应的套接字
因此: 多个不同的套接字可以拥有相同的目的端口号80,由于源ip或端口号不同,TCP套接字就可以唯一标识,通信就可以进行。
同时,一个端口号只能被一个进程所监听
举一反三:其实所有使用TCP的应用程序对的通信都是一样的,分为建立连接和发送数据阶段。
在建立连接时,对于 客户端TCP套接字还没有形成,服务器端进程也是如此,它只是监听80端口而已,把所有目的端口号为80的TCP报文段统统接收,然后去处理,即建立套接字,为套接字分配处理进程。当连接建立时,对应着服务器、客户端套接字的形成,之后的通信,不会将TCP报文段在发送给监听80端口的服务器进程,而是发给与连接对应的套接字,它的目的端口号也是80,但他只是套接字的一部分。
对于服务器进程也是不一样的:一个专门用于建立连接,它监听端口,创建用于连接的套接字
一个专门用于处理请求,通过新建立的套接字发送和接收请求
最好再次总结
1.一个端口同一时间只能bind给一个SOCKET。就是同一时间一个端口只可能有一个监听线程
2.为什么一个端口能建立多个TCP连接,同一个端口也就是说 server ip和server port 是不变的。那么只要[client ip 和 client port]不相同就可以了。能保证接唯一标识[server ip, server port, client ip, client port]的唯一性。
- 端口号不是套接字的唯一标识。另外,UDP套接字是(目的ip地址,目的端口号),
二、TCP端口(客户端-服务端)
关于TCP服务器最大并发连接数有一种误解就是“因为端口号上限为65535,所以TCP服务器理论上的可承载的最大并发连接数也是65535”。
先说结论:对于TCP服务端进程来说,他可以同时连接的客户端数量并不受限于可用端口号。并发连接数受限于linux可打开文件数,这个数是可以配置的,可以非常大,所以实际上受限于系统性能。
从理论上说,端口号的作用是在网络连接中标识应用层的进程,服务端一般使用众所周知的端口号进行监听,客户端连接时系统自动分配端口号。一个服务端进程服务于n个客户远程进程,只需要能通过ip地址+端口号的组合把他们区分开即可,没有必要占用本机的其他端口号,客户端连接数增加并不会占用服务器端口号,因此端口号并不能限制并发连接数。当然一台机器上端口号数量的上限确实是65536个,因为tcp首部中使用16bit去存储端口号。所以如果说65536影响了连接数,只有一种可能,就是同一台客户端机子上开n个进程去连同一个服务端进程,因为客户端ip是同一个,为了区分出这些连接,只能使用客户端连接的端口号,那么服务端和一个客户端主机之间的tcp连接数理论上线确实是65536。但是,服务端可以连接n多客户端机子呢。
实际上,确实有个限制端口号的配置,就是MaxUserPort,这实际上是一台主机向外连接使用端口数的限制,这个数也可以配置的,可能默认值才5000,实际上对于正常的服务器主机是够用的,因为你是等别人连接进来的,不是要去连接很多不同的其他主机的。当然你的服务器上可能跑了一些转发的服务,这样你就需要对外连接了,如果被限制在这个配置这儿了确实需要改。但是这个MaxUserPort确实和服务器可以承载的来自客户端的并发连接数没有关系。
伴随这个误解的还有另外一个误解,就是accept之后产生的已连接套接字占用了新的端口。这个绝对是错误的,linux内核不会这么写的,因为完全没必要嘛。客户端连接上来之后产生的这个socket fd就是用来区分客户端的,里面会填上客户端的ip和端口作为发包用,来自客户端的包也会使用这个fd去读取。可以试试netstat -ano,然后起一个服务器看下,客户端连上来这后产生的套接字的服务端端口还是监听的端口。
三、HTTP连接
浏览器发送的请求是复用同一个连接吗?
不一定。
发的不同域名,肯定不复用。
发的同域名。若第一个请求与第二个请求并行发送,不复用。
发的同域名,并且是第一个请求完事了才发第二个请求。则看是否有 connection: keep-alive 请求头,没有则不复用。
发的同域名,第一个请求完了后发第二个请求,有 connection: keep-alive 请求头。则复用同一个 TCP 连接。
补充说明
HTTP 1.1 里大概规范了几项提高性能的手段:
持久连接 (keep-alive/persistent connection)
并行连接
Pipelining
持久连接
每一个请求都会重新建立一个 TCP 连接,一旦响应返回,就关闭连接。 而建立一个连接,则需要进行三次握手。HTTP 1.1 出了一个请求头 connection,默认 keep-alive,告诉服务器不关闭 TCP 连接。
并行连接
由于现代网页通常包含了复数个(>=10)资源,而按照默认设定,一个连接中的每一个请求必须等待收到响应后才能发送下一个请求,所以如果复数的资源请求全部在一个连接 one by one 发送给服务器显然会很慢,而为了弥补这一缺陷,浏览器通常会默认开启多个 TCP 连接,然后再根据每个连接的状态在其中依次发送数据请求,而且客户端有权任意关闭超发的连接。各个浏览器允许的并行连接数大致是这样的(From SO):
Firefox 2: 2
Firefox 3+: 6
Opera 9.26: 4
Opera 12: 6
Safari 3: 4
Safari 5: 6
IE 7: 2
IE 8: 6
IE 10: 8
Chrome: 6
由于 TCP 协议本身有慢启动的特征,会随着时间调谐连接的最大速度,因此在现代浏览器中持久连接和并行连接通常是搭配在一起使用的—— 一方面由于持久连接的存在,每个 TCP 连接已经处于调谐后的状态,另一方面持久连接可以避免重新三次握手的开销。
在 Chrome 中,页面初始并行加载一堆静态资源是会最大开 6 个 TCP 连接去并行运作,其后发 Ajax 请求则是复用之前的 TCP 连接。
Pipelining
按照 HTTP 1.1 的描述,还有种可以提升性能的方案是管道化,可以在一个 TCP 连接中并行执行多个请求并返回。
因为这项技术比较复杂,如何能在一个 TCP 中有序的处理所接收到的包,并且不会乱序返回,这在早期没有规范,所以各大浏览器都没有支持此功能,形同鸡肋。
关于 HTTP 2
HTTP 2 为了性能做了不少努力,比如提供了规范以支持连接的多路复用。
如前文所说,在同一个 TCP 连接里面同时发生两个请求响应就不是那么简单。而 HTTP 2 正是提供了这样的规范,可以给数据拆成包,并打上包的顺序标签以供 TCP 能正确认知接收的包的顺序。
所以很多网络优化的知识已经过时
因为“所有的 HTTP 2.0 的请求都在一个 TCP 链接上”,“资源合并减少请求”,比如 CSS Sprites ,多个 JS 文件、CSS 文件合并等手段没有效果,或者说没有必要。
因为“多路复用”,采用“cdn1.cn,cdn2.cn,cdn3.cn,打开多个 TCP 会话,突破浏览器对同一域名的链接数的限制”的手段是没有必要的。因为因为资源都是并行交错发送,且没有限制,不需要额外的多域名并行下载。
因为“服务器推送”,内嵌资源(如base64的图片)的优化手段也变得没有意义了。而且使用服务器推送的资源的方式更加高效,因为客户端还可以缓存起来,甚至可以由不同的页面共享(依旧遵循同源策略)
参考
https://blog.csdn.net/gao_zhennan/article/details/97494480
https://www.jianshu.com/p/4a58761d758f
https://www.cnblogs.com/everlose/p/12779773.html