网络编程二
传输层:
1:PORT协议
2:TCP协议与UDP协议
规定了数据传输所遵循的规则
TCP协议
‘基于TCP传输数据,数据不容易丢失,不容易丢失的原因是在于二次确认机制’
1 三次握手:建立双向通道
客户端和服务端之间的通信
1 c>s 发送请求询问是否可以建立一个数据通道
(2 s>c 发送确认允许建立一个数据通道
3 s>c 发送请求 询问是否可以建立一个数据通道)
4 c>s 发送确认允许建立一个数据通道
双方都可以基于彼此的通道给对方发送数据
三次握手建连接(2和3合并)
补充:洪水攻击
同时让大量的客户端朝服务端发送建立TCP连接的请求
四次挥手:断开双向通道
1 c>s 发送请求关闭数据通道
2 s>c 发送确认只能允许关闭数据通道
[四次挥手不能合并因为另外一方游客鞥还有数据没有发送完毕需要有一个检查的过程]
3 s>c 发送请求请求关闭数据通道
4 c>s 发送确认只能允许关闭数据通道
UDP协议:
基于UDP协议发送数据,没有任何的通道也没有任何的权限
UDP发送数据没有TCP安全(没有二次确认机制)
应用层
主要取决于程序员自己采用什么策略和协议
常见协议有:HTTP HTTPS FTP…
socket套接字(模块)
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
基于网络类型的套接字家族
套接字家族的名字:AF_INET
查看ip地址 ipconfig
‘运行程序时先运行服务端,在运行客户端’
socket模块服务端
import socket
# 1创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
#2 绑定一个固定的地址(pip\port)
server.bind(('127.0.0.1',8181))# 127.0.0.1本地回环地址(只允许自己的机器访问)
#3 半连接池
server.listen(5)
# 4 等待接收信息
sock,address = server.accept() # sock是双向通道 address是客户端地址
print(sock,server)
# 5 数据交互
sock.send(b'dui dui a') # 朝客户端发送数据
data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
print(data)
# 6 断开连接
sock.close() # 断链接
server.close() # 关机
socket模块客户端
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('127.0.0.1',8181))
# 3.数据交互
data=client.recv(1024)# 接收服务端发送的数据
print(data)
client.send(b'xi xi xi') # 朝服务端发送数据
#4 关闭
client.close()
socket代码优化
1.send与recv
客户端与服务端不能同时执行同一个
有一个收 另外一个就是发
有一个发 另外一个就是收
不能同时收或者发!!!
2.消息自定义
input获取用户数据即可(主要编码解码)
3.循环通信
给数据交互环节添加循环即可
4.服务端能够持续提供服务
不会因为客户端断开连接而报错
异常捕获 一旦客户端断开连接 服务端结束通信循环 调到连接处等待
5.消息不能为空
判断是否为空 如果是则重新输入(主要针对客户端)
6.服务端频繁重启可能会报端口被占用的错(主要针对mac电脑)
from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
7.客户端异常退出会发送空消息(针对mac linux)
针对接收的消息加判断处理即可
socket模块服务端
import socket
# 1创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
#2 绑定一个固定的地址(pip\port)
server.bind(('127.0.0.1',8181))# 127.0.0.1本地回环地址(只允许自己的机器访问)
#3 半连接池
server.listen(5)
# 4 等待接收信息
while True:
sock,address = server.accept() # sock是双向通道 address是客户端地址
print(sock,server)
# 5 数据交互
while True:
try:
msg = input('请输入发送给客户端的消息>>>:').strip()
if len(msg) == 0: continue
sock.send(msg.encode('utf8')) # 朝客户端发送数据
data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
print(data.decode('utf8'))
except ConnectionError:
sock.close()
break
socket模块客户端
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('127.0.0.1',8181))
# 3.数据交互
while True:
data=client.recv(1024)# 接收服务端发送的数据
print(data.decode('utf8'))
msg = input('请输入发送给服务端的消息>>>:').strip()
if len(msg) ==0: continue
client.send(msg.encode('utf8')) # 朝服务端发送数据
半连接池
server.listen()
主要是为了做缓冲 避免太多无效等待
黏包问题
黏包现象只发生在tcp协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
基于tcp协议特点的黏包现象成因
标题会发生黏包的两种情况
情况一 发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
情况二 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
黏包的解决方案
解决方案一
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
解决方案二
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes