1、socket概念
图片位置
2、理解socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
2.1站在人的角度看待socket模块
socket就是一个模块。通过调用模块中已经实现的方法建立两个进程之间的连接和通信。要确定一个计算机要有ip地址和port端口,因此socket也可以说成ip+port。
因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。
3、套接字(socket)的发展史
基于文件型的和基于网络型
3.1基于文件类型的套接字家族:AF_UNIX
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
3.2基于网络类型的套接字家族名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
4、socket通信代码
#服务端 import socket server = socket.socket() server.bind(('127.0.0.1',8080))# server.listen(5) conn, addr = server.accept() data = conn.recv(1024) print(data) conn.send(b'hello boby~') conn.close() server.close()
#客服端 import socket client = socket.socket() client.connect(('127.0.0.1',8080)) client.send(b'hello world') data = client.recv(1024) print(data) client.close()
res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码
且只能从管道里读一次结果,PIPE称为管道
现在,多打印了一行stuout.read().decode('gbk'),蓝色箭头显示多了一空行
4、通信循环
服务端:
import socket server = socket.socket() server.bind(('127.0.0.1',8080))#bangding yige jiekou server.listen(5)#半连接 conn ,addr =server.accept() #循环对话 while True: data = conn.recv(1024)#接收数据 print(data) conn.send(data.upper())#将接收的数据变成大写
客户端:
import socket client = socket.socket()#默认tcp client.connect(('127.0.0.1',8080))#与哪个服务端连接 while True: msg = input('msg:>>>').encode('utf-8') #先写send与recv取决于服务端先写的什么 client.send(msg) data = client.recv(1024) print(data)
服务端断开报错:
Traceback (most recent call last): File "C:/untitled5/服务端.py", line 11, in <module> data = conn.recv(1024)#接收数据 ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。
该行报错conn
#conn类似于双向通道。与客户端的双向通道,现在客户端断开,因此拿着一个失效的通道,在接受值,因此报错
但我们不能控制用户的操作。只能在代码层面解决问题。
此时我们知道1、错误的类型,但不知道2、错误合适发生————异常处理
while True: try: data = conn.recv(1024)#接收数据 print(data) conn.send(data.upper())#将接收的数据变成大写 except ConnectionResetError as m : print(m)
此时一直打印报错信息、
[WinError 10054] 远程主机强迫关闭了一个现有的连接。 [WinError 10054] 远程主机强迫关闭了一个现有的连接。 [WinError 10054] 远程主机强迫关闭了一个现有的连接。 [WinError 10054] 远程主机强迫关闭了一个现有的连接。 [WinError 10054] 远程主机强迫关闭了一个现有的连接。
应该break和conn.close().完美结果报错
while True: try: data = conn.recv(1024)#接收数据 print(data) conn.send(data.upper())#将接收的数据变成大写 except ConnectionResetError as m : print(m) break conn.close()
addr为客户端的接口。
解决等待(什么都不输入):
#加入判断 while True: msg = input('msg:>>>').encode('utf-8') if len(msg)==0: continue #先写send与recv取决于服务端先写的什么 client.send(msg) data = client.recv(1024) print(data)
引出问题:服务端是24小时不停服务!以及固定的ip和端口。现在写的服务端只满足第二点
如何同时满足这两个要求?
我们应该在客户端断开之后,继续跑到门口接客!即把就得conn双向连接关闭,建立新的双向连接
#这又是一个循环 while True: conn ,addr =server.accept() # print(addr) while True: try: data = conn.recv(1024)#接收数据 print(data) conn.send(data.upper())#将接收的数据变成大写 except ConnectionResetError as m : print(m) break conn.close()
现在产生一个状态,开多个客户端,服务端始终只会和第一个客户交互,除非第一个客户端停止,才继续跟下一个客户交互!
半连接池的概念
server.listen(5),允许最大等待数。有一个小房间正在干活,外面还等5个
subproccess模块回顾
import subprocess cmd = input('请输入指令>>>') res = subprocess.Popen( cmd, #字符串指令:'dir','ipconfig',等等 shell=True, #使用shell,就相当于使用cmd窗口 stderr=subprocess.PIPE, #标准错误输出,凡是输入错误指令,错误指令输出的报错信息就会被它拿到 stdout=subprocess.PIPE, #标准输出,正确指令的输出结果被它拿到 ) print(res.stdout.read().decode('gbk')) print(res.stderr.read().decode('gbk'))
只能从管道里读一次结果,PIPE称为管道
TCP粘包问题:
数据取不干净
TCP的一个特点:
会将数据量比较小的并且时间间隔比较短的数据
一次性打包发送给对方
若将recv:接收容量增大,可能会报错!因为recv是和内存要数据!
客户端‘三合一’发送给“服务端”,前提是服务端的半连接池容量够。节省资源
产生粘包的原因:
是因为conn.recv()的容量不知道该设置成多大;不知道数据多大。盲目的输入1024。
解决粘包
recv设置好每一个数据的大小。需要一个东西来告诉我们这个数据有多大,但我们也不知道这个人有多大!
如何能将收干净:思路找一个人告诉我这个数据多大!
找一个探字,如果探子的长度是固定的,比如4b,6bytes。那么conn.recv(4)就确定了,那么问题就解决了!
如何固定探字长度?
引出struct的概念
将探子的长度固定(struct模块打包数据):
struct模块如何使用?
import struct res= '23werfgfreghfcgfrefghffewgsfbg' res1= struct.pack('i',len(res)) print(len(res1)) #此时无论数据多大,打包之后都是4!
此时无论数据多大,打包之后都是4!
现在有一个问题:如何取回被打包的数据?(解包)
res2 = strcut.unpack('i',res1)[0] #res2为真实数据的长度
关于struct模式
即使是最大的一个模式,数据长度如果太大,也接收不了,那如何解决呢?
res1 = struct.pack('q',1232323234234312444) #报错: #struct.error: argument out of range
模拟通信最完整版
#客户端 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'))
#服务端 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()