tcp和udp的区别:https://www.jianshu.com/p/c63b082ac565
1.基于tcp
# 服务端 import socket
# 创建服务端socket对象
server = socket.socket()
# 绑定IP和端口
server.bind(('192.168.3.6',8000))
# 后边可以等5个人
server.listen(5)
print('服务端准备开始接收客户端的连接')
# 等待客户端来连接,如果没人来就傻傻的等待。
# conn是客户端和服务端连接的对象(伞),服务端以后要通过该对象进行收发数据。
# addr是客户端的地址信息。
# #### 阻塞,只有有客户端进行连接,则获取客户端连接然后开始进行通信。
conn,addr = server.accept()
print('已经有人连接上了,客户端信息:',conn,addr)
# 通过对象去获取(客户端通过伞给我发送的消息)
# 1024表示:服务端获取数据时,一次性最多拿1024字节。
data = conn.recv(1024)
print('已经有人发来消息了',data)
# 服务端通过连接对象给客户端回复了一个消息。
conn.send(b'stop')
# 与客户端断开连接
conn.close()
# 关闭服务端的服务
server.close()
#客户端
import socket
client = socket.socket()
# 客户端向服务端发起连接请求(递伞)
# 阻塞,去连接,直到连接成功后才会继续向下走。
client.connect(('192.168.3.6',8000))
# # 链接上服务端后,向服务端发送消息
client.send(b'hhhhhhhhhhhh')
# 等待服务端给他发送消息
data = client.recv(1024)
print(data)
# 关闭自己
client.close()
1.1 while版
#服务端
import socket
server = socket.socket()
server.bind(('192.168.3.6',8001))
server.listen(5)
while True:
conn,addr = server.accept()
# 字节类型
while True:
data = conn.recv(1024) # 阻塞
if data == b'exit':
break
data1=' 梦想成真'
data1=data1.encode("utf8")
response = data +data1
conn.send(response)
conn.close()
#客户端
import socket
sk = socket.socket()
sk.connect(('192.168.3.6',8001))
while True:
name = input("请输入姓名:")
sk.send(name.encode('utf-8')) # 字节
if name == 'exit':
break
response = sk.recv(1024) # 字节
print(response.decode('utf-8'))
sk.close()
2.基于udp
#服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM) # 建立一个socket对象,
# 指定以UDP协议的形式来连接
sk.bind(('127.0.0.1',8080))
# 指定服务的地址
msg,addr = sk.recvfrom(1024) # 接收消息,发送端的地址
print(msg,addr)
sk.sendto(b'HELLO',addr) # 给发送端回复消息
sk.close() # 关闭socket连接
#客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.sendto(b'hello',('127.0.0.1',8080)) # 直接给服务器发送一段消息
msg,addr = sk.recvfrom(1024) # 接收对面的回信
print(msg)
sk.close()
2.1 udp聊天小工具
#udp聊天小工具
#服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9090))
while True:
msg,addr = sk.recvfrom(1024)
print('来自[%s:%s]的消息--%s'%(addr[0],addr[1],msg.decode('utf-8')))
inp = input('>>>')
sk.sendto(inp.encode('utf-8'),addr)
sk.close()
#客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
addr = ('127.0.0.1',9090)
while True:
msg = input('>>>')
sk.sendto(msg.encode('utf-8'),addr)
msg_recv,addr = sk.recvfrom(1024)
print(msg_recv.decode('utf-8'))
sk.close()
2.2 时间服务器
#时间服务器
#服务端
# 需求
# 写一个时间同步的服务器
# 服务端接收请求
# 按照client端发送的时间格式,将服务器时间转换成对应格式
# 发送给客户端
import time
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9000))
while True:
msg,addr = sk.recvfrom(1024)
# msg 客户端发送给server端的时间格式 "%Y-%m-%d %H:%M:%S"
time_format = msg.decode('utf-8')
time_str = time.strftime(time_format)
sk.sendto(time_str.encode('utf-8'),addr)
sk.close()
#客户端
# client端每隔一段时间发送请求到服务端
# 发送时间的格式
import time
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.sendto('%Y-%m-%d %H:%M:%S'.encode('utf-8'),('127.0.0.1',9000))
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.close()
3.上传和下载
3.1 知识补充
# JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式 # 方法 描述 # json.dumps() 将 Python 对象编码成 JSON 字符串 # json.loads() 将已编码的 JSON 字符串解码为 Python 对象 # json.dump() 将Python内置类型序列化为json对象后写入文件 # json.load() 读取文件中json形式的字符串元素转化为Python类型
#服务端
import json
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
content = conn.recv(1024).decode('utf-8')
content_dic = json.loads(content) # json.dumps()函数的使用,将字典转化为字符串 dumps(丢弃)
# json.loads函数的使用,将字符串转化为字典 loads(容器)
if content_dic['operate'] == 'upload':
conn.send(b'received!')
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()
# 客户端
import os
import json
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): # 看下面enumerate函数的用法
print(num, opt)
num = int(input('请输入您要做的操作序号 : '))
if num == 1:
'''上传操作'''
# file_path = 'E:\python10\day33\作业.py'
file_path = input('请输入要上传的文件路径 : ')
# 告诉对方要上传的文件的名字
file_name = get_filename(file_path)
# 读要上传的文件 存成字符串
with open(file_path, encoding='utf-8') as f:
content = f.read() # content(内容)
dic = {'operate': 'upload', 'filename': file_name, 'content': content}
# 将字符串send给server端
str_dic = json.dumps(dic)
sk.send(str_dic.encode('utf-8'))
# server端接收 bytes转码程字符串
# server端打开文件 写文件
elif num == 2:
'''下载操作'''
sk.close()
# 客户端结果
# 1 upload
# 2 download
# 请输入您要做的操作序号 :
# ===========================================================
# enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)
# 组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
list(enumerate(seasons, start=1)) # 下标从 1 开始
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
4.远程执行系统命令
4.1 基于tcp
#基于tcp
#服务端(服务端向客户端远程执行命令)
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8090))
sk.listen()
conn,addr = sk.accept()
while True:
cmd = input('cmd : ')
if cmd == 'q':
conn.send(cmd.encode('utf-8'))
break
conn.send(cmd.encode('utf-8'))
print('stdout : ',conn.recv(1024).decode('gbk'))
conn.close()
sk.close()
#客户端
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
while True:
cmd = sk.recv(1024).decode('utf-8')
if cmd == 'q': break
res = subprocess.Popen(cmd,shell=True, #管道的数据只能取一次
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
sk.send(res.stdout.read())
sk.send(res.stderr.read())
sk.close()
4.1 基于udp
#基于udp的远程执行系统命令
#服务端
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('cmd : ')
if cmd == 'q':
sk.sendto(cmd.encode('utf-8'),addr)
break
sk.sendto(cmd.encode('utf-8'),addr)
print('stdout : ',sk.recvfrom(2048)[0].decode('gbk')) #接收UDP数据,与recv()类似,
#但返回值是(data,address)。
#其中data是包含接收数据的字符串,
#address是发送数据的套接字地址。
print('stderr : ',sk.recvfrom(2048)[0].decode('gbk'))
sk.close()
#客户端
import socket
import subprocess
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.sendto(b'111',('127.0.0.1',8090))
while True:
cmd = sk.recvfrom(1024)[0].decode('utf-8')
if cmd == 'q': break
res = subprocess.Popen(cmd,shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
sk.sendto(res.stdout.read()*100,('127.0.0.1',8090))
sk.sendto(res.stderr.read(),('127.0.0.1',8090))
sk.close()
5.黏包
5.1 黏包的产生
#连续两个send()会出现黏包
#连续两个recv(),第一个recv()接收很少的数据
#没接收完的数据会在内存缓存着
#不能一次性把很多数据放到内存
#一般一次性传4096字节
#大文件传输
#大文件的传输,一定是按照字节读,每一次读固定字节
#传输过程中,一边读一边传,接收端一边收一边写
5.2 接收发的缓存引起的黏包
#服务端
from socket import * #导入包,导入模块只需import+模块即可
ip_port=('127.0.0.1',8080)
tcp_socket_server=socket()
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)
conn,addr=tcp_socket_server.accept()
data1=conn.recv(2)
data2=conn.recv(10)
print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))
import time
# time.sleep(1)
data2=conn.recv(10)
print('----->',data2.decode('utf-8'))
conn.close()
tcp_socket_server.close()
#服务端结果
#-----> he
#-----> lloegg
#----->
#客户端
import time
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)
s.send('hello'.encode('utf-8'))
# time.sleep(1)
s.send('egg'.encode('utf-8'))
5.3 发送发的缓存引起的黏包
#服务端
from socket import *
ip_port=('127.0.0.1',8080)
tcp_socket_server=socket()
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)
conn,addr=tcp_socket_server.accept()
data1=conn.recv(10)
data2=conn.recv(10)
print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))
conn.close()
#服务端结果
#-----> helloegg
#----->
#客户端
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)
s.send('hello'.encode('utf-8'))
s.send('egg'.encode('utf-8'))
6.黏包问题解决方案
#为什么会出现黏包现象???
#只有在tcp协议中才会出现黏包现象,是因为tcp是面向流的
#tcp协议在发送数据的过程中还有缓存机制来避免数据丢失
#在连续发送小数据的时候,以及接收大小不符合的时候容易出现黏包现象
#本质还是因为在接收数据的时候不知道发送数据的大小
#解决黏包的问题
#不用struct
#不要连续发或者连续收,发一次收一次
#用struct
#先发送数据长度,转换成四个字节,再发数据,接收方先接收4个字节知道长度再接数据
#struct模块
#struct.pack("i",4096) i是int,就是把一个数字转换成固定长度的bytes类型
#函数 return explain
#pack(fmt,v1,v2…) string 按照给定的格式(fmt),把数据转换成字符串(字节流),并将该字符串返回.
#unpack(fmt,v1,v2…) tuple 按照给定的格式(fmt)解析字节流,并返回解析结果
#服务端
#用的上传和下载的代码
#现在不只是发数据长度,还发文件路径,文件名,文件大小
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] # [0]拿到之前打包的数字
content = conn.recv(dic_len).decode('utf-8') # 根据解包的数字拿到报头,报头和字节数字一起发的,
# 也可以和数据一起发,报头中有数据size
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()
#客户端
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()
7.验证客户端连接的合法性(hashlib)
# 服务端
import os
import socket
import hashlib
def check_client(conn):
secret_key = b'egg' # 密钥
send_str = os.urandom(32) # 随机生成32位的字节
# random() 方法返回随机生成的一个实数,它在[0,1)范围内
conn.send(send_str)
md5_obj = hashlib.md5(secret_key)
md5_obj.update(send_str) # 把send_str更新到md5_obj里面
secret_ret = md5_obj.hexdigest() # hash.digest() 返回摘要,作为二进制数据字符串值
# hash.hexdigest() 返回摘要,作为十六进制数据字符串值
if conn.recv(1024).decode('utf-8') == 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()
#客户端
import socket
import hashlib
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
recv = sk.recv(1024) #接收到的是服务端随机生成的32位字节
# 用和server端相同的手法对这个字符串进行摘要
secret_key = b'egon' # 密钥和服务端相同才是合法的客户端
md5_obj = hashlib.md5(secret_key)
md5_obj.update(recv)
ret = md5_obj.hexdigest()
sk.send(ret.encode('utf-8'))
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()
8.验证客户端连接的合法性(hmac)
#服务端
import os
import socket
import hmac
def check_client(conn):
secret_key = b'egg' # 密钥
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()
#客户端
import socket
import hmac
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
recv = sk.recv(1024)
# 用和server端相同的手法对这个字符串进行摘要
secret_key = b'egg' # 密钥
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.socketserver
# socketserver的介绍
# socketserver是标准库中的一个高级模块
# socketserver可以简化创建客户端跟创建服务端的代码
导入模块 import socketserver
# 初始化控制器类Handler-->Handler是一个继承BaseRequestHandler的类Handler中的
# handle方法决定了每一个连接过来的操作
# 控制器类的类名可以是其他的,不一定是Handler,只要继承了BaseRequestHandler就行
init(): # 初始化控制设置,初始化连接套接字,地址,处理实例等信息
handle():# 定义了如何处理每一个连接
setup(): # 在handle()之前执行,一般用作设置默认之外的连接配置
finish():# 在handle()之后执行
#变量:
self.request # 属性是套接字对象,所以使用self.request.xxxx调用套接字的函数
self.server # 包含调用处理程序的实例
self.client_address # 是客户端地址信息
#服务端
import socketserver
class Myserver(socketserver.BaseRequestHandler):
def handle(self):
# 字节类型
while 1:
# 针对window系统
try:
print("等待信息")
data = self.request.recv(1024) # 阻塞
# 针对linux
if len(data) == 0:
break
if data == b'exit':
break
response = data + b'SB'
self.request.send(response)
except Exception as e:
break
self.request.close()
# 1 创建socket对象 2 self.socket.bind() 3 self.socket.listen(5)
server=socketserver.ForkingUDPServer(("127.0.0.1",8899),Myserver)
server.serve_forever()
#============================================================
#客户端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8899))
while 1:
name = input(">>>>:")
sk.send(name.encode('utf-8')) # 字节
response = sk.recv(1024) # 字节
print(response.decode('utf-8'))
来源:https://www.cnblogs.com/yzg-14/p/12204038.html