Python之路(第三十二篇) 网络编程:udp套接字、简单文件传输

python

服务端

  # udp是无链接的,先启动哪一端都不会报错

# udp没有链接,与tcp相比没有链接循环,只有通讯循环

server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建一个服务器的套接字

server.bind() #绑定服务器套接字

inf_loop: #服务器无限循环

cs = server.recvfrom()/server.sendto() # 对话(接收与发送)

server.close() # 关闭服务器套接字

  

客户端

  client = socket()   # 创建客户套接字

comm_loop: # 通讯循环

client.sendto()/client.recvfrom() # 对话(发送/接收)

client.close() # 关闭客户套接字

  

 简单例子

服务端

  import socket

ip_port = ('127.0.0.1',8081)

server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server.bind(ip_port)

while True:

print('udp服务端开始运行了')

data,addr = server.recvfrom(1024)

print(data.decode('utf-8'))

msg = input("请输入").strip()

server.sendto(msg.encode("utf-8"),addr)

  

客户端

  import socket

ip_port = ('127.0.0.1', 8081)

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:

print('udp客户端开始运行了')

msg = input("请输入").strip()

server.sendto(msg.encode("utf-8"), ip_port)

data, addr = server.recvfrom(1024)

print(data.decode("utf-8"))

  

注意:udp 可以发空 数据报协议 说是发空,其实不是空 ,还有一个IP 端口的信息,发空时 带个端口信息,

tcp:不是一一对应的,udp:是一一对应的 数据报完整的

用upd做一个ntp时间服务器

服务端

  import socket

import time

ip_port = ("127.0.0.1",8080)

buffer_size = 1024

ntp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

ntp_server.bind(ip_port)

while True:

data,addr = ntp_server.recvfrom(buffer_size)

print("收到客户端的命令是",data.decode("utf-8"))

if not data:

fmt = "%Y-%m-%d %X"

else:

fmt = data.decode("utf-8")

time_now = time.strftime(fmt,time.localtime())

ntp_server.sendto(time_now.encode("utf-8"),addr)

  

客户端

  import socket

ip_port = ("127.0.0.1",8080)

buffer_size = 1024

ntp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:

msg = input(">>>")

ntp_client.sendto(msg.encode("utf-8"),ip_port)

recv_msg,addr = ntp_client.recvfrom(buffer_size)

print(recv_msg.decode("utf-8"))

  

基于udp简单实现QQ聊天

服务端

from socket import *

udp_server= socket(AF_INET,SOCK_DGRAM)

udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

udp_server.bind(('127.0.0.1',8080))

print('start running...')

while True:

qq_msg,addr = udp_server.recvfrom(1024)

print('来自[%s:%s]的一条消息:\033[44m%s\033[0m'%(addr[0],addr[1],qq_msg.decode('utf-8')))

back_msg = input('回复消息:>>').strip()

udp_server.sendto(back_msg.encode('utf-8'),addr)

udp_server.close()

  

客户端

  from socket import *

udp_client = socket(AF_INET,SOCK_DGRAM)

qq_name_dic = {

'pony':('127.0.0.1',8080),

'jack':('127.0.0.1',8080),

'charles':('127.0.0.1',8080),

'nick':('127.0.0.1',8080)

}

while True:

print("QQ名单列表:")

for i in qq_name_dic.keys():

print(i)

qq_name = input('请输入聊天对象:>>').strip()

if qq_name not in qq_name_dic: continue

while True:

msg = input('请输入消息,回车发送:').strip()

if msg=='quit':break

if not msg or not qq_name or qq_name not in qq_name_dic:continue

udp_client.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])

back_msg,addr = udp_client.recvfrom(1024)

print('来自[%s:%s]的一条消息:\033[41m%s\033[0m'%(addr[0],addr[1],back_msg.decode('utf-8')))

udp_client.close()

  

二、tcp与udp对比

tcp基于链接通信,数据流式协议

  • 基于链接,则需要listen(backlog),指定连接池的大小

  • 基于链接,必须先运行的服务端,然后客户端发起链接请求

  • 对于mac/linux系统:如果客户端断开了链接,那服务端的链接recv将会阻塞,通讯循环收到的是一直空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)

  • 对于windows系统:如果一端断开了链接,那另外一端的链接也跟着出错,(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)

  • 相对于upd传输速度慢

  • 流式协议 会粘包 不可以发空 send recv 不是 一 一对应

  • tcp适用于:

    • 数据一定要可靠

    • 远程执行命令

    • 下载文件

udp无链接,数据报式协议

  • 无链接,因而无需listen(backlog),更加没有什么链接池

  • 无链接,udp的sendto不用管是否有一个正在运行的服务端可以一直发消息,只不过数据可能会丢失

  • recvfrom收的数据小于sendto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错

  • 只有sendto发送数据没有recvfrom收数据,数据丢失

  • 数据报协议 不会粘包 可以发空 sendto recvfrom 一 一 对应 数据报协议 数据不安全 有可能发送数据 > 1024 或者网络网络异常 数据没了

  • udp适用于

    • QQ

    • 查询操作 eg: ntp时间服务器 dns服务器(查域名,转ip) 能保证查询效率高,数据虽然不可靠,传输过程中可能会发生数据丢失

