前言:略
一、前因
一切从tcp、udp开始。
众所周知,网络模型一般有两种模型,一种为OSI概念模型(七层),另一种为TCP/IP网络模型(四层)。
TCP/IP应用层对应OSI的应用层、显示层、会话层;TCP、UDP对应传输层;互联网层对应网络层;设备驱动程序和硬件对应物理层。如下图所示:’
在linux上,四层以下被称为通讯子网,四层以上被称为应用层。前者更注重通信细节,主要实现主机与主机之间如何完成数据报文传输;后者更注重应用层细节,主要实现资源交换。
linux启动后,应用层细节在用户空间实现,通信细节则在内核空间实现。
所以,最原始最有需求的跨主机间的通信,皆由内核完成;相对而言真正的应用则是通信协议在用户空间特定的实现方式,比如dns完成域名解析,比如ssh实现安全登陆。自然,既是完成特定功能,便不应该位于内核空间。
当然,也有所疑惑,主机间的通信不是通过IP地址间进行通信的吗? 没错。但是Ip协议本身并不可靠,且是无连接的。相对而言,tcp协议确是有链接,传输层在一定程度上弥补了网络层的缺陷,当然这仅是其中一部分缘由。
二、正题
回归正题,所谓传输层(tcp、udp)是提供进程地址空间,用端口号(port number)来标识进程地址空间。
其中tcp是一种传输控制协议,即面向对象协议;通信前需建立虚拟链路,需事先知道对方是否在线,通信结束后还需断开链路(tcp的三次握手)。
而udp(User Datagram Protocol)是无连接协议;通信前无需建立虚拟链路,无需确定对方是否在线。
二者为完全不同的协议,且各有0-65535个端口【自然需要排除一些众所周知的端口,80/tcp(http),22/tcp(ssh)等端口】。
前文所述,所有的应用层协议都位于应用层,倘若需要跨主机间进行通信,必须向内核申请,然而。。。然而,内核标记各主机之间通过网络进行通信时,却是通过套接字方式进行的。
那么问题又来了,套接字又是什么?
套接字(Socket):IPC的一种实现方式;主要目的是允许不同主机(也可以是同意主机)上不同进程之间进行通信从而完成数据交换。诚然,套接字既由内核实现也就属于内核,属于system call。事实上,Socket一般称之为Socket API,主要是它在system call中提供了太底层太基础,因此,在C标准库中才被称作Socket API。
诚然,跨主机间的套接字为:Ip+端口【ip用来标记主机,端口用来标识进程空间】
所以具体实现方式是:当用户发起跨主机间通信时,会基于套接字的方式调用套接字,从而内核空间申请tcp、udp端口,从而建立虚拟链路(如果是tcp的话)进行通信。
题外话:除了tcp、udp之外还有另一种方式,使用裸套接字的方式,直接封装ip报文。
Socket的三种类型:
SOCK_STREAM:tcp套接字
SOCK_DGRAM:udp套接字
SOCK_RAM:裸套接字
综上所述,无论什么网络服务,都必定需要使用网络通信的基本功能。即tcp、udp、或者其他。
三、深入
向前一步!
主机间通过套接字进行通信,套接字为ip+端口,那么问题又来了,如果服务器与客户端进行通信,所使用的套接字又该如何解决?
端口号(port number)是用来标记进程空间地址(tcp和udp各一种),显然端口号是唯一且独占的。倘若服务器端和客户端皆使用同一端口【80/tcp(http)】,那事情就大条了,服务器端口被独占又如何能够回应多个客户端,显然这是不可能的。所以,只能在妥协中前行,客户端使用随机端口,而服务器端使用固定端口。
这之中又牵扯出了两大疑问:
(1)为何是客户端使用随机端口,而不是服务器端?
服务器端就好比是学院,而客户端好比学院的家。学院的地址必定是固定的,否则又如何上学;相对的,学院的家并无需固定地址。当发起跨主机间通信时,会向内核申请一个随机端口1访问远端服务器
(2)服务器端是如何回应多个客户端?
如图:本地发送缓冲对应远端接受缓冲;本地接受缓冲对应远端发送缓冲。在信道通信上是采取不同通信信道(接收和发送不是一个信道),而其中还包含缓冲,抑制高发低收压垮通信信道。显然,服务器端一旦接受连接后,响应客户端也是随机端口。
另外:事实上,所说套接字是ip+端口并不是完全准确,根据其所使用的地址可划分为多个域。
AF_INET:IPv4
AF_INET::IPv6
AF_UNIX:同一主机不同进程中的通信时使用
且每种类型至少都提供了两种套接字(Socket),一种是无连接,有边界的,不可靠传递的数据报(UDP);另一种是有连接,有边界,可靠传递的流(TCP)。顺便一提,AF_UNIX不同于另外两种,它无需经过数据链路层,仅仅在传输层(第三层)中便可完成通信。
随机端口:
IANA将端口范围进行了明确划分:
0-1023:永久划分给应用程序使用的端口,特权端口(同样也是管理员才能使用的端口范围),比如:22/tcp(ssh)、80/tcp(http)
1024-41952:注册端口,但是并不严格,分配给某些注册的应用程序使用,比如:3306/tcp(mysql)
41952+:客户端动态分配的端口,动态端口,或私有端口
所以,服务器响应客户端的随机端口号事实上是41952号端口以上的动态端口。但是。。。但是,服务器在某些高并发的情况下动态端口的数量是不足的,需手动调整端口范围让其包含41952以下的端口,在linux上修改:/proc/sys/net/ipv4/ip_local_port_range
不过,调整的端口范围不能包含1024以下的端口,毕竟0-1023只有管理员才能够使用。