Socket概念
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
一、socker层 (在程序中就是一个模块功能可以直接导入使用)
Socker 是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,其实就i是一个门面模式,把复杂的协议放在socker后面。
IP地址: 127.0.0.1是本机回还地址,只能自己识别自己,其他人无法访问,用于python代码客户端和服务端的测试
二、 套接字(socker)的发展史
1:基于文件类型的套接字家族:AF_UNIX(一切皆文件)
2:基于网络类型的套接字家族:AF_INET(被用于ipv6)
三、tcp协议和udp协议
TCP:可靠的面向连接的协议(如:打电话),传输效率低于全双工通信
UDP:不可靠的、无连接的服务,传输效率高,一对一,一对多
TCP和TCP间的通信
基于TCP协议的socket
server端
import socket sk = socket.socket() sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字 sk.listen() #监听链接 conn,addr = sk.accept() #接受客户端链接 ret = conn.recv(1024) #接收客户端信息 print(ret) #打印客户端信息 conn.send(b'hi') #向客户端发送信息 conn.close() #关闭客户端套接字 sk.close() #关闭服务器套接字(可选)
client端
import socket sk = socket.socket() # 创建客户套接字 sk.connect(('127.0.0.1',8898)) # 尝试连接服务器 sk.send(b'hello!') ret = sk.recv(1024) # 对话(发送/接收) print(ret) sk.close() # 关闭客户套接字
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
基于UDP协议的socket
server端
import socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字 udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字 msg,addr = udp_sk.recvfrom(1024) print(msg) udp_sk.sendto(b'hi',addr) # 对话(接收与发送) udp_sk.close() # 关闭服务器套接字
client端
import socket ip_port=('127.0.0.1',9000) udp_sk=socket.socket(type=socket.SOCK_DGRAM) udp_sk.sendto(b'hello',ip_port) back_msg,addr=udp_sk.recvfrom(1024) print(back_msg.decode('utf-8'),addr)
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接
四、套接字(socker)的使用
client(客户端)关键字:connect send recv
server(服务端)关键字:bind listen accept recv send conn.slose()
TCP协议是基于连接的,必须先启动服务端,然后在启动客户端去连接服务端:
server:服务端
import socket """" 实现服务端24小时不间断的服务功能,固定的IP和port """ server = socket.socket() # 生成一个对象 server.bind(('127.0.0.1',8080)) # 绑定ip和port server.listen(5) # 半连接池 """ 用两层循环 实现客户端与服务端之间的循环交互式 """ while True: conn,addr = server.accept() # 循环接收用户端的请求 print(addr) while True: try:# 解决报错抛出的异常处理(位置,类型,) data = conn.recv(1024) print(data) if len(data) == 0 : break # 针对mac和 Linux 客户端异常退出之后 conn.send(data.upper()) # 返转成大写 except ConnectionRefusedError as e: # 提示的报错信息 print(e) break conn.close()
client:客户端
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(1024) print(data)
send(发出)与recv(接收)对应关系,不能出现两边都相同的情况
recv 是跟内存要数据的,
TCP的特点:
会将数据量比较小的并且时间间隔比较短的数据
一次性打包发送给对方
利用socker模块,用代码实现了服务端与客户端的实际交互情况,IP地址和pore端口地址必须完全匹配,其中注意一些概念 如 server.listen(5) # 半连接池 指的是规定客户端访问的量,报错的异常处理。
一个作为接收端,一个作为反馈请求端,所用的参数也不一样
五、黏包
会发生黏包的两种情况
情况一 发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
情况二 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
黏包:指的是当客户端同时请求所传输的内容过大,过长是,服务端反馈的结果可能只有其中的一部分,显示不全,在执行其他命令的时候又接受收到了之前执行的另外一部分的结果。
补充的subprocess子进程模块
import subprocess cmd = input('cmd>>>:') obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) print(obj.stdout.read().decode('gbk')) # 正确命令返回的结果 print(obj.stderr.read().decode('gbk')) # 错误的命令返回的结果 # subprocess获取到的数据 拿完就没有了 不能重复的拿 # print(obj.stdout.read().decode('gbk')) # 正确命令返回的结果 # print(obj.stderr.read().decode('gbk')) # 错误的命令返回的结果
只能单次获取请求的数据,取完就没了, 不能重复的取
该模块可以在python解释器里,实现终端的请求命令行执行并打印结果:
它的功能以及常用的操作
# subprocess模块 # 1.用户通过网络连接上了你的这台电脑 # 2.用户输入相应的命令 基于网络发送给了你这台电脑上某个程序 # 3.获取用户命令 里面subprocess执行该用户命令 # 4.将执行结果再基于网络发送给用户 # 这样就实现 用户远程操作你这台电脑的操作 # ''
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
struct.pack:打包
struct.unpack解包
有“i” ’q‘等模式,是处理数据大小的等级
dict_size = struct.unpack('i',header_dict)[0] # 解包的时候一定要加上索引0
服务端:结合json模块 dumps序列化成一个字典,制作一个报头
客户端:用loads把字典反序列化成字符串出来,读取报头内容
当有黏包现象存在时如何解决?(即数据过大过长时)
服务端client:
1:先制作一个发送给客户端色字典
2:制作字典的报头
3:发送字典的报头
4:再发真实的数据
客户端srever:
1.先接受字典的报头
2.解析拿到字典的数据长度
3.接受字典
4.从字典中获取真实数据的长度
5.接受真实数据
用字典打包好报头,获取固定的长度后在传输
内置函数构造:
简单黏包问题的存在,接收的数据和传出去的内容不一致导致。
client 客户端
import socket client = socket.socket() # 拿电话 client.connect(('127.0.0.1',8080)) # 拨号 写的是对方的ip和port client.send(b'hello') client.send(b'baby')
server:服务端
import socket server = socket.socket() # 买手机 不传参数默认用的就是TCP协议 server.bind(('127.0.0.1',8080)) # bind((host,port)) 插电话卡 绑定ip和端口 server.listen(5) # 开机 半连接池 conn, addr = server.accept() # 接听电话 等着别人给你打电话 阻塞 data = conn.recv(5) # 听别人说话 接收1024个字节数据 阻塞 print(data) data = conn.recv(5) # 听别人说话 接收1024个字节数据 阻塞 print(data)
注意:只有TCP有粘包现象,UDP永远不会粘包
实现解决黏包的问题?
客户端:client
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 # 3.接收字典数据 dict_bytes = client.recv(dict_size) dict_json = json.loads(dict_bytes.decode('utf-8')) # 4.从字典中获取信息 print(dict_json) recv_size = 0 real_data = b'' while recv_size < dict_json.get('file_size'): # real_size = 102400 data = client.recv(1024) real_data += data recv_size += len(data) print(real_data.decode('gbk')) """ 1.如何将对方发送的数据收干净
服务端:server
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() d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'} json_d = json.dumps(d) # 1.先制作一个字典的报头 header = struct.pack('i',len(json_d)) # 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: break conn.close()
文件的上传和下载
(重点掌握,也是基于tcp协议的一个粘包问题,利用打包和解包)
import socket import json import os import struct client= socket.socket() client.connect(('127.0.0.1',8080)) while True: MOVIE_DIR = r'E:\python脱产10期视频\day29\视频' movie_list= os.listdir(MOVIE_DIR) for i,movie in enumerate(movie_list,1): # 枚举列出所有数据 print(i,movie) # 用户选择 choice=input("请选择你要下载的视频>>:") if choice.isdigit(): choice = int(choice)-1 if choice in range(0,len(movie_list)): path = movie_list[choice] file_path = os.path.join(MOVIE_DIR,path) file_size = os.path.getsize(file_path) res_d={ 'file_name':"精彩视频要你好看.MP4", 'file_size':file_size, 'msg':"做个年少有为的青年" } json_d = json.dumps(res_d) json_bytes = json_d.encode('utf-8') header = struct.pack('i',len(json_bytes)) client.send(header) client.send(json_bytes) with open (file_path,'rb') as f: for line in f : client.send(line) else: print("not in range") else: print("must be a number") """ if else 的使用 先把正确逻辑的代码写好,后面再考虑搭配来写else的其他情况 """
import json import socket import struct server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn,addr = server.accept() while True: try: header_len = conn.recv(4) # 解析字典报头 header_len=struct.unpack('i',header_len)[0] # 再接收字典数据 header_dic = conn.recv(header_len) real_dic = json.loads(header_dic.decode('utf-8')) total_size = real_dic.get('file_size') recv_size = 0 with open (real_dic.get('file_name'),'wb') as f: while recv_size < total_size: data = conn.recv(1024) f.write(data) recv_size+= len(data) print("上传成功!") except ConnectionRefusedError as e: print(e) break conn.close()
简易版本的QQ实现:
QQ服务端:
import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('127.0.0.1',8080)) while True: data,adder = server.recvfrom(1024) msg= input(">>>>:") server.sendto(msg.encode('utf-8'),adder)
QQ客户端:
import socket client = socket.socket(type=socket.SOCK_DGRAM) server_address = ('127.0.0.1',8080) while True: msg = input('>>>:') msg = '来自客户端1的消息:%s'%msg client.sendto(msg.encode('utf-8'),server_address) data, server_addr = client.recvfrom(1024) print(data.decode('utf-8'))