1.什么是socket?
两个进程如果需要进行通讯最基本的一个前提能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。
能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
socket是应用层与TCP/IP协议族通信的中间软件层,它是一组接口。在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合之id那个的协议。
所以,我们无需深入理解TCP/UDP协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,那么写出的程序自然是符合TCP/UDP标准的。
2.套接字的种类
我们认识的套接字分为两种:1)基于文件的套接字:AF_UNIX
2) 基于协议的套接字:AF_INET
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一台机器上可以通过访问同一个文件系统间接完成通信。但是,AF_INET是使用最为广泛的一个,python支持多种地址家族,但是我们只关心网络编程,所以大部分我们只是用AF_INET。
3.套接字的使用
1)基本使用
import socket# 买手机 默认得到是一个TCP的socket server = socket.socket()server.bind(("127.0.0.1",16888)) # 绑定手机卡 server.listen() # 开始待机# 得到对方的socket对象与地址 client,addr = server.accept() # 接收通话请求 # 该函数会阻塞 直到有连接请求过来# buffersize 表示应用程序的缓冲区大小 recv其实是 从系统缓冲区读取数据到应用程序 data = client.recv(1024) # 该函数会阻塞 直到操作缓冲区有数据位置print("收到客户端发来的数据:%s" % data.decode("utf-8"))# 发生的数据必须为bytes类型 client.send(data) client.close() #挂断电话 server.close() # 关机
import socketclient = socket.socket()client.connect(("127.0.0.1",16888))client.send("hello 服务器".encode("utf-8"))data = client.recv(1024) # 该函数会阻塞 直到操作缓冲区有数据位置print("收到服务器:%s" % data.decode("utf-8"))client.close()
2)通信循环
import socket server=socket.socket() server.bind(("127.0.0.1",16888)) # 开始待机 server.listen()# 通信循环 while True:client,addr=server.accept()while True:try:# 如果是windows 对方强行关闭连接会抛出异常# 如果是linux 不会抛出异常 会收到空的数据包进入死循环data=client.recv(1024)if not data :client.close()breakprint('收到数据%s:'%data.decode('utf-8'))client.send(data)except ConnectionResetError:print('客户端关闭连接')client.close()break # 挂断电话 client.close() # 关机 server.close()
import socket client=socket.socket() client.connect(("127.0.0.1",16888)) while True:msg=input('>>>:')client.send(msg.encode('utf-8'))data=client.recv(1024)print('收到服务器:%s'%data.decode('utf-8')) client.close()
3)TCP协议的异常处理
import socketserver = socket.socket()server.bind(("127.0.0.1",8888))server.listen()client,addr = server.accept()while True:try:data = client.recv(1024)if not data:print("对方已经关闭.....")breakprint(data.decode("utf-8"))client.send(data.upper())except ConnectionResetError:print("对方异常关闭连接...")client.close() server.close()
import socketclient = socket.socket() client.connect(("127.0.0.1",8888))client.send("hello".encode("utf-8"))data = client.recv(1024) print(data.decode("utf-8"))client.close()
4.粘包问题(处理方式)
粘包产生的原因:1.对于数据接受端来说,它不知道自己接受的将会是多大的数据。
2.对于发送方来说,TCP内部会将很小的数据都打包在一起,一起发送。
粘包的解决方案:(以操作shell指令为例)
import socket, subprocess import json import structIP_PORT = ('127.0.0.1', 8888) server = socket.socket() server.bind(IP_PORT) server.listen(5)while True:conn, addr = server.accept()while True:try:data = conn.recv(1024).decode('utf-8')if len(data) == 0: breakobj = subprocess.Popen(data, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)stdout = obj.stdout.read()stderr = obj.stderr.read()info_dic = {'name': '二进制指令','len': len(stdout + stderr)}info_bytes = json.dumps(info_dic).encode('utf-8')header = struct.pack('i', len(info_bytes))conn.send(header)conn.send(info_bytes)conn.send(stderr + stdout)except ConnectionResetError:breakconn.close()
import socket import struct import jsonIP_PORT = ('127.0.0.1', 8888) cilent = socket.socket() cilent.connect(IP_PORT)while True:cmd = input('cmd>>>>')if len(cmd) == 0: continuecilent.send(cmd.encode('utf-8'))header = cilent.recv(4)header_len = struct.unpack('i', header)[0]info_dic = json.loads(cilent.recv(header_len).decode('utf-8'))total_size = info_dic.get('len')rec_size = 0res = b''while rec_size < total_size:data = cilent.recv(1024)res += datarec_size += len(data)print(res.decode('gbk'))
5.案列:客户端传送大文件
import socket import json import structIP_PORT = ('127.0.0.1', 9633) server = socket.socket() server.bind(IP_PORT) server.listen(5)while True:conn, addr = server.accept()while True:try:# 接收报头header = conn.recv(4)# 解包得到字典的真实长度file_bytes = struct.unpack('i',header)[0]# 反序列化成字典file_info = json.loads(conn.recv(file_bytes).decode('utf-8'))file_name = file_info.get('file_name')file_size = file_info.get('file_size')rec_size = 0# 开始接受文件,并写入with open(file_name, 'wb') as w:while rec_size < file_size:data = conn.recv(1024)w.write(data)rec_size += len(data)print(file_info.get('msg'))except ConnectionResetError:breakconn.close()
import socket import json import struct import osIP_PORT = ('127.0.0.1', 9633) cilent = socket.socket() cilent.connect(IP_PORT)# 文件的大小 file_size = os.path.getsize(r'E:\fullstack_s4\day32\3.标记删除.mp4') # 文件的名字 file_name = "王鹏是小猪.mp4" # 定义文件信息字典 file_info = {'file_name': file_name,'file_size ': file_size,'msg': "不要老师看?会被影响的!" } # 将字典序列化为二进制 header_bytes = json.dumps(file_info).encode('utf-8') # 打包并生报头 header = struct.pack('i', len(header_bytes)) # 发送报头 cilent.send(header) # 发送报头字典 cilent.send(header_bytes) # 发送真实的数据 with open(r'E:\fullstack_s4\day32\3.标记删除.mp4', 'rb') as f:for line in f:cilent.send(line)
6.UDP协议
UDP协议属于数据报协议,没有双向通道的存在。
相比TCP协议: 1.他不会存在粘包问题
2.客户端可以发空
3.看似是并发过程
4.在不启动服务短的情况下,运行客户端不会报错
import socketIP_PORT = ('127.0.0.1', 8080) server = socket.socket(socket.SOCK_DGRAM) server.bind(IP_PORT)while True:conn, addr = server.recvfrom(1024)print(conn, addr)server.sendto(conn.upper(), addr)
import socketIP_PORT = ('127.0.0.1', 8080) cilent = socket.socket(socket.SOCK_DGRAM)cilent.sendto(b'hello baby',IP_PORT)
7.socketserver模块
import socketserverclass MyServer(socketserver.BaseRequestHandler):def handle(self):while True:data = self.request.recv(1024)print(data)self.request.send(data.upper())if __name__ == '__main__':server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyServer)server.serve_forever()
import socketcilent = socket.socket() cilent.connect(('127.0.0.1',8080))while True:cilent.send(b'hello')data = cilent.recv(1024)print(data)