计算机网络通信与Socket

计算机组成与系统结构,数据结构与算法,计算机网络,操作系统,编译原理都是计算机基础课程。简单总结下计算机通讯网络的各种基础概念,与通讯过程。通讯双方使用的通讯协议

TCP/IP五层模型

TCP/IP五层模型

只要是在网络上跑的包,都是完整的。可以有下层没上层,绝对不可能有上层,没下层。至于为什么要分层,程序复杂了就要分层一个概念,降低开发成本,把各个成之间扮演的角色分离出来。

数据传输过程:

1
2
3
4
5
6
源主机                 路由器           交换机            目标主机
应用层 应用层
(TCP头)传输层 传输层
(IP头)网络层 网络层 网络层
(链路头)数据链路层 数据链路层 数据链路层 数据链路层
物理层 物理层 物理层 物理层

助记:传输层(Transmission) 网络层(Internet)

TCP/IP 4层模型

应用层
传输层
网络层
网络接口层(数据链路+物理层)

数据链路层:
在网络发展过程中先后出现三种链路层包结构:

  1. Ethernet
    目的MAC、源MAC、类型、数据、FCS
  2. 802.3 LLC
    目的MAC、源MAC、长度、目的服务加入点Snap、源服务加入点Snap、控制LLC、FCS(32bitCRC)
  3. 802.3 LLC Snap
    目的MAC、源MAC、长度、目的服务加入点Snap、源服务加入点Snap、控制LLC、Snap、FCS(32bitCRC)

socket

  • select、poll、epoll、iocp
  • MTU: Maxitum Transmission Unit 最大传输单元
  • MSS: Maxitum Segment Size 最大分段大小

MTU= MSS+TCP层头部长度+IP层头部长度 (MSS是指应用层在一个数据包内最大能传输的字节数,MTU是指IP层在一个数据包内最大能传输的字节数)
举个例子:如果你要搬家,需要把东西打包,用车运走。这样的情况下,车的大小受路的宽度限制;箱子的大小受车限制;能够搬运的东西的大小受箱子的限制。这时可以将路的宽度理解成第二层的MTU,车的大小理解成第三层的MTU,箱子的大小理解成第四层的MTU,搬运的东西理解成MSS

我们可以把MSS设置成1300,解决大部分环境问题,TCP建立连接握手的时候会协商各自支持的最大包大小

客户端IP 客户端Port 服务端IP 服务端Port
socketfd *.* * 192.168.33.228 80
socketfd1 192.168.33.226 13455 192.168.33.228 80

理论上一台电脑并且只有一个网卡也只配置了一个IP地址可以对同一个服务端建立65535(端口数量)个连接,一个服务端可以有IP*65535(2^32*65535)个客户端连接

服务端:Socket()→Bind()→Listen()→Accept→Receive()/Send()→Close()
客户端: Socket()→Connect()→Send()/Receive()→Close()

滑动窗口(Sliding Window):一发一确认太慢,所以弄出了滑动窗口,可以同时发送多个报文、进行多个报文的确认,确认后窗口向右移动。结构体struct tcp_sock中有很多成员数据跟滑动窗口协议相关,max_window记录来自对端通告的窗口的最大值,snd_wnd是发送窗口的大小

物理层

以太网V2格式数据帧 : 链路层

Destination Source Type DataAndPad FCS
6 6 2 46~1500 4

目的MAC:48bit ,可以广播、单播、组播
源MAC: 48bit
类型:2字节,识别上层协议
数据:被封装数据,46-1500字节
FCS: 32比特的CRC

IP: 网络层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding | <-- optional
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DATA ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Source Address 和 Destination Address 在IP层, Source Port 和 Destination Port 在传输层
当发送端的MTU大于到目的路径链路上的MTU时就会被分片

Flags取值:
Bit 0: 保留,必须是0
Bit 1: (DF) 0 = 可能分片, 1 = 不分片
Bit 2: (MF) 0 = 最后的分片, 1 = 还有分片

TCP或UDP : 传输层

TCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |C|E|U|A|P|R|S|F| |
| Offset| Res. |W|C|R|C|S|S|Y|I| Window |
| | |R|E|G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

数据偏移(Data Offset),4bits,单位为4字节,它指出TCP报文头长度

UDP

1
2
3
4
5
6
7
8
9
10
11
0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
| |
| DATA ... |
+-----------------------------------+

UDP有报文长度Length字段,TCP没有报文长度字段

应用层:HTTP

HTTP完整包结构

