一、套接字家族
1、AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
2、AF_INET 用的比较官方,有些会用到AF_INET6 即ipv6
二、TCP协议与UDP协议
TCP协议,可靠的,面向连接的协议,但是效率低,流式协议,应用较广泛
UDP协议,不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制
三、TCP与UDP的socket套接字应用
1、TCP套接字 SOCK_STREAM
服务端
import socket server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建对象,设置套接字家族与传输类型,这里的SOCK_STREAM是表示TCP传输类型 server.bind(('127.0.0.1',8898)) #把地址绑定到套接字 端口范围:0-65535 其中0-1024是给操作系统用的 server.listen() #监听链接 conn,addr = server.accept() #接受客户端链接 这里返回的是一个元组 ret = conn.recv(1024) #接收客户端信息 print(ret) #打印客户端信息 conn.send(b'hi') #向客户端发送信息 conn.close() #关闭客户端套接字 server.close() #关闭服务器套接字(可选)
客户端
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建客户套接字 client.connect(('127.0.0.1',8898)) # 尝试连接服务器 client.send(b'hello!') #发送 ret = client.recv(1024) # 接收 print(ret) client.close() # 关闭客户套接字
2、UDP套接字 SOCK_DGRAM
服务端
import socket server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 创建对象 server.bind(('127.0.0.1',8002)) # 绑定ip,端口 msg,addr = server.recvfrom(1024) # 接收,获取客户端的ip print(msg,addr) server.sendto(b'hello',addr) # 发送 server.close()
客户端
import socket client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) client.sendto(b'hello',('127.0.0.1',8002)) # 发送 conter,addr = client.recvfrom(1024) # 接受 print(conter) # b'hello' print(addr) # ('127.0.0.1', 8002) client.close() # 关闭
四、黏包问题 ,TCP中有,UTP中没有
1、什么是黏包演示如下:
# 服务端 import socket server = socket.socket(socket.AF_INET,socket.SOCK_STREAM ) server.bind(('127.0.0.1',8002)) server.listen(5) conn,addr = server.accept() conn.send(b'good') conn.send(b'hollo') # 客户端 import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',8002)) ret = client.recv(2) # 只接收2个字节,其他的缓存到管道中 print(ret) res = client.recv(5) # 这里你会发现前面没接收的在这里与第二次一起接收了 print(res) rer = client.recv(4) print(rer) #结果 b'go' b'odhol' b'lo'
2、还有多次发送一次接收
# 服务端 import socket server = socket.socket(socket.AF_INET,socket.SOCK_STREAM ) server.bind(('127.0.0.1',8002)) server.listen(5) conn,addr = server.accept() ret = conn.recv(1024) # 只接收一次 print(ret) # 客户端 import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',8002)) client.send(b'good') client.send(b'h') client.send(b'e') # 这里多次发送 client.send(b'a') # 结果 b'goodhea'
3、这种情况叫黏包,黏包的原因是TCP是可靠的面向流式的协议,所以在传输前要架两条管道,一条是服务端到客户端,一条客户端到服务端,TCP底层使用了Nagle算法进行优化,当有多次发送且间隔时间极短时会打包在一起一次发送过去,如果接收时没有接收完会缓存在管道中,等待下一次继续接收
黏包解决方法如下:在解决之前先用一个模拟ssm的小案例来演示
什么是ssm即:在客户端发送命令到服务端,让服务端执行发送过来的命令
黏包未解决时:
服务端
import socket import subprocess severe = socket.socket(socket.AF_INET,socket.SOCK_STREAM) severe.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) severe.bind(('127.0.0.1',8001)) severe.listen(5) while 1: conn,addr = severe.accept() while 1: try: print(addr) data = conn.recv(1024) obj = subprocess.Popen(data.decode('utf-8'),shell=True, # 这里是执行接收到的命令拿到执行结果 stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stderr.read() conn.send(stdout)# 发送 conn.send(stderr)# 发送 except ConnectionResetError: break conn.close()
客户端
import socket cilm = socket.socket() cilm.connect(('127.0.0.1',8001)) while 1: use = input("<<<").strip() if not use: continue cilm.send(use.encode('utf-8')) ret = cilm.recv(1024) # 接收结果 print(ret.decode('GBK')) # 打印结果,这里解码gbk是因为windows中终端默认是gbk cilm.close()
4、首先在这里实现了ssm功能,但是只能用来执行结果少的一些命令,如果你执行一个结果长一点的命令就出问题了如:windows系统执行ipconfig,mac或Linux系统执行ifconfig就会发现结果接收不全,再执行以下其他的命令会发现会接收前面未接收完的数据
接下来解决黏包问题:
首先黏包问题是由于对方不知道你要发送的数据的长度而接收不全产生的,所以只要让对方知道要发送数据的长度就可以了
服务端:
import socket import subprocess import struct import json severe = socket.socket() severe.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 这里用这个是为了防止电脑回收端口速度比较慢而导致服务端报错 severe.bind(('192.168.2.110',8000)) severe.listen(5) while 1: conn,addr = severe.accept() while 1: try: print(addr) data = conn.recv(1024) obj = subprocess.Popen(data.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stderr.read() # 1.制作报头 heard = {'filename': 'werwr.txt', 'md5': '12313', 'size': len(stderr) + len(stdout)} # 2.制作报头长度的报头 # 序列化,因为网络传输不能传字典,将他序列化为字符串 heard_json = json.dumps(heard) heard_bytes = heard_json.encode('utf-8') # 将序列化后的报头长度进行打包 heard_size = struct.pack('i', len(heard_bytes)) # 3.发送报头长度的报头 conn.send(heard_size) # 4.发送报头 conn.send(heard_bytes) # conn.send(stdout+stderr) # + 是一个可以优化的点 conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close()
客户端:
import socket import struct import json cilm = socket.socket() cilm.connect(('192.168.2.110',8000)) while 1: use = input("<<<").strip() if not use: continue cilm.send(use.encode('utf-8')) # 1.接收报头长度的报头 heard_size = cilm.recv(4) # 2.解包拿到报头的长度 struct_size = struct.unpack('i',heard_size)[0] # 3.接收报头长度 heard_cent = cilm.recv(struct_size) # 4.反序列化 # heard_json = json.loads(heard_cent) # 未知情况 bytes直接loads heard_json = json.loads(heard_cent.decode('utf-8')) print(heard_json,type(heard_json)) # 5.拿到报头中的报文的长度 total_size = heard_json["size"] number = 0 data = b'' while number< total_size: ret = cilm.recv(1024) # 1024 这里是一个坑 data +=ret number+=len(ret) print(data.decode('GBK')) cilm.close()