文章目录
网络编程实操
socket 套接字编程
服务端
server = socket.socket() # 默认就是基于网络的TCP传输协议
server.bind(('127.0.0.1', 8080)) # 绑定ip和port '127.0.0.1' 是本地回环地址
server.listen(5) # 半连接池 开机(过渡)
sock, address = server.accept() # 监听 三次握手的listen态
print(address) # 客户端地址
data = sock.recv(1024) # 接收客户端发送的消息
print(data)
sock.send(b'hello my big baby~~~') # 给别人回话
sock.close() # 挂电话
server.close() # 关机
客户端
import socket
client = socket.socket() # 买手机
client.connect(('127.0.0.1', 8080)) # 拨号
# 说话
client.send(b'hello big DSB DSB DSB!')
# 听他说
data = client.recv(1024)
print(data)
client.close()
通信循环以及代码优化
- 上诉代码实现了简单的服务端和客户端交互,但是次数太少了,接下来我们给代码进行优化,能够频繁交流,并且能够交流
server 端
import socket
server = socket.socket() # 默认就是基于网络的TCP传输协议
server.bind(('127.0.0.1', 8080)) # 绑定ip和port '127.0.0.1' 是本地回环地址
server.listen(5) # 半连接池 开机(过渡)
sock, address = server.accept() # 监听 三次握手的listen态
print(address) # 客户端地址
while True:
data = sock.recv(1024) # 接收客户端发送的消息
print(data.encode('utf8'))
sock.send(data+b'123')
sock.close() # 挂电话
server.close() # 关机
client 端
import socket
client = socket.socket() # 买手机
client.connect(('127.0.0.1', 8080)) # 拨号
while True:
into_info = input('你说一句>>>:').strip()
# 说话
client.send(into_info.decode('utf8'))
data = client.recv(1024)
print(data.decode('utf8'))
client.close()
完成以上功能够,还是有一些问题需要我们去解决,例如:
1. 客户端校验消息不能为空
if len(data) == 0: continue
2. 服务端添加兼容性代码
if len(data) == 0: break
3. 服务端重启频繁报端口占用错误
from socket import SOL_SOCKET, SO_REUSEADDR
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 在bind前加
4. 客户端异常关闭服务端报错的问题
使用异常捕获
5. 服务端链接循环
加一层循环,当客户端断开链接后,能够重新接受新的客户端ip
6. 半连接池
server.listen(5)
设置可以等待的客户端数量
server 端
import socket
from socket import SOL_SOCKET, SO_REUSEADDR # 3
server = socket.socket()
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 3
server.bind(('127.0.0.1', 8080))
server.listen(5) # 6
while True: # 5
sock, address = server.accept()
print(address)
while True:
try : # 4
data = sock.recv(1024)
if len(data) == 0: break # 2
print(data.encode('utf8'))
sock.send(data+b'123')
except Exception as e:
print(e)
sock.close()
server.close()
client 端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
into_info = input('你说一句>>>:').strip()
if len(into_info) == 0: continue # 1
client.send(into_info.decode('utf8'))
data = client.recv(1024)
print(data.decode('utf8'))
client.close()
黏包现象
黏包现象产生的原因
黏包现象的解决办法
该模块可以把一个类型,如数字,转成固定长度的bytes
server 端
import socket
import subprocess
import json
import struct
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, address = server.accept()
while True:
data = sock.recv(1024) # 接收cmd命令
command_cmd = data.decode('utf8')
sub = subprocess.Popen(command_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
res = sub.stdout.read() + sub.stderr.read() # 结果可能很大
# 1.制作报头
data_first = struct.pack('i', len(res))
# 2.发送报头
sock.send(data_first)
# 3.发送真实数据
sock.send(res)
client 端
import socket
import struct
client = socket.socket() # 买手机
client.connect(('127.0.0.1', 8080)) # 拨号
while True:
msg = input('请输入cmd命令>>>:').strip()
if len(msg) == 0:
continue
client.send(msg.encode('utf8'))
# 1.先接收固定长度为4的报头数据
recv_first = client.recv(4)
# 2.解析报头
real_length = struct.unpack('i',recv_first)[0]
# 3.接收真实数据
real_data = client.recv(real_length)
print(real_data.decode('gbk'))
案例: 局域网内实现文件上传下载
server 端
import os
import json
import socket
from socket import SOL_SOCKET, SO_REUSEADDR
import common
server = socket.socket()
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind(('192.168.11.43', 8080)) # 服务器ip地址和端口
server.listen(5)
data_path = r'./服务端视频' # 服务端路径
if not os.path.exists(data_path):
os.mkdir('./服务端视频')
if not os.path.
# 获取文件名列表
file_name_list = os.listdir(data_path)
while True:
sock, address = server.accept()
print(address)
while True:
data = sock.recv(4)
if not data: break
# 判断是否从服务器下载资源
if data == bytes(1):
# 序列化文件列表
list_json = json.dumps(file_name_list)
# 传输文建列表
common.info_into(sock,'文件列表', len(list_json))
sock.send(list_json.encode('utf8'))
# 获取用户选择文件信息
choice_data = sock.recv(4)
if not choice_data: break
choice = int(choice_data.decode('utf8'))
file_name = file_name_list[choice-1]
# 拼接路径,获取文件大小
res = common.read_file(data_path, file_name, sock)
if res:
break
elif data == bytes(2):
real_dict2 = common.parse_dict(sock)
common.witer_file(file_name_list, real_dict2, data_path, sock)
client 端
import os
import json
import socket
import common
client = socket.socket()
choice_list = ['下载文件', '上传文件', '退出']
data_path = r'./本地视频' # 本地文件路径
file_name_list = os.listdir(data_path)
while True:
client.connect(('192.168.11.43', 8080)) # 服务器ip地址和端口
res = common.print_list(choice_list)
if not res: continue
client.send(bytes(res))
if res == 1:
real_dict1 = common.parse_dict(client)
# 接受文件列表
life_list = client.recv(real_dict1.get('size'))
# 获取文件列表信息
real_file_list = json.loads(life_list)
# 打印文件信息
temp = common.print_list(real_file_list)
if not temp : continue
client.send(str(temp).encode('utf8'))
real_dict2 = common.parse_dict(client)
common.witer_file(file_name_list, real_dict2, data_path, client)
elif res == 2:
choice = common.print_list(file_name_list)
if not choice: continue
file_name = file_name_list[choice - 1]
res = common.read_file(data_path, file_name, client)
else:
client.close()
break
common 公用部分
import json
import struct
import os
import time
# 枚举出列表内信息
def print_list(mylist):
for i, j in enumerate(mylist, 1):
print('%s>>>>%s' % (i, j))
choice = eval(input('请输入功能编号: ').strip())
if choice not in range(1, len(mylist) + 1):
print('请输入正确的编号!!!')
return
return choice
# 解析报头
def parse_dict(sock):
# 接受报头信息
recv_first = sock.recv(4)
# 解析字典报头
dict_length = struct.unpack('i', recv_first)[0]
# 接收字典数据
real_data = sock.recv(dict_length)
# 解析字典(json格式的bytes数据 loads方法会自动先解码 后反序列化)
return json.loads(real_data)
# 信息传输
def info_into(sock, name, size):
# 定义一个字典数据
data_dict = {
'file_name': name,
'size': size,
}
# 序列化字典
data_json = json.dumps(data_dict)
# 制作字典报头
data_first = struct.pack('i', len(data_json))
# 发送字典报头
sock.send(data_first)
# 发送字典数据
sock.send(data_json.encode('utf8'))
# 写入文件
def witer_file(file_name_list, real_dict2, data_path, sock):
for f_name in file_name_list:
if real_dict2.get('file_name') == f_name:
write_file_name = '(1)' + real_dict2.get('file_name')
break
else:
write_file_name = real_dict2.get('file_name')
write_file_path = os.path.join(data_path, write_file_name)
recv_size = 0
real_size = real_dict2.get('size')
scale = 50
print("正在下载".center(scale // 2, "-"))
start = time.time()
with open(write_file_path, 'wb') as f:
while recv_size < real_size:
data = sock.recv(1024)
recv_size += len(data)
f.write(data)
i = (recv_size / real_size) * 50
i = int(i)
a = '#' * i
b = '.' * (scale - i)
c = (i / scale) * 100
dur = time.time() - start
print("\r{:^3.0f}%[{}->{}]{:.2f}s".format(c, a, b, dur),end='')
print("\n" + "下载完成".center(scale // 2, '-'))
# 读出数据
def read_file(data_path, file_name, sock):
file_path = os.path.join(data_path, file_name)
file_size = os.path.getsize(file_path)
recv_size = 0
scale = 50
try:
# 传输文件
info_into(sock, file_name, file_size)
start = time.time()
print("正在上传".center(scale // 2, "-"))
with open(file_path, 'rb') as f:
for line in f:
sock.send(line)
recv_size += len(line)
i = (recv_size / file_size) * 50
i = int(i)
a = '#' * i
b = '.' * (scale - i)
c = (i / scale) * 100
dur = time.time() - start
print("\r{:^3.0f}%[{}->{}]{:.2f}s".format(c, a, b, dur), end='')
print("\n" + "上传完成".center(scale // 2, '-'))
except Exception as e:
print(e)
return True
扩展知识
在阅读源码的时候
1.变量名后面跟冒号 表示的意思是该变量名需要指代的数据类型
2.函数后更横杆加大于号表示的意思是该函数的返回值类型