socket套接字编程
服务端&客户端流程
TCP服务端&客户端示例代码:
# 服务端
import socket,threading
def jieshou(a,b):
print(f'建立连接{a}{b}')
while True:
c = a.recv(1024) # 接收消息
if c:
shuju = c.decode('gbk') # 解码
print(f'{b}数据:{shuju}')
else:
break
a.close()
if __name__ == '__main__':
tcp = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True) # 设置端口号复用
tcp.bind(("",9090)) # 绑定地址,端口号
tcp.listen(128) # 设置监听
while True:
a, b = tcp.accept()
thread1 = threading.Thread(target=jieshou,args=(a,b)) # 创建子线程
thread1.daemon = True # 设置为守护线程
thread1.start() # 执行
tcp.close()
# 客户端
import socket
if __name__ == '__main__':
my_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
my_socket.connect(("172.40.91.185",9090)) # 连接服务端
my_socket.send('Hello'.encode('gbk')) # 发送消息
my_socket.close()
运行结果:
客户端:
服务端:
UDP客户端&服务端示例:
# 客户端
import socket
udp = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 创建套接字
udp.sendto('Hello'.encode('gbk'),("172.40.91.185",10000)) # 发送消息
udp.close()
# 服务端
import socket
'''
UDP不用连接,直接接收数据
'''
if __name__ == '__main__':
udp = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 创建套接字
udp.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True) # 设置端口号复用
udp.bind(("",10000)) # 绑定地址
while True:
data, addr = udp.recvfrom(2048) # 接收消息
if not data:
break
print(f'来自{addr}的数据:{data}')
udp.close()
运行结果:
客户端:
服务端:
解析
1.创建套接字
sockfd = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM,proto=0,fileno=None)
功能:创建套接字
参数:
family -> 网络地址类型: AF_INET_表示_IPv4、 AF_INET6_表示_IPv6
type -> 套接字类型: SOCK_STREA(数据流,基于TCP)、 SOCK_DGRAM(数据包,基于UDP)
_proto -> _通常为0, 选择子协议
返回值: 套接字对象
2.设置端口号复用
sockfd.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
3.绑定地址
本地地址:'localhost', '127.0.0.1'
网络地址:'172.40.91.185'
自动获取地址:'0.0.0.0'
sockfd.bind(("",9090)) # bind((IP地址,端口号))
4.设置监听(TCP)
sockfd.listen(n)
功能: 将套接字设置为监听套接字,确定监听队列大小
参数: 监听队列大小(没什么用,摆设而已,一般会开多线程连接)
5.等待处理客户端连接请求(TCP)
connfd, addr = sockfd.accept()
功能: 阻塞等待处理客户端请求
返回值: 元组
_connfd -> _ 客户端连接套接字
_addr -> _ 连接的客户端地址
6.连接服务端(TCP)
sockfd.connect((IP地址,端口号))
功能: 连接指定的服务端
7.收发消息
TCP版: data = connfd.recv(buffersize)
UDP版: data = connfd.recvfrom(buffersize)
功能: 接收消息, 如果一方断开连接,则会返回空字符串
参数: 每次传输的数据大小
TCP粘包
原因: TCP以字节流方式传输,没有边界消息。多次发送的消息被一次接收,此时就会形成粘包
影响: 如果每次发送的内容是一个独立含义,需要接收端独立解析,此时粘包会有影响
处理方法
1. 人为的添加消息边界
2. 控制发送速度
3. 使用struct
import struct
struct.pack(format,v)
功能:打包字节对象(bytes)
参数:format 格式化字符 'i' int; 'q' long long
返回:bytes
实例:文件传输助手
# 发送端
import socket,os
from struct import pack
def send_file(file_name,file_socket:socket.socket):
try:
f = open(file_name,'rb')
size = os.path.getsize(file_name)
if size < 1024:
read_size = 500
elif size < 1024*1024 and size >= 1024:
read_size = 500*1024
else:
read_size = 500*1024*1024
file_socket.send(pack('q',size))
del size
file_socket.recv(1024)
while True:
data = f.read(read_size)
if not data:
break
file_socket.send(data)
f.close()
except FileNotFoundError:
print(f'没有找到{file_name}')
if __name__ == '__main__':
try:
file_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
file_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
ip = str(input('请输入接收端IP:'))
port = int(input('请输入接收端端口号:'))
file_socket.connect((ip,port))
print('连接成功,准备开始传输。。。')
path = r'%s' % (input('请输入文件所在文件夹:') + '/')
print(file_socket.recv(1024).decode('gbk'))
file_name = str(input())
file_socket.send(file_name.encode('gbk'))
send_file(path + file_name,file_socket)
except ConnectionResetError:
print('接收端断开连接')
# 接收端
import socket,os
from time import time
from struct import unpack
from tqdm import tqdm
DOWNLOAD_PATH = 'D:/Python文件传输/' # 传输目录
def download(file_name,file_socket:socket.socket):
file_size = unpack('q',file_socket.recv(1024))[0]
if file_size < 1024:
print(f'文件大小:{file_size} B')
elif file_size < 1024*1024 and file_size >= 1024:
print(f'文件大小:{file_size/1024} KB')
else:
print(f'文件大小:{file_size/(1024*1024)} MB')
f = open(file_name,'wb')
print('开始传输...')
download_size = 2048
file_socket.send('开始传输'.encode('gbk'))
start = time()
for i in tqdm(range(int(file_size/download_size) + 1)):
data = file_socket.recv(download_size)
f.write(data)
end = time()
f.close()
print('传输完成!')
print(f'大约耗时{end - start}秒')
if __name__ == '__main__':
os.chdir(DOWNLOAD_PATH)
try:
file_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
file_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
file_socket_port = int(input('请输入端口号:'))
file_socket.bind(('',file_socket_port))
print('成功启动,等待连接。。。')
file_socket.listen(1)
f_socket, f_addr = file_socket.accept()
print(f'建立连接{f_addr}')
f_socket.send('请输入文件名'.encode('gbk'))
file_name = f_socket.recv(1024)
download(file_name.decode('gbk'),f_socket)
f_socket.close()
file_socket.close()
except ConnectionResetError:
print('发送端已断开连接')
except UnicodeDecodeError:
print('文件编码错误,请检查文件格式是否正确')