物理层-》数据链路层-》网络层-》传输层-》会话层-》表示层-》应用层
网络接口层(物理层,数据链路层)-》互联网层(链路层)-》传输层(传输层)-》应用层(会话层,表示层,应用层)
TCP和UDP协议是传输层最重要的两种协议,为上层用户提供级别的通信可靠性
传输控制协议(TCP):TCP定义了两台计算机之间进行可靠的传输而交换的数据和确认信息的格式,以及计算机为了确保数据的正确到达而采取的措施。协议规定了TCP软件怎样识别给定计算机上的多个目的进程,如何对分组、重复这类差错进行恢复。还规定了两台计算机如何初始化TCP数据流传输以及如何结束这已传输。
特点:提供的是面向连接、可靠的字节流服务
TCP适用于对可靠性要求高的通信系统
用户数据报协议(UDP):UDP是一个简单的面向数据报的传输层协议。
特点:提供的是非面向连接的、不可靠的数据流传输,由于在传输数据报文前不用在客户端和服务器之间建立一个连接,也没有超时重发等机制,所以传输速度很快。
UDP适用于一次只传输少量数据、对可靠性要求不高的应用环境
TCP协议与UDP协议最大的区别就是:TCP是面向连接的,UDP是无连接的。TCP协议和UDP协议各有所长,各有所短,使用于不同的通信环境
HTTP是一个简单的响应-请求协议,通常运行在TCP之上。
HTTP是基于客户/服务器模式,且面向连接的,无状态的。
事务处理过程:
socket是应用层与TCP/IP协议族通信的中间软件抽象层,是一组接口。在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。
本地的进程间通信可以使用队列,同步(互斥锁、条件变量等)
网络进程间的通信:
利用IP地址,协议,端口号,就可以在标识网络中的进程,这样就可以利用这个标识去与其他进程进行交互
scoket是进程间通信的一种方式,它能实现不同主机间的进程通信
简单文件传输协议
特点:简单,占用资源小,适合传递小文件,适合在局域网进行传递,端口号为69,基于UDP实现
TFTP数据包格式
分为服务端和客户端
from socket import *
import struct
# 服务器端socket
s = socket(AF_INET, SOCK_DGRAM)
# 绑定IP和端口号
s.bind(('', 69))
def download(filename, client_ip, client_port):
# 创建一个新的socket,负责发送文件内容的数据包到客户端
new_socket = socket(AF_INET, SOCK_DGRAM)
# 文件内容数据包计数器,其实就是数据块的编号
num = 0
# 定义客户端退出的标签
flag = True
# 定义文件f
f = None
try:
f = open(filename, 'rb')
except:
error_package = struct.pack('!HH5sb', 5, 5, 'error'.encode('utf-8'), 0) # H表示python的Integer转成c的没有符号的short,
# 5s表示把python中含有5个字符的字符串转换成c的字符数组
new_socket.sendto(error_package, (client_ip, client_port)) # 把错误数据包发给客户端
# exit()#当前线程结束,当前客户端退出服务器
flag = False
# 如果文件存在,需要把文件内容切成一个个的数据包发送给客户端,一个数据包数据内容包含512字节数据
while flag:
# 从文件内容中读取512字节
read_data = f.read(512)
# 创建一个数据包
data_package = struct.pack('!HH', 3, num) + read_data
# 发送数据包
new_socket.sendto(data_package, (client_ip, client_port))
if len(read_data) < 512: # 文件内容的数据读完
print(f"客户端{client_ip},文件下载完成")
# exit() # 当前线程退出
break
# 服务器接收ACK的确认数据
recv_ack = new_socket.recvfrom(1024) # 里面有数据包内容,还有客户端ip和端口
oprator_code, ack_num = struct.unpack('!HH', recv_ack[0])
print('客户端:%s,的确认信息是' % client_ip, ack_num)
num += 1
# 保护性代码
if int(oprator_code) != 4 or int(ack_num) < 0: # 不正常的ack确认信息
break
if f:
f.close()
new_socket.close() # 客户端真正退出
def upload(filename, client_ip, client_port):
# 新建一个socket,用来发送数据包
new_socket = socket(AF_INET, SOCK_DGRAM)
# 新建文件,用来写入数据
f = None
# 新建flag,标记客户端退出
flag = True
try:
f = open('service_' + filename, 'ab')
except:
# 建立一个error包,用来给客户端返回错误数据
error_package = struct.pack('!HH5sb', 5, 5, 'error'.encode('utf-8', 0))
# 将数据包发送给客户端
new_socket.sendto(error_package, (client_ip, client_port))
flag = False
# 向客户端返回ack
data_ack = struct.pack('!HH', 4, 0)
new_socket.sendto(data_ack, (client_ip, client_port))
while flag:
# 接收客户端返回的文件内容
recv_data = new_socket.recvfrom(1024)
operator_data, num = struct.unpack('!HH', recv_data[0][:4])
if operator_data == 5:
print('上传出错,文件不存在')
break
# 将文件内容写入文件
f.write(recv_data[0][4:])
# 向客户端返回ack
data_ack = struct.pack('!HH', 4, num)
new_socket.sendto(data_ack, (client_ip, client_port))
if len(recv_data[0]) < 516:
print(f'客户端{client_ip},上传完成')
break
def server():
while True:
# 服务器等待客户端发送过来数据,进行接收
recv_date, (clien_ip, client_port) = s.recvfrom(1024)
print(recv_date, clien_ip, client_port)
# 判断数据包是否是客户端请求的数据包
if struct.unpack('!b5sb', recv_date[-7:]) == (0, b'octet', 0):
# 得到操作码的值
operator_code = struct.unpack('!H', recv_date[:2])
# 得到文件名字
file_name = recv_date[2:-7].decode('utf-8')
if operator_code[0] == 1: # 如果等于1就是下载请求数据包
print(f'客户端想下载文件:{file_name}')
download(file_name, clien_ip, client_port)
elif operator_code[0] == 2: # 如果等于2就是上传请求数据包
print(f'客户端想上传的文件:{file_name}')
upload(file_name, clien_ip, client_port)
if __name__ == '__main__':
server()
from socket import *
import struct # 负责python数据结构和C语言的数据结构转换
def download(file_Name, s, host_port):
# octet是C里面的字节数据
# pack把数据变成包
# '!H%dsb5sb'代表格式,!开头,H:将python整形转换为C中短整型,%ds:字符串长度为len的字符串,b:数字型字符
# 请求的数据包
data_package = struct.pack('!H%dsb5sb' % len(file_Name), 1, file_Name.encode('utf-8'), 0, 'octet'.encode('utf-8'),
0)
# 把数据包发到目标服务器
s.sendto(data_package, host_port)
# 客户端首先创建一个空白文件
f = open('client_' + file_Name, 'ab')
while True:
# 客户端接收服务器发过来的数据,数据只有两种:1.下载文件内容数据包,2.error信息包
recv_date, (server_ip, server_port) = s.recvfrom(1024)
oprator_code, num = struct.unpack('!HH', recv_date[:4]) # 把前四个字节的数据解包出来
if int(oprator_code) == 5: # 判断数据包是否是error信息包
print('服务器返回:要下载的文件不存在')
break
# 如果是文件内容数据包,需要保存文件内容
f.write(recv_date[4:])
if len(recv_date) < 516: # 意味着服务器传送过来的文件已经接受完成了
print('客户端下载成功')
break
# 客户端收到数据包之后还需要发送一个确认ACK给服务器
ack_package = struct.pack('!HH', 4, num)
s.sendto(ack_package, (server_ip, server_port))
# 释放资源
f.close()
def upload(file_Name, s, host_port):
flag = True
num = 0
f = None
try:
f = open(file_Name, 'rb')
except:
print('文件不存在')
return
# 创建请求数据包
data_package = struct.pack(f'!H{len(file_Name)}sb5sb', 2, file_Name.encode('utf-8'), 0, 'octet'.encode('utf-8'), 0)
# 向服务器发送数据包
s.sendto(data_package, host_port)
# 接收服务器传回来的ACK
data_ack, (service_ip, service_port) = s.recvfrom(1024)
oprator_data, ack_num = struct.unpack('!HH', data_ack)
print(f"服务器返回的ack确认信息是{oprator_data},{ack_num}")
if int(oprator_data) != 4 or ack_num < 0:
flag = False
while flag:
# 读取文件中的数据,每次512字节
read_data = f.read(512)
# 创建文件数据包
new_package = struct.pack('!HH', 3, num) + read_data
# 发送数据包
s.sendto(new_package, (service_ip, service_port))
# 接收服务器返回的ack数据
recv_ack = s.recvfrom(1024) # 里面有数据包内容,还有服务端ip和端口
oprator_code, ack_num = struct.unpack('!HH', recv_ack[0])
print('服务端的确认信息是', ack_num)
if int(oprator_code) != 4 or int(ack_num) < 0: # 不正常的ack确认信息
break
# 判断是不是最后一次传输数据
if len(read_data) < 512:
print(f'客户端文件{file_Name},上传完成')
break
else:
num += 1
if f:
f.close()
if __name__ == '__main__':
file_Name = input("请输入文件名:")
while True:
try:
code = int(input("上传还是下载?(1:下载;2:上传)"))
except:
print('请输入1或2!')
else:
break
# 客户端套接字
s = socket(AF_INET, SOCK_DGRAM)
# 定义服务器地址和端口号
host_port = ('192.168.199.103', 69)
if code == 1:
download(file_Name, s, host_port)
elif code == 2:
upload(file_Name, s, host_port)
s.close()
注意点:
from socket import *
server_socket = socket(AF_INET,SOCK_STREAM)
server_socket.bind(('',8008))
server_socket.listen(1)
while True:
new_socket,client_ip_port = server_socket.accept()
while True:
recv_data = new_socket.recv(1024)
if len(recv_data) > 0:#客户端没有退出,而且发送数据到服务器了
print('客户端:',recv_data.decode('utf-8'))
else:
print('客户端已经退出!')
break
if recv_data == 'exit':
print('客户端已经退出!')
break
# 发送数据给客户端
send_data = input('send:')
if len(send_data) > 0:
new_socket.send(send_data.encode('utf-8'))
new_socket.close()
server_socket.close()
from socket import *
client_socket = socket(AF_INET,SOCK_STREAM)
client_socket.connect(('192.168.199.103',8008))
while True:
send_data = input('send:')
if len(send_data) > 0:
client_socket.send(send_data.encode('utf-8'))
else:
break
if send_data == 'exit':
client_socket.close()
break
# 客户端接收服务器返回的内容
recv_data = client_socket.recv(1024)
print('服务器:',recv_data.decode('utf-8'))
client_socket.close()
黏包指的是数据和数据之间没有明确的分界线,导致不能正确读取数据
服务器端
# author:ycw
# date: 2020/10/23 11:31
from socket import *
server_socket = socket(AF_INET,SOCK_STREAM)
server_socket.bind(('',8080))
server_socket.listen(5)
new_socket,client_addr = server_socket.accept()
recv_data1 = new_socket.recv(1024)
recv_data2 = new_socket.recv(1024)
print('第一条数据:',recv_data1,'第二条数据:',recv_data2)
new_socket.close()
server_socket.close()
客户端
from socket import *
client_socket = socket(AF_INET,SOCK_STREAM)
client_socket.connect(('192.168.199.103',8080))
client_socket.send('hello'.encode('utf-8'))
client_socket.send('word'.encode('utf-8'))
client_socket.close()
服务器端
# author:ycw
# date: 2020/10/23 11:47
#接收端黏包问题
from socket import *
import time
server_socket = socket(AF_INET,SOCK_STREAM)
server_socket.bind(('',8080))
server_socket.listen(5)
new_socket,client_addr = server_socket.accept()
print('连接成功',client_addr)
data1 = new_socket.recv(2) #第一次没有接收完整
print('第一条数据:',data1)
time.sleep(6)
data2 = new_socket.recv(10)# 第二次会接收旧数据(第一次没有接收完的数据),如果还有空间,再接收新书
print('第二条数据:',data2)
new_socket.close()
server_socket.close()
客户端
# author:ycw
# date: 2020/10/23 11:43
# 接收方可能出现的黏包问题
from socket import *
import time #通过time模块保证客户端发送多个数据包的时候,间隔时间长
client = socket(AF_INET,SOCK_STREAM)
client.connect(('192.168.199.103',8080))
client.send('hello'.encode('utf-8'))
time.sleep(5)#让当前的线程休眠5秒
client.send('world'.encode('utf-8'))
client.close()
先计算将要发送的数据的长度,发送给接收方,接收方根据这个长度来接收相应的数据
服务器端
# author:ycw
# date: 2020/10/23 12:20
from socket import *
import os
import struct
server = socket(AF_INET,SOCK_STREAM)
server.bind(('',9999))
server.listen(5)
conn,client_addr = server.accept()
f = open('D:\学习内容\软件学习\python网络编程\服务器.flac','wb')
head = conn.recv(4)
size = struct.unpack('!i',head)[0]#unpack返回的都是一个元组,元组的第一个值就是长度
recv_size = 0 # 已经接收到多长的数据
while recv_size<size:
data = conn.recv(1024)
recv_size += len(data) # 接收的字节长度要累加
print(recv_size)
f.write(data)
print('服务器端接收完成')
f.close()
conn.close()
server.close()
客户端
# author:ycw
# date: 2020/10/23 12:10
from socket import *
import struct
import os
client = socket(AF_INET,SOCK_STREAM)
client.connect(('192.168.199.103',9999))
# 客户端传送文件到服务器,new.flac
file_path = 'new.flac'
f = open(file_path,'rb')
#在发送真正的文件数据之前,先准备一个报头
size = os.path.getsize(file_path) # 文件的字节长度.
print(size)
# 创建一个包头,i为4个字节的int,
head = struct.pack('!i',size)#接收方会使用struct解包,得到一个int类型的数字
client.send(head)#发送包头
#发送文件内容
while True:
data = f.read(1024)#每次读1024字节
if not data:
break
client.send(data)#发送给服务器
print('客户端上传文件完成')
f.close()
client.close()