网络通信
作用: 通过网络(介质),进行信息的交换(数据的接收,发送)
网络:网络是远距离交流的介质, 就像说话时,空气就是传递声音的介质
目的:实现 不同电脑的,进程之间的通信
协议
什么是协议:大家都遵循的交流语言
TCP/IP协议
是一个协议族: Internet Protocol Suite ,通用协议的标准
分类
应用层: -->应用层
-->表示层
-->会话层
传输层: -->传输层 TCP
网络层: -->网络层 IP
链路层: -->数据链路层
-->物理层
端口(port)
端口: 标记唯一进程的一种方法
IP标记是哪台电脑,port标记是哪一个程序
端口号: 对端口进行的的标记
范围: 2**16 --> 65536个: 0-->65535
知名端口(well konwn ports): 众所周知的端口 2**10 --> 1024, 即0 到 1023
80: http
21: FTP
动态端口(dynamic ports): 随机动态分配,程序需要网路通信时,向主机申请.
程序关闭时,释放所占用的端口
查看端口(linux) : netstat -an
端口不是一一对应的:
比如你的电脑作为客户机访问一台WWW服务器时,WWW服务器使用“80”端口与你的电脑通信,
但你的电脑则可能使用“3457”这样的端口。
主机区分网络服务的方式:
IP + 端口号
IP(IP version4)
IP version4/IP version6
ip地址:用来在网络中标记唯一一台终端设备,的一串数字.(同一网络中,具有唯一性)
网络地址:指定所在网络
主机地址:指明主机号
iPv4数量: 256 : 256 : 256 : 256
表示法: 点分十进制
2**32 --> 2**30*4 -->1G*4 --> 4G (总共个数)
xxx.xxx.xxx.255: 广播地址
分类:
A类: 1字节的网络地址和3字节主机地址组成
A类网络有126个,每个网络能容纳1677214个主机
B类: 2个字节的网络地址和2个字节的主机地址组成
B类网络有16384个,每个网络能容纳65534主机
C类: 3字节的网络地址和1字节的主机地址组成
C类网络可达2097152个,每个网络能容纳254个主机
D类: 第一个字节以“1110”开始,它是一个专门保留的地址
用于多点广播(多播Multicast):
多播:指定一部分人可以收到,另一部分人收不到
广播:所有人都能收到
私有ip: 局域网内部使用 ,不能再公网中使用
回路测试: 127.0.0.1~127.255.255.255
socket
套接字:用于实现进程的的通信 Socket = Ip address + TCP/UDP + Port
相当于与创建进程间的一个通道,(就像是两个人之间都沟通桥梁)
socket.socket(AddressFamily, Type)
Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET
Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)
udp_s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_s = socket.socket(ip协议,传输协议)
套接字类型:
流 套接字(SOCK_STREAM):
提供 可靠数据传输 服务,使用TCP(Transmission Control Protocol)
因为tcp的数据传输就像 流水 一样,流过来
数据报 套接字(SOCK_DGRAM):
提供 无连接服务 数据传输,不可靠,使用UDP(User Datagram Protocol)
原始 套接字(SOCK_RAW):
可以读写 内核没有处理的 IP数据包
STREAM: 只能处理 TCP协议 的数据(慢,不丢数据)
DGRAM: 只能处理 UDP协议 的数据(快,可能丢数据)
RAW: 可访问 其它协议发送的数据
tcp: 面向连接(如打电话先连接) 可靠的服务 面向字节流(数据看成一连串无结构的字节流) 全双工,可靠信道
tcp可靠的精髓: 发送方对数据 进行编号
udp: 无连接,即发送数据之前不需要建立连接(像是写信) 我尽力但不保证 面向报文 不可靠信道
AF_INET / PF_INET: Adress Family / Protocol Family IPv4协议
AF_INET6 / PF_INET6: IPv6协议
UDP
sendto(数据,("ip地址",端口))
udp_s.sendto("Hello World",("192.168.1.55",8080))
bind((ip,port))
给进程绑定一个固定的IP及port
随机端口:
当一个进程在发送数据时还没端口的话,会随机分配给它一个端口
recv_data = ecvfrom(buffersize) --> (data,sender's address info)
recv_data 接收的是一个元组(接收的数据,(发送的IP,port))
recvfrom(bufsize[, flags]) Receive data from the socket.
recvfrom(1)就是从缓冲区读一个字节的数据
接收/发送流程(udp)
发送流程:
1.创建套接字 (创建一个通道)
选择协议, (选择一个处理方法)
2.发送数据
3.关闭套接字 (关闭套接字)
接收流程:
1.创建套接字(创建一个通道)
选择协议, (选择一个处理方法)
2.绑定本地的信息(IP和port)
3.接收数据
4.关闭端口
TFTP
核心思想:
client(客户端)向server(服务器)请求某个数据时,
server该如何把数据发送过去呢(如何发送): 一点一点给!
每次给你一点,如何判断你已经收到(如何保证数据稳定性): 收到请回复!
TFTP核心思想:
1.编号,确认!
2.超时重传!
TFTP: Trivial File Transfer protocol 简单文件传输协议
作用: 提供不复杂,开销不大的 简单文件传输服务 即:小文件的传输
端口号: 69 0b1000101
过程:
1.client(客户端)向server(服务器)请求某个数据
eg: 下载请求 1 tet.doc 0 octet 0
读写请求: Opcode | Filename | 0 | Mode | 0
操作码 | 文件名 | 0 | 模式 | 0
2.server在自身查找,有则发送回去一个 定长516字节的数据包,没有返回一个Error包(5) (缺点: 不能列出目录, 不进行认证)
516 = 2字节的操作码(3) + 2字节的包序号 + 512字节的数据
数据包: Opcode | Block # | Data
操作码 | 块编号 | 数据
当没有,返回Error:
ERROR : Opcode | ErrorCode | Errmg | 0
操作码 | 差错码 | 差错信息| 0
3.客户端接收到: 返回一个ACK数据包,告诉服务器已经收到,回复确认(4)
ACK : Opcode | Block #
操作码 | 块编号
4.反复2,3
5.确定发送完毕:
1.当接收到的数据小于516字节时,意味着发送完毕
2字节操作码+2个字节的序号+512字节数据(发送的数据小于512字节)
2.单最后一个数据包恰好等于516字节时,再发一个空数据包
只有:2字节操作码+2个字节的序号,没有数据
传输模式(Mode): (不区分大小写)
netascii : 传输8位ASCII码形式
octet : 传输8位 源数据类型
mail : 直接将数据返回给用户,而不是保存为文件(已经不再支持)
opcode operation:
1.Read request (RRQ) Opcode | Filename | 0 | Mode | 0 下载
2.Write request (WRQ) 上传
3.Data(DATA) Opcode | Block # | Data
4.Acknowledgment(ACK) Opcode | Block #
5.Error(ERROR) Opcode | ErrorCode | Errmg | 0
大端和小端
查看网卡
windows:
ipconfig:
linux:
ifconfig
ifconfig 网卡 up
ifconfig 网卡 down
双工/单工
单工: 只能往单一方向传输
半双工: 同一时间段内,只能往一个方向
全双工: 同一时间段内,能同时收 / 发
TCP
像是打电话: 如果发送的数据没有收到,会在发一遍 超时重传
如果数据检验是出错, 也会要求 再发一遍 错误校验
应答机制
- 超时重传
- 错误校验
流量控制和阻塞管理
阻塞管理: 内存中有缓存机制,缓存接收到的消息
tcp服务器
- socket创建一个套接字 —>买个手机
- bind绑定ip和port —->插上手机卡
- listen使套接字变为可以被动链接 —>设计手机为正常接听状态(即能够响铃)
- accept等待客户端的链接 —>静静的等着别人拨打
- recv/send接收发送数据
listen
listen(self, backlog=None ): Enable a server to accept connections.
参数backlog 这个参数涉及到一些网络的细节。进程处理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。 毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。
accept
accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,
如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;
如果失败就返回 INVALID_SOCKET。
三次握手
- client –> server 今晚有空吗?(SYN) –>client. connect()
- SYN ( synchronous ) 建立连接
- Seq ( Sequence number) 顺序号码
- server –> client 有空 ; 你想约我吗?(ACK ; SYN)
- ACK ( acknowledgement ) 确认
- Ack ( Acknowledgement number) 确认号码 = 顺序号码 +1
- SYN ( synchronous ) 建立连接
- Seq ( Sequence number) 顺序号码
- client –> server 是的(ACK)
- ACK ( acknowledgement ) 确认
- Ack ( Acknowledgement number) 确认号码 = 顺序号码 +1
四次挥手
客户端关闭 发送 , 告知服务器 close( client.send() )
服务器回复; 并关闭 接收 close( server.recv() )
服务器关闭发送, 告知客户端 close( server.send() )
- 为什么不合并, 因为 客户端 只是有可能 关闭send, 如果是合并发送,当出现不关闭send时, 会导致服务器回复无法发出
- 如果分开,就可以什么时候调用close.send() , 我就什么时候发送告知消息
- 等待时间(超时重传):
- 目的: 为了防止 客户端没有接收到消息, 我就提前关闭了(2msl) 这种情况
- 直到接收到回复才关闭, 没接收到就反复发送
- 在调用close(server.send())时, 会等待一段时间(2 msl)才关闭
- 当接收到 客户端的回复后, 立即关闭server.send()
- 当超出等待时间, 再次发送告知消息
客户端回复; 并关闭 接收 close( client.recv() )
回复后, 也会等待一段时间才会关闭(时间: 服务器等待时间的两倍: 2-5mins)
为了防止一种情况: 我(client)收到了客户端的消息, 但我的回复 客户端没有收到!
当客户端没有收到时(), 会触发对方的超时回复机制, 重新会再给client发一个消息
- 没收到的原因: 网速太慢了, 当到达时,server已经重发了
如果我不等待, 客户端补发的消息就没有接收,很尴尬!
总结:
- 谁先主动断 发送, 谁等待 2msl(保留资源2msl), 最后断 接收(等待 2-5 分钟)
- 因为接收需要固定端口, 所以这个端口将会被占用
四次挥手搞笑版
- 我不和你玩了 –> close( client.send() )
- 行,我知道了 –> 确认我收到了
- 那么也不和你玩了 –> new_socket.close()
- 当我调用了close, 我才会发送 (等你回复我了就立马关)
- 不和我玩拉倒 –>
四次挥手 具体过程版:
- 我client要关闭发送信道
- 我serve知道了( 同时我关闭了接收信道)
- 我serve要关闭发送信道了
- 我client知道了( 我client也要关闭接收信道了); —-> serve收到后关闭发送信道
精简版:
client 关闭发送, serve确认
serve关闭发送, client确认
为什么关闭了仍然可以交流?
- 关闭的是应用层, 传输层仍然可以交流
MSL
- MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”
- 他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
- 因为tcp报文(segment)是ip数据报(datagram)的数据部分,具体称谓请参见《数据在网络各层中的称呼》一文,而ip头中有一个TTL域,
- TTL是time to live的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个ip数据报可以经过的最大路由数,
- 每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。
- RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。
- 2MSL即两倍的MSL,
- TCP的TIME_WAIT状态也称为2MSL等待状态,
- 当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,
- 等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。
- 在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。
- 当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。
- 不过在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。
- TTL与MSL是有关系的但不是简单的相等的关系,MSL要大于等于TTL。
每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。
它是任何报文段被丢弃前在网络内的最长时间。
我们知道这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
RFC 793 [Postel 1981c] 指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟,或2分钟。
在实际应用中,对IP数据报TTL的限制是基于跳数,而不是定时器。
对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIMEWAIT状态停留的时间为2倍的MSL。
这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。
这个连接只能在2MSL结束后才能再被使用。
遗憾的是,大多数TCP实现(如伯克利版)强加了更为严格的限制。
在2MSL等待期间,插口中使用的本地端口在默认情况下不能再被使用。
某些实现和API提供了一种避开这个限制的方法。
使用插口API时,可说明其中的SOREUSEADDR选项,它将让调用者对处于2MSL等待的本地端口进行赋值。
但我们将看到TCP原则上仍将避免使用仍处于2MSL连接中的端口。
在连接处于2MSL等待时,任何迟到的报文段将被丢弃。
因为处于2MSL等待的、由该插口对(socket pair)定义的连接在这段时间内不能被再用,因此当要建立一个有效的连接时,来 自该连接的一个较早替身(incarnation)的迟到报文段作为新连接的一部分不可能不被曲解(一个连接由一个插口对来定义。一个连接的新的实例(instance)称为该连接的替身)。