socket套接字
socket是什么?
socket是应用层与TCP/IP协议族通信的中间的一个抽象层,它是一组接口,它把复杂的TCP/IP协议族隐藏在socket接口后面。
对于我们来说,socket就像是 一个模块,我们通过调用模块中已实现的方法建立两个进程之间的连接和通信。只需要遵循socket的规定去编程,我们写出的程序自然就是遵循tcp/udp标准的。
套接字的流程代码实现
#socket通信的代码实现 #server import socket server = socket.socket() # 不传参数默认是TCP协议 server.bind(('127.0.0.1',8080)) # 绑定ip和端口 server.listen() # 监听链接 conn,addr = server.accept() #等着接受客户端链接 data = conn.recv(1024) # 接受客户端信息,长度是1024个字节数据 print(data) # 打印客户端信息 conn.send(b'hello') # 向客户端发送信息 conn.close() # 关闭客户端套接字 server.close() # 关闭服务器套接字 #client import socket client = socket.socket() # 创建客户套接字 client.connect(('127.0.0.1',8080)) # 尝试连接服务器 client.send(b'hi') # 给服务端发送消息 data = client.recv(1024) # 对话(发送/接受) print(data) client.close() # 关闭客户套接字
注意
127.0.0.1是本机回还地址,只能自己识别自己,其他人是无法访问的。
send与recv对应,不要出现两边都是相同的情况。recv是跟内存要数据(数据的来源你无需考虑)。
TCP特点:会将数据量比较小的并且时间间隔比较短的数据一次性打包发送给对方。
#连接循环和通信循环 #server import socket server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) # 半连接池,就是在等待与服务端连接的客户端的数量 while True: # 循环接收地址,可以使一个客户端断开连接之后接待另一个客户端 conn,addr = server.accept() while True: # 循环执行消息的接收 try: data = conn.recv(1024) print(data) if len(data) == 0:break # 判断是不是接收到了空的值如果是则结束循环 conn.send(data.upper()) except ConnectionResetError as i: # 捕获当用客户端与服务端断开时出现的错误,然程序能继续运行。 print(i) 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)
from socket import SOL_SOCKET,SO_REUSEADDR sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 #重启服务端出现问题时添加这个
半连接池
1.什么是半连接池:当服务器在响应了客户端的第一次请求后会进入等待状态,会等客户端发送的ack信息,这时候这个连接就称之为半连接
2.半连接池其实就是一个容器,系统会自动将半连接放入这个容器中,可以避免半连接过多而保证资源耗光
3.产生半连接的两种情况:
①.客户端无法返回ack信息
②.服务器来不及处理客户端的连接请求
黏包问题
#server import socket server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) conn, addr = server.accept() data = conn.recv(1024) data = conn.recv(1024) print(data) data = conn.recv(1024) print(data) #client import socket client = socket.socket() client.connect(('127.0.0.1',8080)) client.send(b'hello') client.send(b'world') client.send(b'baby') #结果 #server >>>:b'helloworldbaby' >>>:b'' >>>:b''
执行一个数据传输的命令时,可能因为数据太长导致在执行其他命令的时候又接收到之前执行的另一部分结果,这种情况就是黏包。
黏包的解决方案struct模块
#struct模块的使用 import struct res = '987654321987654321987654321' print('打包之前的',len(res)) res1 = struct.pack('i',len(res)) # 打包 print('打包的长度',len(res1)) res2 = struct.unpack('i',res1)[0] # 解包 print('解包之后的',res2)
用struct打包的好处是可以将数据的长度数字转换成一个标准大小的4字节数字,因此可以利用这个特点来预先发送数据长度。这样再发送数据的话,客户端可以按照这个长度接收数据。
黏包问题的解决
服务端:
1.先制作一个发送给客户端的字典
2.制作字典的报头
3.发送字典的报头
4.发送字典
5.发送真是数据
客户端
1.先接受字典的报头
2.解析拿到字典的数据长度
3.接收字典
4.从字典中获取真实数据的长度
5.接受真实数据
#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':'what are you doing?'} 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) except ConnectionResetError: break conn.close() #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] #3.接收字典数据 dict_bytes = client.recv(dict_size) #4.从字典中获取信息 dict_json = json.loads(dict_bytes.decode('utf-8')) print(dict_json) recv_size = 0 real_data = b'' while recv_size < dict_json.get('file_size'): data = client.recv(1024) real_data += data recv_size += len(data) print(real_data.decode('gbk'))