三、基于socket实现文件网络传输

简单版本

服务端

  import socket

import os

import hashlib

import json

import struct

ip_port = ("127.0.0.1",9001)

back_log = 5

buffer_size = 1024

base_path = os.path.dirname(os.path.abspath(__file__))

share_path =os.path.join(base_path,"share")

ftp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

ftp_server.bind(ip_port)

ftp_server.listen(back_log)

def creat_md5(file):

md5_value = hashlib.md5()

with open(file,"rb") as f:

while True:

data = f.read(1024)

if not data:

break

md5_value.update(data)

return md5_value.hexdigest()

while True:

print("FTP服务器开始运行啦!")

conn,address = ftp_server.accept()

while True:

try:

# 第一步:收命令

res = conn.recv(8096) #"get a.txt"

if not res: continue

# 第二步:解析命令, 提取相应的命令参数

cmds = res.decode("utf-8").split()

file_name = cmds[1]

if cmds[0] == "get":

file_path = os.path.join(share_path,file_name)

file_md5 = creat_md5(file_path)

file_size = os.path.getsize(file_path)

#第三步:以读的方式打开文件,读取文件内容 发送给客户端,

# 1、先自制报头,传递文件的相关信息

header_dic = {

"filename":file_name,

"filemd5":file_md5,

"filesize":file_size

}

header_json = json.dumps(header_dic).encode("utf-8")

header_length = len(header_json)

header_struct = struct.pack("i",header_length)

# 2、发送报头的长度

conn.send(header_struct)

# 3、发送报头,传递文件的各种信息

conn.send(header_json)

# 4、打开文件,读取内容,一行一行的发送读取的内容给客户端

with open(file_path,"rb") as f:

for line in f:

conn.send(line)

except Exception as e:

print(e)

break

conn.close()

  

客户端

  import socket

import os

import struct

import json

import time

ip_port = ("127.0.0.1", 9001)

buffer_size = 1024

base_path = os.path.dirname(os.path.abspath(__file__))

download_path = os.path.join(base_path,"download")

ftp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

ftp_client.connect(ip_port)

while True:

# 第一步:写命令,发送命令给服务端

cmd = input("请输入命令: ")

if not cmd: continue

if cmd == "quit": break

ftp_client.send(cmd.encode("utf-8"))

# 第二步:收取自制报头的长度

header_struct = ftp_client.recv(4)

header_length = struct.unpack("i", header_struct)[0]

print("报头长度",header_length)

# 第三步:收取自制报头的信息

header_json = ftp_client.recv(header_length).decode("utf-8")

header_dic = json.loads(header_json)

print("报头字典",header_dic)

# 第四步:根据报头信息拼出文件的各种信息

file_name = header_dic["filename"]

file_md5 = header_dic["filemd5"]

file_size = header_dic["filesize"]

file_download_path = os.path.join(download_path,file_name)

# 第五步:以写的方式打开一个新文件,接收服务端发来的文件内容写入客户的新文件

with open(file_download_path,"wb") as f:

data_size = 0

start_time = time.perf_counter()

while data_size < file_size:

line = ftp_client.recv(buffer_size)

f.write(line)

data_size = data_size + len(line)

# print("已经写入数据",data_size)

download_percent = int((data_size/file_size)*100)

# print("百分比",download_percent)

a = "*" * download_percent

# print(a)

b = "." * (100 - download_percent)

# print(b)

c = (data_size/file_size)*100

during_time = time.perf_counter() - start_time

print("\r{:3.0f}%[{}-{}]共计用时:{:.3f}s".format(c,a,b,during_time),end="")

# sys.stdout.flush()

print("\n" + "执行结束")

ftp_client.close()

  

基于类写的文件传输

服务端

  import socket

import os

import struct

import pickle

class TCPServer:

address_family = socket.AF_INET

socket_type = socket.SOCK_STREAM

listen_count = 5

max_recv_bytes = 8192

coding = 'utf-8'

allow_reuse_address = False

# 下载的文件存放路径

down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'share')

# 上传的文件存放路径

upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'upload')

def __init__(self,server_address,bind_and_listen=True):

self.server_address = server_address

self.socket = socket.socket(self.address_family,self.socket_type)

if bind_and_listen:

try:

self.server_bind()

self.server_listen()

except Exception:

self.server_close()

def server_bind(self):

if self.allow_reuse_address: #重用ip和端口

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

self.socket.bind(self.server_address)

def server_listen(self):

self.socket.listen(self.listen_count)

def server_close(self):

self.socket.close()

def server_accept(self):

return self.socket.accept()

def conn_close(self,conn):

conn.close()

def run(self):

print('starting...')

while True:

self.conn,self.client_addr = self.server_accept()

print(self.client_addr)

while True:

try:

res = self.conn.recv(self.max_recv_bytes)