数据帧{IP包{TCP或UDP包{Data}}}
以太网的物理特性决定了数据帧的长度范围为(46+18)→(1500+18),其中的18是数据帧的头和尾,也就是说数据帧的内容最大为1500,即MTU(Maximum Transmission Unit)为1500
在网络层,因为IP包的首部要占用20字节,所以这的MTU为1500-20=1480
在传输层,对于TCP包的首部要占用20字节,所以这的MTU为1480-20=1460

MTU 和 MSS 关系

最大传输单元MTU(Maximum Transmission Unit,MTU)

(1)以太网和802.3对数据帧的长度都有一个限制,其最大值分别是1500和1492个字节。链路层的这个特性称作MTU。
如果IP层有一个数据要传,且数据的长度比链路层的 MTU还大,那么IP层就要进行分片(fragmentation)。
(2)把一份IP数据报进行分片以后,由到达目的端的IP层来进行重新组装,其目的是使分片和重新组装过程对运输层(TCP/UDP)是透明的。
(3)尽管IP分片过程看起来透明的,但有一点让人不想使用它:即使只丢失一片数据也要重新传整个数据报。why?

因为IP层本身没有超时重传机制,由更高层(比如TCP)来负责超时和重传。

1
2
3
Iface      MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
enp3s0 1500 7580450 0 2 0 6890759 0 0 0 BMRU
lo 65536 1584 0 0 0 1584 0 0 0 LRU

MSS(Maxitum Segment Size)最大分段大小的缩写,是TCP协议里面的一个概念

(1)MSS就是TCP数据包每次能够传输的最大数据分段。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,
这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。
通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。
(2)TCP无所谓分段,因为每个TCP数据报在组成前其大小就已经被MSS限制了,所以TCP数据报的长度是不可能大于MSS的,由它形成的IP包的长度也就不会大于MTU,因此不用IP分片

简而言之:

  1. IP分片产生的原因是网络层的MTU;TCP分段产生原因是MSS.
  2. IP分片由网络层完成,也在网络层进行重组;TCP分段是在传输层完成,并在传输层进行重组. //透明性
  3. 对于以太网,MSS为1460字节,而MUT往往会大于MSS.
    故采用TCP协议进行数据传输,是不会造成IP分片的。若数据过大,只会在传输层进行数据分段,到了IP层就不用分片。

示例

PC–1–TP_Link –2–宽带路由器–3–电信公司—-微信服务器

在节点3处由于有额外8个字节的PPPoE头部封装,从服务器下行的1500字节IP包将会变成1508字节,按理说电信公司会依据:
IP包里的DF = 0 ,分片传输,这样不会有问题。
IP包里的DF = 1,丢弃,并发送ICMP告诉服务器包太大了,但是有时ICMP无法到达服务器(禁止ICMP),微信服务器一直重传1500字节包含图片的IP包,然后被丢弃,就会造成流量黑洞

解决方案:

修改节点1处的MTU = 1492 或更小。两端都需要修改,保持对称。这样TCP协商MSS,可以协商成1452。
这样上下行数据途径节点3处,整个包将变为: PPPoE + IP Header + TCP Header + TCP segment (MSS) = 8 + 20 + 20 + 1452 = 1500,正好等于MTU 1500,无需分片就不会有问题。

手工测试发现MTU值

网络层1500字节=20字节IP头+8字节ICMP头+1472字节的载荷

linux命令:

1
2
3
4
5
6
7
8
ping -c 3 -s 1472 -M do 192.168.33.113
# PING 192.168.33.113 (192.168.33.113) 1472(1500) bytes of data.
# 1480 bytes from 192.168.33.113: icmp_seq=1 ttl=64 time=0.742 ms

ping -c 3 -s 1473 -M do 192.168.33.113
#PING 192.168.33.113 (192.168.33.113) 1473(1501) bytes of data.
#ping: local error: Message too long, mtu=1500

测试出MTU=1500

TCP-MSS 协商

程序会协商TCP-MSS,用最小的一方进行发送。
当时经过运营商网络,还是会被拆分成更小的包。

Server7 的MTU为9000字节,Server8 的MTU 2000 字节
Server7 MSS值为9000-40=8960字节
Server8 MSS值为2000-40=1960字节
Server7 和 Server8 会达成协议,两者中取Server8 的MSS值,即1960字节
最终达到了TCP的单包最大值2014字节,即TCP-MSS值
2014字节=14字节二层帧头+20字节IP头+20字节标准TCP头+12字节TCP可选项+1948的负载
此处的TCP-MSS仍然为1960字节,只是被拆分成为了12字节的TCP可选项+1948的负载

