一、socket是什么
Socket是应用层与TCP/UDP协议族通信的中间软件抽象层,可以理解为一组接口,把复杂的TCP/UDP协议隐藏在Socket接口后面。遵循socket的规定去编程,那么写出的程序就是遵循TCP/UDP标准的。
套接字的分类:基于文件的(AF_UNIX)和面向网络的(AF_INET)
套接字地址:主机+端口号
二、套接字工作流程
要创建套接字,必须使用socket.socket()函数
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #基于TCP套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #基于UDP套接字
套接字对象(内置)方法:
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
三、基于TCP的套接字
TCP是基于连接的,必须先启动服务器,然后再启动客户端去连接服务器
TCP服务器
ss = socket() #创建服务器套接字
ss.bind() #把地址绑定到套接字
ss.listen() #监听链接
inf_loop: #服务器无限循环
cs = ss.accept() #接受客户端链接
comm_loop: #通讯循环
cs.recv()/cs.send() #对话(接收与发送)
cs.close() #关闭客户端套接字
ss.close() #关闭服务器套接字(可选)
TCP客户端
cs = socket() # 创建客户套接字
cs.connect() # 尝试连接服务器
comm_loop: # 通讯循环
cs.send()/cs.recv() # 对话(发送/接收)
cs.close() # 关闭客户套接字
创建TCP客户端
from socket import *
from time import ctime
HOST = '127.0.0.1' # 本机IP
PORT = 3000 # 端口号
BUFSIZ = 1024 # 缓冲区
ADDR = (HOST, PORT)
# tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建TCP/IP套接字
# udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #创建UDP/IP套接字
tcpSerSock = socket(AF_INET, SOCK_STREAM) # TCP服务器套接字
tcpSerSock.bind(ADDR) # 将套接字绑定到服务器地址
tcpSerSock.listen(5) # 开启TCP监听器的调用
while True:#循环建立连接
print('waiting for connecting ....')
conn, addr = tcpSerSock.accept() # 等待客户端发起TCP连接
print('...connection from :', addr)
while True:#循环接受这个连接发来的消息
try: # 防止客户端异常中断,不然会报错:ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。
data = conn.recv(BUFSIZ)
if not data:break # 防止客户端正常中断,此时循环接收为空
conn.send(('[{}] {}'.format(ctime(), data)).encode('utf-8')) # 格式化返回客户端发送的消息
except Exception:
break
conn.close()
tcpSerSock.close()
创建TCP客户端
from socket import *
HOST = '127.0.0.1' # 服务器的主机号和端口号
PORT = 3000
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpCliSock = socket(AF_INET, SOCK_STREAM) #TCP客户端套接字
tcpCliSock.connect(ADDR) # 主动连接到TCP服务器
while True:
data = input('>>')
if not data:#如果本客户端发送的消息是空的,则跳出此次循环
continue
tcpCliSock.send(data.encode('utf-8'))
data = tcpCliSock.recv(BUFSIZ)
print(data.decode('utf-8'))
tcpCliSock.close()
四、基于UDP的套接字
UDP服务器
ss = socket() #创建UDP服务器套接字
ss.bind() #绑定服务器套接字
inf_loop: #服务器无线循环
cs = ss.revefrom()/ss.sendto() #对话(发送/接受)
ss.close()
UDP客户端
cs = socket() #创建客户端套接字
comm_loop: #通信循环
cs.sendto()/recvfrom() #对话(发送/接收)
cs.close() #关闭
创建UDP服务器
from socket import *
ip_port = ('127.0.0.1', 8000)
BUFSIZE = 1024
udpSerSocket = socket(AF_INET, SOCK_DGRAM)
udpSerSocket.bind(ip_port)
while True:
msg, addr = udpSerSocket.recvfrom(BUFSIZE)
print(msg, addr)
udpSerSocket.sendto(msg.upper(), addr)
创建UDP客户端
from socket import *
ip_port = ('127.0.0.1', 8000)
BUFSIZE = 1024
udpCliSocket = socket(AF_INET, SOCK_DGRAM)
while True:
msg = input('>>:').strip()
if not msg:continue
udpCliSocket.sendto(msg.encode('utf-8'), ip_port)
back_msg, addr = udpCliSocket.recvfrom(BUFSIZE)
print(back_msg.decode('utf-8'), addr)
五、粘包
只有TCP协议才会粘包,UDP协议不会粘包。
UDP协议不用建立连接,面向消息的,既是sendto的是空消息,UDP协议也会帮你封装报头,这样对于接收端来说,就可以区分消息的边界了。即面向消息的通信是有消息保护边界的。数据可以丢失,数据是不可靠的,不会粘包。
TCP协议需要建立连接,三次握手四次挥手,面向数据流。发送发发送文件是按照一段一段的字节流发送的,接收方不知道字节流的边界。TCP协议的包不会丢失,这次没有收完包,下次接受会继续上次接受,己端总是在收到ACK确认是才会清楚缓冲区内容。数据是可靠的,但会粘包。
两种情况下会发生粘包:
(1)发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
(2)接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
六、基于TCP执行远程代码(可能会有粘包)
server:
from socket import *
import subprocess
ip_port = ('127.0.0.1', 8000)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET, SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn, addr = tcp_server.accept()
print('新的客户端连接:', addr)
while True:
try:# 防止客户端异常中断
cmd = conn.recv(buffer_size)
if not cmd: break # 防止客户端正常中断,,此时循环接收为空
print('收到的命令:', cmd)
# 处理收到的命令
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
# 发送命令结果
conn.send(cmd_res)
except Exception as e:
print(e)
break
conn.close()
client:
from socket import *
ip_port = ('127.0.0.1', 8000)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET, SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd: continue
if cmd == 'quit': break # 退出命令
# 发送命令
tcp_client.send(cmd.encode('utf-8'))
# 接受命令结果
cmd_res = tcp_client.recv(buffer_size)
print('命令的执行结果:', cmd_res.decode('gbk'))
tcp_client.close() # 正常关闭掉连接
七、基于UDP执行远程代码(不会有粘包)
server:
from socket import *
import subprocess
ip_port = ('127.0.0.1', 8000)
buffer_size = 1024
udp_server = socket(AF_INET, SOCK_DGRAM)
udp_server.bind(ip_port)
while True:
cmd, addr = udp_server.recvfrom(buffer_size)
# 处理命令
print('收到的命令:', cmd)
# 处理收到的命令
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
if not cmd_res:#如果执行的命令没有返回值
cmd_res = '执行成功'.encode('gbk')
udp_server.sendto(cmd_res, addr)
client:
from socket import *
ip_port = ('127.0.0.1', 8000)
buffer_size = 1024
udp_client = socket(AF_INET, SOCK_DGRAM)
while True:
cmd = input('>>:').strip()
print('cmd', cmd)
if cmd == 'quit':break
udp_client.sendto(cmd.encode('utf-8'), ip_port)
cmd_res, addr = udp_client.recvfrom(buffer_size)
print(cmd_res.decode('gbk'))
udp_client.close()
八、TCP解决粘包(方法一)
server:
from socket import *
import subprocess
ip_port = ('127.0.0.1', 8000)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET, SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn, addr = tcp_server.accept()
print('新的客户端连接:', addr)
while True:
try:# 防止客户端异常中断
cmd = conn.recv(buffer_size)
if not cmd: break # 防止客户端正常中断,,此时循环接收为空
print('收到的命令:', cmd)
# 处理收到的命令
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
# 发送命令结果
if not cmd_res:
cmd_res = '执行成功'.encode('gbk')
# 解决粘包
length = len(cmd_res)
conn.send(str(length).encode('utf-8'))#发送长度
client_ready = conn.recv(buffer_size) # 接收client发送的ready信号,此时可以发送
if client_ready == b'ready':
conn.send(cmd_res)
except Exception as e:
print(e)
break
conn.close()
client:
from socket import *
ip_port = ('127.0.0.1', 8000)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET, SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd: continue
if cmd == 'quit': break # 退出命令
# 发送命令
tcp_client.send(cmd.encode('utf-8'))
# 解决粘包
length = tcp_client.recv(buffer_size)#接收server发送来的长度
tcp_client.send(b'ready')#给server发送ready信号,server收到后就会发送信息
length = int(length.decode('utf-8'))
recv_size = 0
recv_msg = b''#用来拼接接收的数据
while recv_size < length: #只要收到的数据长度小于length,那么就会继续接收
recv_msg += tcp_client.recv(buffer_size)
recv_size = len(recv_msg)
print('命令的执行结果:', recv_msg.decode('gbk'))#windows系统是gbk编码,不是utf-8
tcp_client.close() # 正常关闭掉连接
九、TCP解决粘包(方法二 利用struct模块)
server:
from socket import *
import subprocess
import struct
ip_port = ('127.0.0.1', 8000)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET, SOCK_STREAM)
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)#解决TCP协议中四次挥手的time_wait状态在占用地址问题
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn, addr = tcp_server.accept()
print('新的客户端连接:', addr)
while True:
try:# 防止客户端异常中断
cmd = conn.recv(buffer_size)
if not cmd: break # 防止客户端正常中断,,此时循环接收为空
print('收到的命令:', cmd)
# 处理收到的命令
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
# 发送命令结果
if not cmd_res:
cmd_res = '执行成功'.encode('gbk')
# 解决粘包
data_length = struct.pack('i', len(cmd_res))#利用struct模块得到数据长度
conn.send(data_length) #发送长度
conn.sendall(cmd_res)#发送真是数据内容
except Exception as e:
print(e)
break
conn.close()
client:
from socket import *
import struct
ip_port = ('127.0.0.1', 8000)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET, SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd: continue
if cmd == 'quit': break # 退出命令
# 发送命令
tcp_client.send(cmd.encode('utf-8'))
# 解决粘包
data_length = tcp_client.recv(4)
length = struct.unpack('i', data_length)[0]#得到数据长度
recv_size = 0
recv_msg = b'' # 用来拼接接收的数据
while recv_size < length: # 只要收到的数据长度小于length,那么就会继续接收
recv_msg += tcp_client.recv(buffer_size)
recv_size = len(recv_msg)
print('命令的执行结果:', recv_msg.decode('gbk'))#windows系统是gbk编码,不是utf-8
tcp_client.close() # 正常关闭掉连接
十、把报头做成字典,字典里包含将要发送的真实数据的详细信息(利用json模块)
server:
from socket import *
import subprocess, struct, json
ip_port = ('127.0.0.1', 8000)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET, SOCK_STREAM)
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)#解决TCP协议中四次挥手的time_wait状态在占用地址问题
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn, addr = tcp_server.accept()
print('新的客户端连接:', addr)
while True:
try:# 防止客户端异常中断
cmd = conn.recv(buffer_size)
if not cmd: break # 防止客户端正常中断,,此时循环接收为空
print('收到的命令:', cmd)
# 处理收到的命令
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
# 发送命令结果
if not cmd_res:
cmd_res = '执行成功'.encode('gbk')
# 解决粘包
headers = {'data_size':len(cmd_res)}
head_json = json.dumps(headers) #序列化
head_json_bytes = bytes(head_json, encoding='utf-8') #转成bytes,用于传输
conn.send(struct.pack('i', len(head_json_bytes)))#发送报头长度,用struck将报头长度这个数字转成固定长度:4个字节
conn.send(head_json_bytes) # 发送报头的字节格式
conn.sendall(cmd_res)#发送真实数据内容
except Exception as e:
print(e)
break
conn.close()
client:
from socket import *
import struct, json
ip_port = ('127.0.0.1', 8000)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET, SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd: continue
if cmd == 'quit': break # 退出命令
# 发送命令
tcp_client.send(cmd.encode('utf-8'))
# 解决粘包
head = tcp_client.recv(4) #先收报头4个bytes,得到报头长度的字节格式,server端用struck将报头长度这个数字转成固定长度:4个字节,故此处接收4个bytes
head_json_len = struct.unpack('i', head)[0] #利用struct提取报头的长度
head_json = json.loads(tcp_client.recv(head_json_len).decode('utf-8')) #得到报头的字节格式,并反序列化得到报头
data_len = head_json['data_size'] #从报头中得到真实数据长度
recv_size = 0
recv_msg = b'' # 用来拼接接收的数据
while recv_size < data_len: # 只要收到的数据长度小于length,那么就会继续接收
recv_msg += tcp_client.recv(buffer_size)
recv_size = len(recv_msg)
print('命令的执行结果:', recv_msg.decode('gbk'))#windows系统是gbk编码,不是utf-8
tcp_client.close() # 正常关闭掉连接