目录
一、传输层之TCP与UDP协议
-
TCP与UDP协议都是用来规定通信方式的。比如我们在聊天的时候可以随心所欲的聊,有些时候又需要遵循一些规律进行聊天。
-
当我们跟关系很好的朋友聊天的时候,百无禁忌,上到国家,下到生活中的琐事都可以聊。这就相当于没有设立规定时的通信。
-
当我们在上课的时候,只能跟老师和同学沟通学习相关的问题。在写作文的时候需要开头空两格,需要分段。写信时需要用上书信格式。这些都相当于设立了规定后的通信。
ps:不遵循上述协议也可以通信 只不过遵循了更合规合法合理!!!
1.TCP协议(重要)
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。
当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。
这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。
三次握手建链接
1.三次握手建立连接的过程
首先是客户端这边向服务端发送建立连接的请求(SYN seq = x)
其次是服务端接收请求并回应,回应的同时向客户端发送建立连接的请求(这里可以看成同时进行了两步,如果服务端同意建立连接,这时候就会建立起客户端发送数据到服务端的数据传输通道,之后的服务端向客户端发送建立连接的请求是为了建立服务端返回数据到客户端的数据传输通道。SYN seq = y,ACK = x + 1)
最后是客户端向服务端进行回应(ACK = y + 1)这时就建立起了两者的双向数据传输通道
2.TCP协议也称为可靠协议(数据不容易丢失)
造成数据不容易丢失的原因不是因为有双向通道 而是因为有反馈机制
给对方发消息之后会保留一个副本 直到对方回应消息收到了才会删除
否则会在一定的时间内反复发送。
3.洪水攻击
同一时间有大量的客户端请求建立链接就会导致服务端一直处于SYN_RCVD状态
4.服务端如何区分客户端建立链接的请求
当我们在建立连接的时候,客户端会发送上图中的请求seq,这个seq带有一串数字,类似识别号码,服务端端受到请求进行反馈的时候返回的信息就是上图中的ACK,ACK会把seq中的识别号码+1然后返回回来,因为不同的客户端发送seq的时候有不同的识别号码,服务端靠这种方式来区分请求(对请求做唯一标识)。
四次挥手断连接
1.四次挥手断开连接的过程
第一步:当客户端没有信息需要发送给服务端的时候,客户端会发送断开连接的请求(SYN seq = x+2)
第二步:这时服务端会回应客户端,确认断开客户端传输信息给服务端的数据传输通道(ACK=x+3)
TIME_WAIT状态:当断开客户端传输数据到服务端的数据传输通道后,服务端需要先确认是否还有消息需要发送给客户端,发送完毕、确认无误后才会继续后续操作断开连接
第三步:服务端向客户端发送断开通道连接的请求(seq = y + 1)
第四步:客户端向服务端发送回应然后断开服务端跟客户端进行数据传输的通道(ACK = y + 2)
2.四次不能合并为三次
因为中间需要确认消息是否发完(TIME_WAIT)
三次握手和四次挥手也可以看成是小情侣谈恋爱的过程:
三次握手:表白在一起
四次挥手:决裂要分手
2.UDP协议
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
早期的QQ使用的是纯生的(没有加任何额外功能)UDP协议,导致很容易出现数据丢失,接收不到。现在的QQ自己添加了很多技术和功能。
使用UDP的原因就是因为很简单:快捷、粗暴,只要指定对方的地址就可以发消息了
3.tcp和udp的对比
-
上面的TCP协议相当于打电话一样,双方有来有回
-
UDP协议就相当于发短信,信息发出去了,但是不一定有回应,因此也称之为数据报协议、不可靠协议
TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快
二、应用层简介
应用层相当于是程序员自己写的应用程序 里面的协议非常的多
常见的有:HTTP、HTTPS、FTP
ps:后续框架部分再做介绍
三、socket模块
1、简介
如果我们需要编写基于网络进行数据交互的程序 意味着我们需要自己通过代码来控制我们之前所学习的OSI七层(很繁琐 很复杂 类似于我们自己编写操作系统)
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
socket模块类似于操作系统 封装了丑陋复杂的接口提供简单快捷的接口
ps:socket也叫套接字
2、基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
3、基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
四、socket代码简介
服务端代码
import socket
# 1.产生一个socket对象并指定采用的通信版本和协议(TCP)
server = socket.socket() # 括号内不写参数 默认就是TCP协议 family=AF_INET基于网络的套接字 type=SOCK_STREAM流式协议即TCP
# 2.绑定一个固定的地址(服务端必备的条件)
server.bind(('127.0.0.1', 8080)) # 127.0.0.1为本地回环地址 只有自己的电脑可以访问,8080为端口号
# 3.设立半连接池(后面会讲)
server.listen(5)
# 4.等待接客
sock, addr = server.accept() # return sock, addr 三次握手
print(sock, addr) # sock就是双向通道 addr就是客户端地址
# 5.服务客人
data = sock.recv(1024) # 接收客户端发送过来的消息 1024字节(基于网络传输数据得是二进制)
print(data.decode('utf8')) # 因为二进制我们看不懂,所以要解码
sock.send('尊敬的客人 您说什么就是什么 一切按照您的要求来'.encode('utf8')) # 给客户端发送消息 注意消息必须是bytes类型
# 6.关闭双向通道
sock.close() # 四次挥手
# 7.关闭服务端
server.close() # 店倒闭了
客户端代码
import socket
# 1.生成socket对象指定类型和协议
client = socket.socket()
# 2.通过服务端的地址链接服务端
client.connect(('127.0.0.1', 8080))
# 3.直接给服务端发送消息
client.send('大爷有钱 把你们店最好的给我叫出来'.encode('utf8'))
# 4.接收服务端发送过来的消息
data = client.recv(1024)
print(data.decode('utf8'))
# 5.断开与服务端的链接
client.close()
五、socket代码优化
1.聊天内容自定义
针对消息采用input获取
2.让聊天循环起来
将聊天的部分用循环包起来(三次握手的代码开始包起来)
3.用户输入的消息不能为空
本质其实是两边不能都是recv或者send 一定是一方收一方发
4.服务端多次重启可能会报错
Address already in use
主要是mac电脑会报
方式1:改端口号
方式2:
#加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)
5.当客户端异常断开的情况下 如何让服务端继续服务其他客人?
windows服务端会直接报错
mac服务端会有一段时间反复接收空消息延迟报错
处理方式:使用异常处理、空消息判断,对上面的情况进行判断并给出解决方案
服务端代码:
import socket
from socket import SOL_SOCKET, SO_REUSEADDR
server = socket.socket()
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind(('127.0.0.1', 8081))
server.listen(5)
while True: # 链接循环
sock, addr = server.accept()
while True: # 通信循环
try:
# 当客户端输入为空的时候通过循环跳过后续代码,防止程序报错
data = sock.recv(1024)
if len(data) == 0:
break
print(f'来自于客户端{addr}的消息>>>:', data.decode('utf8'))
msg = input('请输入发送给客户端的消息(不能发空消息)>>>:').strip()
# 返回的信息设置成自定义
sock.send(msg.encode('utf8'))
except BaseException:
# 当客户端直接退出的时候服务端这边会报错,我们用异常处理让程序跳过这些会报错的代码,继续运行
break
客户端代码:
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8081))
while True:
msg = input('请输入您想要发送给服务端的消息>>>:').strip()
# 把发送的信息设置成自定义
if len(msg) == 0:
print('不能发送空消息')
continue
client.send(msg.encode('utf8'))
data = client.recv(1024)
print('来自于服务端发送过来的消息>>>:', data.decode('utf8'))
六、半连接池的概念
server.listen(5) # 半连接池
当有多个客户端来链接的情况下 我们可以设置等待数量(不考虑并发问题)
假设服务端只有一个人的情况下
比如上面我们半连接池设置成五个,在优化后的代码中,由于用while循环包裹了代码,当我们在运行代码的时候可以这样理解,第一个客户端就会直接跟服务端进行交互,除此之外还有五个客户端可以排队,但是发出去的信息服务端那边暂时不会处理,要等到第一个客户端处理结束断开连接才会根据先后顺序依次往后处理
在测试半连接池的时候 可以不用input获取消息 直接把消息写死即可