在运营商网络:一个完整的2014字节数据包就这样被一分为二:1514字节数据包+534字节数据包

解决方案:拦截TCP-MSS值
set security flow tcp-mss all-tcp mss 1460

PathMTU Discovery

主机发送的数据包就会出现如下结局:

  1.  如果主机本地链路的MTU大于端到端链路中某一点的MTU值,那么这个数据包因为有DF=1的原因,会被丢弃。
  2.  如果路由器本地链路的MTU为整个端到端链路中最小值时,数据包很幸运的被送达目的地。
    而我们需要重点说说结局1。在结局1中,当链路中路由器丢弃此数据包时,此路由器会返回一个ICMP 的Destination Unreachable,Fragment Needed(目标不可达,需要分片)的消息给源主机
    这个ICMP包中另有玄机,它同时也包含了此链路路由器的下一跳MTU值!

依赖ICMP协议

可能出现问题:
防火墙把ICMP干掉。
解决方案:大家可以考虑在边界防火墙上放行ICMP协议

夹心饼干,三层设备之间夹杂二层设备

数据包分片只存在于ISO第三层的网络设备上,而二层节点只查看二层帧(数据链路层 MAC地址),其并没有重写三层IP头的能力,更不知晓三层IP层的具体网络细节
当数据包过大以后,二层设备是不会也不能去分片数据包,因为在配置的时候,没有赋予它任何此二层VLAN的IP信息,导致其没有写IP数据包的能力去执行数据包分片

二层交换机:

(1) 当交换机从某个端口收到一个数据帧,它先读取包头中的源MAC地址,这样它就知道源MAC地址的机器是连在哪个端口上的;
(2) 再去读取数据帧头中的目的MAC地址,并在地址表中查找相应的端口;
(3) 如表中有与这目的MAC地址对应的端口,把数据帧直接复制到这端口上;
(4) 如表中找不到相应的端口则把数据帧广播到所有端口上,当目的机器对源机器回应时,交换机又可以学习一目的MAC地址与哪个端口对应,在下次传送数据时就不再需要对所有端口进行广播了。不断的循环这个过程,对于全网的MAC地址信息都可以学习到,二层交换机就是这样建立和维护它自己的地址表。

解决方案:

  1. 方案一:请务必保证路由器之间的二层设备MTU值与三层路由器设备MTU相同,所以可以改三层交换机配置
  2. 方案二:客户端或者服务端程序设置MSS值,比如1300
    setsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, (int *)&maxseg, sizeof(maxseg));

TIME_WAIT

TIME_WAIT 是这么一种状态:TCP 四次握手结束后,连接双方都不再交换消息,但主动关闭的一方保持这个连接在一段时间内不可用。原因:TIME_WAIT 是为了保证全双工的 TCP 连接正常终止,TIME_WAIT 的存在是为了保证网络中迷失的数据包正常过期

开发注意事项

对于使用异步返回的情况请求与返回如何对应上?指令与返回加上原样返回字段(流水号或者随机码)根据情况在内存自己维护
根据具体使用场景尽量简化与统一调用协议,比如把自定义tcp改用http+callback或者直接http post返回位置信息
服务端开发一般监听0.0.0.0或者:::,或者band监听服务器上所有ip,如果指定ip就只能通过那个ip访问。只监听回环地址(Loopback Address)127.0.0.1只能本机请求127.0.0.1
netstat只看到:::1521 ipv6的监听,是因为默认ipv6也能处理ipv4,如果禁用AP_ENABLE_V4_MAPPED就会出现两个监听

问题

UDP为什么没有粘包问题?
UDP有包头、包头里面有长度。他直接是一端发送什么数据,直接就发出去了 ,既然他不会对数据合并,每一个数据包都是完整的也就没有粘包一说了。UDP不存在粘包问题,是由于UDP发送的时候,没有经过Negal算法优化,不会将多个小包合并一次发送出去。另外,在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包,这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包。也就是说,发送端send了几次,接收端必须recv几次(无论recv时指定了多大的缓冲区)。
分包产生的原因就简单的多:可能是IP分片传输导致的,也可能是传输过程中丢失部分包导致出现的半包,还有可能就是一个包可能被分成了两次传输,在取数据的时候,先取到了一部分(还可能与接收的缓冲区大小有关系),总之就是一个数据包被分成了多次接收。

参考