python基础知识(九)网络编程
目录
9.1套接字(socket)
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
基于网络类型的套接字家族
套接字家族的名字:AF_INET,AF_INET6被用于ipv6
基于TCP协议的socket
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
#sever
import socket
sk = socket.socket() #买手机
sk.bind(('127.0.0.1',8080)) #绑定手机号 #sk.bind(('ip','port端口')) 把地址(环回地址)绑定到套接字
sk.listen() #监听链接
conn, addr = sk.accept() #接受客户端链接 connection 连接 address 地址
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
ret = conn.recv(1024) # 对话(发送/接收)
print(ret.decode('utf-8')) #汉字记得需要,添加编码格式utf-8
sk.close() #关闭服务器套接字(可选)
#client
import socket
sk = socket.socket() # 创建客户套接字
sk.connect(('127.0.0.1',8080)) # 尝试连接服务器
sk.send(b'hello!')
ret = sk.recv(1024) # 对话(发送/接收)
sk.send(bytes('你好',encoding='utf-8'))#汉字需要转码 encode()
print(ret)
sk.close()
基于UDP协议的socket
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接
在启动服务之后只能被动的等待客户端发送信息
客户端发送消息的同时还会自带地址信息
消息回复的时候,不仅需要发送消息,还需要把对方的地址发送过来
#sever
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',8080))
msg, addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.sendto(b'bye',addr)
sk.close()
#client
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
sk.sendto(b'hello',ip_port)
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.sendto(b'bye',addr)
sk.close()
9.2黏包
基于tcp协议实现的黏包
#sever
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
conn, addr = sk.accept()
while True:
cmd = input('>>>')
conn.send(cmd.encode('utf-8'))
ret = conn.recv(1024).decode('utf-8')
print(ret)
conn.close()
sk.close()
#client
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
while True:
cmd = sk.recv(1024).decode('gbk')
ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = 'stdout:' + (ret.stdout.read()).decode('gbk')
stderr = 'stderr:' + (ret.stderr.read()).decode('gbk')
sk.send(stdout.encode('utf-8'))
sk.send(stderr.encode('utf-8'))
sk.close()
基于udp协议实现的黏包
#sever
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 8090))
msg, addr = sk.recvfrom(1024)
while True:
cmd = input('>>>')
if cmd == 'q':
break
sk.sendto(cmd.encode('utf-8'), addr)
msg, addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.close()
#client
import socket
import subprocess
sk = socket.socket(type=socket.SOCK_DGRAM)
addr = ('127.0.0.1', 8090)
sk.sendto(b'hh', addr)
while True:
cmd, addr = sk.recvfrom(1024)
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = 'stdout:' + (ret.stdout.read()).decode('gbk')
stderr = 'stderr:' + (ret.stderr.read()).decode('gbk')
sk.sendto(stdout.encode('utf-8'),addr)
sk.sendto(stderr.encode('utf-8'),addr)
注意:只有TCP有粘包现象,UDP永远不会粘包
9.3使用struct解决黏包
该模块可以把一个类型,如数字,转成固定长度的bytes
借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。
定制报头进行上传下载
#sever
import json
import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
conn, addr = sk.accept()
dic_len = conn.recv(4) # 4个字节 数字的大小
dic_len = struct.unpack('i', dic_len)[0]
content = conn.recv(dic_len).decode('utf-8') # 70
content_dic = json.loads(content)
if content_dic['operate'] == 'upload':
with open(content_dic['filename'], 'wb') as f:
while content_dic['filesize']:
file = conn.recv(1024)
f.write(file)
content_dic['filesize'] -= len(file)
conn.close()
sk.close()
#client
import os
import json
import struct
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
def get_filename(file_path):
filename = os.path.basename(file_path)
return filename
#选择 操作
operate = ['upload','download']
for num, opt in enumerate(operate, 1):
print(num, opt)
num = int(input('请输入您要做的操作序号 : '))
if num == 1:
'''上传操作'''
file_path = input('请输入要上传的文件路径 : ')
file_size = os.path.getsize(file_path) # 获取文件大小
file_name = get_filename(file_path)
dic = {'operate': 'upload', 'filename': file_name, 'filesize':file_size}
str_dic = json.dumps(dic).encode('utf-8')
ret = struct.pack('i', len(str_dic)) # 将字典的大小转换成一个定长(4)的bytes
sk.send(ret + str_dic)
with open(file_path, 'rb') as f:
while file_size:
content = f.read(1024)
sk.send(content)
file_size -= len(content)
elif num == 2:
'''下载操作'''
sk.close()
9.4检验客户端的合法性
利用hmac模块进行加密
#sever
import os
import socket
import hmac
def check_client(conn):
secret_key = b'aa' # 密钥
send_str = os.urandom(32)
conn.send(send_str)
hmac_obj = hmac.new(secret_key,send_str)
secret_ret = hmac_obj.digest() #bytes类型
if conn.recv(1024) == secret_ret:
print('合法的客户端')
return True
else:
print('非法的客户端')
return False
sk = socket.socket()
sk.bind(('127.0.0.1',8090))
sk.listen()
conn,addr = sk.accept()
ret = check_client(conn)
while ret:
inp = input('>>>')
conn.send(inp.encode('utf-8'))
msg = conn.recv(1024)
print(msg.decode('utf-8'))
conn.close()
sk.close()
#client
import socket
import hmac
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
recv = sk.recv(1024)
# 用和server端相同的手法对这个字符串进行摘要
secret_key = b'aa' # 密钥
hmac_obj = hmac.new(secret_key,recv)
ret = hmac_obj.digest()
sk.send(ret)
msg = sk.recv(1024)
if msg:
print(msg.decode('utf-8'))
while True:
inp = input('>>>')
sk.send(inp.encode('utf-8'))
msg = sk.recv(1024)
print(msg.decode('utf-8'))
sk.close()
9.5socketserver
#sever
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
while 1:
msg = self.request.recv(1024).decode('utf-8')
print(msg)
info = input('>>>')
self.request.send(info.encode('utf-8'))
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000), MyServer)
server.serve_forever()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while 1:
msg = input('>>>').encode('utf-8')
if msg == 'q':
break
sk.send(msg)
ret = sk.recv(1024).decode('utf-8')
print(ret)
sk.close()