if not res:continue

cmds = res.decode(self.coding).split()

if hasattr(self,cmds[0]):

func = getattr(self,cmds[0])

func(cmds)

except Exception:

break

self.conn_close(self.conn)

def get(self,cmds):

""" 下载

1.找到下载的文件

2.发送 header_size

3.发送 header_bytes file_size

4.读文件 rb 发送 send(line)

5.若文件不存在,发送0 client提示:文件不存在

:param cmds: 下载的文件 eg:['get','a.txt']

:return:

"""

filename = cmds[1]

file_path = os.path.join(self.down_filepath, filename)

if os.path.isfile(file_path):

header = {

'filename': filename,

'md5': 'xxxxxx',

'file_size': os.path.getsize(file_path)

}

header_bytes = pickle.dumps(header) #直接用pickle转为bytes,不用json+encode转为bytes

self.conn.send(struct.pack('i', len(header_bytes)))

self.conn.send(header_bytes)

with open(file_path, 'rb') as f:

for line in f:

self.conn.send(line)

else:

self.conn.send(struct.pack('i', 0))

def put(self,cmds):

""" 上传

1.接收4个bytes 得到文件的 header_size

2.根据 header_size 得到 header_bytes header_dic

3.根据 header_dic 得到 file_size

3.以写的形式 打开文件 f.write()

:param cmds: 下载的文件 eg:['put','a.txt']

:return:

"""

obj = self.conn.recv(4)

header_size = struct.unpack('i', obj)[0]

header_bytes = self.conn.recv(header_size)

header_dic = pickle.loads(header_bytes)

print(header_dic)

file_size = header_dic['file_size']

filename = header_dic['filename']

with open('%s/%s' % (self.upload_filepath, filename), 'wb') as f:

recv_size = 0

while recv_size < file_size:

res = self.conn.recv(self.max_recv_bytes)

f.write(res)

recv_size += len(res)

tcp_server = TCPServer(('127.0.0.1',8080))

tcp_server.run()

tcp_server.server_close()

  

客户端

import socket

import struct

import pickle

import os

class FTPClient:

address_family = socket.AF_INET

socket_type = socket.SOCK_STREAM

# 下载的文件存放路径

down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'download')

# 上传的文件存放路径

upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'share')

coding = 'utf-8'

max_recv_bytes = 8192

def __init__(self, server_address, connect=True):

self.server_address = server_address

self.socket = socket.socket(self.address_family, self.socket_type)

if connect:

try:

self.client_connect()

except Exception:

self.client_close()

def client_connect(self):

self.socket.connect(self.server_address)

def client_close(self):

self.socket.close()

def run(self):

while True:

# get a.txt 下载 put a.txt 上传

msg = input(">>>:").strip()

if not msg: continue

self.socket.send(msg.encode(self.coding))

cmds = msg.split()

if hasattr(self,cmds[0]):

func = getattr(self,cmds[0])

func(cmds)

def get(self, cmds):

""" 下载

1.得到 header_size

2.得到 header_types header_dic

3.得到 file_size file_name

4.以写的形式 打开文件

:param cmds: 下载的内容 eg: cmds = ['get','a.txt']

:return:

"""

obj = self.socket.recv(4)

header_size = struct.unpack('i', obj)[0]

if header_size == 0:

print('文件不存在')

else:

header_types = self.socket.recv(header_size)

header_dic = pickle.loads(header_types)

print(header_dic)

file_size = header_dic['file_size']

filename = header_dic['filename']

with open('%s/%s' % (self.down_filepath, filename), 'wb') as f:

recv_size = 0

while recv_size < file_size:

res = self.socket.recv(self.max_recv_bytes)

f.write(res)

recv_size += len(res)

print('总大小:%s 已下载:%s' % (file_size, recv_size))

else:

print('下载成功!')

def put(self, cmds):

""" 上传

1.查看上传的文件是否存在

2.上传文件 header_size

3.上传文件 header_bytes

4.以读的形式 打开文件 send(line)

:param cmds: 上传的内容 eg: cmds = ['put','a.txt']

:return:

"""

filename = cmds[1]

file_path = os.path.join(self.upload_filepath, filename)

if os.path.isfile(file_path):

file_size = os.path.getsize(file_path)

header = {

'filename': os.path.basename(filename),

'md5': 'xxxxxx',

'file_size': file_size

}

header_bytes = pickle.dumps(header)

self.socket.send(struct.pack('i', len(header_bytes)))

self.socket.send(header_bytes)

with open(file_path, 'rb') as f:

send_bytes = b''

for line in f:

self.socket.send(line)

send_bytes += line

print('总大小:%s 已上传:%s' % (file_size, len(send_bytes)))

else:

print('上传成功!')

else:

print('文件不存在')

ftp_client = FTPClient(('127.0.0.1',8080))

ftp_client.run()

ftp_client.client_close()

  

以上是 Python之路(第三十二篇) 网络编程:udp套接字、简单文件传输 的全部内容, 来源链接: utcz.com/z/388538.html

回到顶部