Socket 层概念
理解socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
TCP 协议下的 Socket 通信
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
客户端
import socket client = socket.socket() # 以默认的TCP协议建立双向通道 client.connect(('127.0.0.1', 8080)) # 服务端的ip 和 port client.send(b'hello') # 向服务端发送信息,二进制格式 data = client.recv(1024) # 接收服务端传来的信息 print(data) client.close() # 关闭服务端
服务端
import socket server = socket.socket() # 实例化socket对象,不传参数默认TCP协议 server.bind(('127.0.0.1', 8080)) # bind((host, prot)) 绑定端口和协议 server.listen() # conn, addr = server.accept() # 等待接听信息, conn:双向传输通道 addr:客户端地址 data = conn.recv(1024) # 将客户端传输过来的内容赋值给data 接受1024个字节数据 print(data) conn.send(b'hello word') # 向客户端传输内容,只能是二进制格式 conn.close() # 关闭通道 server.close() # 关闭服务端
连接循环+通信循环
客户端
import socket client = socket.socket() client.connect(('127.0.0.1', 8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) data = client.recv(1027) print(data)
服务端
import socket ''' 服务端有固定的ip和port,要24小时不间断服务客户端 ''' server = socket.socket() # 生成一个对象 server.bind(('127.0.0.1', 8080)) # 绑定ip和port server.listen(5) # 半连接池 while True: # 连接循环 conn, addr = server.accept() # 当一个客户端断开通道后,等待下一个客户端的连接 while True: # 通信循环 try: data = conn.recv(1024) print(data) # mac与linux 客户端异常退出后,服务端并不会报错,会循环打印 b'' if len(data) == 0:break # 用户兼容mac与linux conn.send(data.upper()) # 将数据大写发送给客户端 except ConnectionAbortedError: # 捕捉 客户端异常退出 的错误信息 break # 当客户端异常退出后结束通信循环 conn.close() # 当客户端异常退出后关闭通道,进入下一次连接循环
struct模块
struct模块的作用是将数据长度转换成固定长度的内容
''' struct模块的作用是将数据长度转换成固定长度的内容 ''' import struct res = 'asdfghjkl' print('装包前长度', len(res)) # >>> 装包前长度 9 # 装包 成固定长度为4 res1 = struct.pack('i', len(res)) print('装包后长度', len(res1)) # >>> 装包后长度 4 # 解包 res2 = struct.unpack('i', res1)[0] print('解包后长度', res2) # 解包后长度 9 d = { 'name':'waller', 'file_size': 33335555555444444446666666, 's':1 } print(len(d)) # 字典键值对个数 import json d_size = json.dumps(d) print(len(d_size)) # 字典转字符转后的字符个数 # 装包 报头 成固定长度为4 msg1 = struct.pack('i', len(d_size)) print(msg1) # >>> b'Cx00x00x00' print(len(msg1)) # 解包 报头 获得原长度 msg2 = struct.unpack('i', msg1)[0] print(msg2)
解决粘包问题
服务端
1.先制作一个发送给客户端的字典
2.制作字典的报头
3.发送字典的报头
4.发送字典
5.再发真实数据
客户端
1.先接受字典的报头
2.解析拿到字典的数据长度
3.接受字典
4.从字典中获取真实数据的长度
5.接受真实数据
服务端
import socket import subprocess import struct import json server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() while True: try: cmd = conn.recv(1024) if len(cmd) == 0:break cmd = cmd.decode('utf-8') obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) res = obj.stdout.read() + obj.stderr.read() print(res) # res 是二进制格式 d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'} json_d = json.dumps(d) # 将字典序列化成字符串,便于编码传输 # 1.先制作一个字典的报头 header = struct.pack('i',len(json_d)) # 报头自动被编码成二进制 print(header) # >>> b'<x00x00x00' # 2.发送字典报头 conn.send(header) # 3.发送字典 conn.send(json_d.encode('utf-8')) # 将字典编码发送 # 4.再发真实数据 conn.send(res) # conn.send(obj.stdout.read()) # conn.send(obj.stderr.read()) except ConnectionResetError as e: print(e) break conn.close()
客户端
import socket import struct import json client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) # 1.先接受字典报头 header_dict = client.recv(4) # 接收的报头是二进制格式 # 2.解析报头 获取字典的长度 dict_size = struct.unpack('i',header_dict)[0] # 解包的时候一定要加上索引0 print(dict_size) # 3.接收字典数据 dict_bytes = client.recv(dict_size) # 按照字典的长度接收字典 dict_json = json.loads(dict_bytes.decode('utf-8')) # 将字典反序列化并解码出来 print(dict_json) # 4.从字典中获取信息 recv_size = 0 real_data = b'' # 初始化二进制 while recv_size < dict_json.get('file_size'): # dict_json.get('file_size') = len(res) data = client.recv(1024) # 接收1024个字节 real_data += data # 每读取一次二进制数据拼接一次 recv_size += len(data) # 每次读取的长度相加,当总长度和len(res)向同时,结束 print(real_data.decode('gbk')) # 将读取的二进制数据解码出来
UDP协议下的 socket 通信
UDP通信
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接
数据报协议(自带报头)
没有双向通道 通信类似于发短信
1.udp协议客户端允许发空
2.udp协议不会粘包
3.udp协议服务端不存在的情况下,客户端不会报错
4.udp协议支持并发
基本使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket # 创建套接字对象 UDP协议 s = socket.socket(type=socket.SOCK_DGRAM) # 绑定ip+port s.bind(('127.0.0.1', 8080)) # UDP 协议不需要双向通道, 所以不需要accept 直接进入通信循环 while True: # 接收客户端信息和地址 data, addr = s.recvfrom(1024) print('客户端发来的信息', data.decode('utf-8')) print('客户端发来的地址', addr) # 将客户端发来的信息大写后发给客户端 s.sendto(data.upper(), addr)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket # 创建套接字对象 修改为UDP协议 c = socket.socket(type=socket.SOCK_DGRAM) # 服务端ip+port s_addr = ('127.0.0.1', 8080) # UDP协议通信部需要创建通道连接 直接进入通信循环 while True: # 向指定的服务端发送信息 c.sendto(b'hello', s_addr) # 接收服务端信息 解压赋值 msg, addr = c.recvfrom(1024) print('服务端发来的数据', msg.decode('utf-8')) print('服务端发来的地址', addr)
图解
socketserver 模块
服务端
import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): # print('来啦 老弟') while True: data = self.request.recv(1024) print(self.client_address) # 客户端地址 print(data.decode('utf-8')) self.request.send(data.upper()) if __name__ == '__main__': """只要有客户端连接 会自动交给自定义类中的handle方法去处理""" server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer) # 创建一个基于TCP的对象 server.serve_forever() # 启动该服务对象
客户端
import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: client.send(b'hello') data = client.recv(1024) print(data.decode('utf-8'))
文件上传实例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import os import struct import json # 创建套接字对象 c = socket.socket() # 默认TCP协议 # 连接服务端ip+port c.connect(('127.0.0.1', 8080)) # 通信循环 while True: file_path = r'D:OldBoy-py网路编程TCP协议socket套接字上传文件file_movie' file_list = os.listdir(file_path) for index, movie in enumerate(file_list): print(index+1, movie) choice = input('请选择要上传的视频>>>:').strip() if not choice.isdigit(): print('请输入数字') choice = int(choice) if not (choice > 0 and choice <= len(file_list)): print('请选择正确编号') movie_name = file_list[choice-1] # 获得文件路径 movie_path = os.path.join(file_path, movie_name) # 获得文件大小 movie_size = os.path.getsize(movie_path) # 制作文件信息字典 d = { 'name': '电影', 'file_size': movie_size } # 序列化字典 d_json = json.dumps(d) # 将序列化后的字典转为二进制类型 d_bytes = d_json.encode('utf-8') # 制作报头 header = struct.pack('i', len(d_bytes)) # 向服务端发送报头 c.send(header) # 向服务端发送字典 c.send(d_bytes) # 发送真实数据 读取文件信息,循环发送 with open(movie_path, 'rb') as f: for line in f: # 发送 c.send(line) # rb 模式读出就是二进制格式
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import struct import json s = socket.socket() # 绑定ip+port s.bind(('127.0.0.1', 8080)) # 监听 s.listen(5) # 连接循环 while True: # 建立双向通道 获取通道地址 conn, addr = s.accept() # 通信循环 while True: try: # 获取客户端信息(字典报头) header_len = conn.recv(4) # 解析报头 拆包 得到字典长度 d_len = struct.unpack('i', header_len)[0] # 按字典长度接收字典 d_json = conn.recv(d_len) # 解码字典 d_bytes = d_json.decode('utf-8') # 把字典反序列化 d = json.loads(d_bytes) # 获得真是数据长度 movie_size = d.get('file_size') # 循环接收真实数据并写入文件 start_size = 0 with open(d.get('name'), 'wb') as f: while start_size < movie_size: # 按1024字节接收数据 data = conn.recv(1024) # 写入文件 f.write(data) start_size += len(data) print('上传成功') except ConnectionResetError as e: print(e) break # 关闭通道 conn.close()
简易版QQ
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket c = socket.socket(type=socket.SOCK_DGRAM) s_d = ('127.0.0.1', 8080) while True: msg = input('>>>') c.sendto(msg.encode('utf-8'), s_d) data, addr = c.recvfrom(1024) print(data.decode('utf-8'))
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket s = socket.socket(type=socket.SOCK_DGRAM) s.bind(('127.0.0.1', 8080)) while True: data, addr = s.recvfrom(1024) print(data.decode('utf-8')) msg = input('>>>:') s.sendto(msg.encode('utf-8'), addr)