Tcp/Udp协议、socket、粘包

五、TCP/UDP协议

三次握手

SYN 创建连接
ACK 确认响应
FIN 断开连接

三次握手
	客户端发送一个请求,与服务端建立连接
	服务端接受这个请求,并且响应与客户端建立连接的请求
	(服务端的响应和请求是在一次发送当中完成的)
	客户端接受服务端的请求之后,把消息在响应给服务端
	
	接下来客户端和服务端可以发送数据了.
	每发送一个数据出去,对应的主机都会有一个回执消息,确认数据的接受情况,
	如果没有得到回执消息,该数据会重发一次,保证数据的完整.
	不会一直不停的发下去,有时间最大允许周期.
	
TCP(Transmission Control Protocol)一种面向链接的、可靠的、传输层通信协议(比如:打电话)
优点:可靠,稳定,传输完整稳定,不限制数据大小
缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认
应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景

UDP(User Datagram Protocol)一种面向无连接的、不可靠的传输层通信协议(比如:发短信)
优点:速度快,可以多人同时聊天,耗费资源少,不许要建立链接
缺点:不稳定,不能保证每次数据都能接收到
应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景

客户端和服务端建立链接时:三次握手
客户端和服务端断开链接时:四次挥手
	四次挥手MSL为最大报文段生存时间,默认规定MSL为2分钟,但实际应用中常用的是30秒,1分钟和2分钟等

六、socket

socket的意义:网络通信过程中,信息拼接的工具(中文:套接字)

在开发中,一个端口只对应一个程序生效,在测试时,允许端口重复捆绑(开发时删掉)

在bind方法之前加上这句话,可以让一个端口重复使用

sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

实现TCP发消息

# 服务端
import socket
# 1 创建socket对象
sk = socket.socket()
# 2 绑定对应的ip和端口(注册网络,让其他主机能够找到)
sk.bind(('127.0.0.1', 8000))
# 3 开启监听
sk.listen()
# 4 建立三次握手
conn, addr = sk.accept()
print(conn, addr)
# 5 收发数据(recv里面的参数单位是字节,代表一次最多接收多少数据)
ret = conn.recv(1024)
# print(ret)
print(ret.decode("utf-8"))
# 6 四次挥手
conn.close()
# 7 退还端口
sk.close()


# 客户端
import socket
# 1 创建一个socket对象
sk = socket.socket()
# 2 与服务器进行连接
sk.connect(('127.0.0.1', 8000))
# 3 发送数据(二进制字节流)
sk.send("今天风和日丽".encode("utf-8"))
# 4 关闭连接
sk.close()

实现TCP循环发消息:

在收发数据的时候不使用encode编码是不能传输中文的

因为传输的数据类型是二进制字节流

要使用b修饰字符串,代表是二进制字节流

注:里面的字符串必须是ascii编码中的字符,不能是中文,否则报错

# 客户端
import socket
sk = socket.socket()
# 让端口重复绑定多个程序(仅仅在测试阶段使用)
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(('127.0.0.1', 8000))
sk.listen()
conn, addr = sk.accept()
while True:
    ret = conn.recv(1024)
    print(ret.decode())
    msg = input("服务端请输入:")
    conn.send(msg.encode())
conn.close()
sk.close()

# 服务端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8000))
while True:
    msg = input("客户端请输入:")
    sk.send(msg.encode())
    ret = sk.recv(1024)
    print(ret.decode())
sk.close()

实现UDP发送消息:

# 服务端
import socket
# 1 创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)
# 2 绑定地址端口
sk.bind(('127.0.0.1', 8000))
# 3 接收消息(udp作为服务端的时候,第一次一定是接收消息)
msg, cli_addr = sk.recvfrom(1024)
print(msg.decode())
print(cli_addr)
# 服务端给客户端发消息
msg = "你好,boy"
sk.sendto(msg.encode(), cli_addr)
# 4 关闭连接
sk.close()

# 客户端
import socket
# 1 创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)
# 2 发送数据
msg = "你好,girl"
sk.sendto(msg.encode(), ("127.0.0.1", 8000))
# 客户端接受服务端发过来的数据
msg, ser_addr = sk.recvfrom(1024)
print(msg.decode())
print(ser_addr)
# 3 关闭文件
sk.close()

实现UDP循环发送消息:

# 服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 8000))
while True:
    ret, cli_addr = sk.recvfrom(1024)
    print(ret.decode())
    msg = input("服务端请输入:")
    sk.sendto(msg.encode(), cli_addr)

sk.close()

# 客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
while True:
    msg = input("客户端输入:")
    sk.sendto(msg.encode(), ("127.0.0.1", 8000))
    ret, ser_addr = sk.recvfrom(1024)
    print(ret.decode())
sk.close()

黏包问题

tcp协议在发送数据时,会出现黏包现象

(1)数据黏包是因为在客户端/服务器端都会有一个数据缓冲区,缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。

(2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包。

黏包出现的两种情况

(1)黏包现象一:
	在发送端,由于两个数据短,发送的时间间隔短,所以在发送端形成黏包
	
(2)粘包现象二:
	在发送端,由于两个数据几乎同时被发送到对方的缓存中,所以在接收端形成了黏包

粘包对比:TCP和UDP

tcp协议
缺点:接收数据之间无边界,有可能粘合几条数据成一条数据,造成粘包
优点:不限制数据包的大小,稳定传输不丢包

udp协议:
优点:接收时侯数据之间有边界,传输速度快,不粘包
缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应相应完毕为止
而udp一旦发送失败,是不会询问对方是否有相应的,数据量过大就会容易丢包

解决黏包问题

解决黏包场景:
	应用场景在实时通讯时,需要阅读此次发送的消息是什么
不需要解决粘包场景:
	下载或者上传文件的时候,最后要把包都结合在一起,粘包无所谓
# 普通解决粘包方式第一种是通过time.slee()阻塞
# 普通解决粘包方式第二种是先把长度发送过去在进行接收
# 服务端
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(('127.0.0.1', 8000))
sk.listen()
conn, addr = sk.accept()
conn.send('00000100'.encode())
msg = "hello" * 20
conn.send(msg.encode())
conn.send(",word".encode())
conn.close()
sk.close()

# 客户端
import socket
import time
sk = socket.socket()
sk.connect(('127.0.0.1', 8000))

ret = sk.recv(8)
num = int(ret.decode("utf-8"))
ret1 = sk.recv(num)
# time.sleep(0.1)
ret2 = sk.recv(1024)
print(ret1)
print(ret2)
sk.close()

struct模块

pack:把任意长度的数字转化成居右4个字节的固定长度的字节流
unpack:把四个字节值恢复成原本的数字,返回元组
参数i:int的简写,要转化的当前数据时整型
注:pack的数值范围不是无限的,上限大概在21个亿左右,不要超过这个值
# 服务端
import socket
import struct
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(('127.0.0.1', 8000))
sk.listen()
conn, addr = sk.accept()
strvar = input("服务端请输入:")
msg = strvar.encode()
length = len(msg)

res = struct.pack('i', length)
conn.send(res)
conn.send(msg)

conn.send(",world".encode())

conn.close()
sk.close()

# 客户端
import socket
import time
import struct

sk = socket.socket()
sk.connect(('127.0.0.1', 8000))
n = sk.recv(4)
tup = struct.unpack('i', n)
n = tup[0]
print(n)
res1 = sk.recv(n)
print(res1.decode())
res2 = sk.recv(1024)
print(res2.decode())
sk.close()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值