Python进阶----SOCKET套接字基础, 客户端与服务端通信, 执行远端命令.
Python进阶----SOCKET套接字基础, 客户端与服务端通信, 执行远端命令.
一丶socket套接字
什么是socket套接字:
专业理解: socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,Socket其实就是使用一个门面模式(门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用), 它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部.让socket去组织数据.
简易理解:socket就是一个模块,封装了网络通信所需要的所有东西.通过调用socket模块实现两个进程之间的连接和通讯.(也可称为:IP+Port , IP用于表示互联网中的一台主机的位置,而port则是定位到这个机器上的某个程序.)
socket套接字的History
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
两个种族:
1.基于网络类型的套接字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
2.基于文件类型的套接字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
二丶基于TCP协议的socket通信
socket函数具体使用代码如下:👇
import socket
socket.socket(socket_family,socket_type,protocal=0)
#socket_family 地址簇 : 可以是 AF_UNIX 或 AF_INET。
#socket_type : 可以是 SOCK_STREAM 或 SOCK_DGRAM。
# protocol : 一般不填,默认值为 0。
### 获取tcp/ip套接字
# socket.AF_INET: IPV4 IP协议
# socket.SOCK_STREAM : 提供面向连接的稳定数据传输 TCP是流传输
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
### 获取udp/ip套接字
# socket.AF_INET: IPV4 IP协议
# socket.SOCK_DGRAM 使用不连续不可靠的数据包连接 , 数据包
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
服务端套接字函数
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是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
### Client 客户端
import socket
client=socket.socket() # 创建 socket对象
client.connect(('127.0.0.1',8848)) # 连接服务端地址,端口号8848
# 发送数据
client.send('abcd'.encode('utf-8')) # 客户端 发送字节类型数据
from_server_data=client.recv(1024) # 客户端接收服务端响应的数据
print(from_server_data)
client.close() # 关闭客户端连接
### server服务端
import socket
server=socket.socket() # 1 . 创建 socket的server对象
server.bind(('127.0.0.1',8848)) # 2 . 绑定IP 和端口
# 3. 监听
server.listen(5)
# 4. 接受连接
conn,addr=server.accept() # 程序等待
print(conn,addr) # 打印 conn 连接信息 ,和addr地址信息
data=conn.recv(1024) # 接收客户端发送的数据 最多接收1024字节
conn.send(data.upper()) # 发送给客户端数据
conn.close() # 关闭连接
server.close() #关闭服务
四丶通信循环
### client
import socket
client=socket.socket()
client.connect(('127.0.0.1',8848))# 连接
# 发送数据
while 1:
user_input=input('>>').strip().encode('utf-8') # 用户输入的字符串数据,转换成字节格式
client.send(user_input) # 客户端 发送字节数据
from_server_data=client.recv(1024) # 接收服务端 返回的数据 ,每次接收1024字节
print(f'来自服务端的信息:{from_server_data.decode("utf8")}')
client.close()
### server服务端
import socket
server=socket.socket() # 1 . 创建 server对象
server.bind(('127.0.0.1',8848)) # 2 . 绑定IP 和端口
# 3. 监听
server.listen(5)
# 4. 接受连接
conn,addr=server.accept() # 程序等待
print(conn,addr)
while 1 : # 循环去
data=conn.recv(1024) # 接收数据 最多接收1024字节
print(f'来自客户端{addr}的信息{data.decode("utf-8")}')
to_client=input('>>>').strip().encode('utf-8')
conn.send(to_client) # 发送数据
conn.close() # 关闭连接
server.close() #关闭服务
五丶通信,连接循环
### 客户端 client ,
# 启动多个客户端, 依次排队, 当第一个连接断开时,第二个连接就会连接服务端
import socket
client =socket.socket()
client.connect(('127.0.0.1',9999))
while 1:
try:
user_input=input('请输入信息:>>>').strip().encode('utf-8')
if user_input.upper()==b'Q':
client.send(user_input)
break
client.send(user_input)
ser_data=client.recv(1024)
print(f'来自服务端的信息:{ser_data.decode("utf-8")}')
except ConnectionResetError:
break
client.close()
### server 服务端
import socket
server=socket.socket()
server.bind(('127.0.0.1',9999))
server.listen(2)
while 1:
conn,addr=server.accept()
print(conn,addr)
while 1:
try:
cli_data=conn.recv(1024)
print(f'来自客户端:{addr}消息: {cli_data.decode("utf-8")}')
if cli_data.upper()==b'Q':
break
response=input('回复消息:>>').strip().encode('utf-8')
conn.send(response)
except ConnectionResetError:
break
conn.close() # 当上一个conn 连接已经关闭或断开时,进入下一次循环,等待新的连接
server.close()
六丶利用socket完成获取远端命令的示例
subprocess模块的使用
# -*-coding:utf-8-*-
import subprocess
while 1:
user_input=input('请输入指令:').strip()
obj=subprocess.Popen(user_input,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if obj.stdout:
print(f'正确信息: {obj.stdout.read().decode("gbk")}')
else:
print(f'错误信息: {obj.stderr.read().decode("gbk")}')
利用socket获取远端命令(产生粘包现象)
什么是粘包:
客户端不能一次性接收完服务器返回的信息.这些没有接收完的信息会保存在一个缓冲区内. 等下次连接再来时,先把上一次没有收完的数据给接收了.
### server
import socket
import subprocess
server =socket.socket()
server.bind(('127.0.0.1',8877))
server.listen(5)
while 1:
conn , addr=server.accept()
print(conn,addr)
while 1:
try:
cmd=conn.recv(1024) # 接收客户端发来的cmd 字节指令
print(cmd)
# 执行本机cmd命令
obj=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# 拼接返回信息
msg_right=obj.stdout.read()
msg_err=obj.stderr.read()
result=(msg_right+msg_err).decode('gbk').encode('utf-8')
print(result.decode('utf-8'))
# 向客户端发送结果
conn.send(result)
except ConnectionResetError:
break
conn.close()
server.close()
### client
import socket
client=socket.socket()
#连接服务器
client.connect(('127.0.0.1',8877))
while 1:
user_input=input('请输入指令:>>>').strip()
# 发送指令 以字节形式
client.send(user_input.encode('utf-8'))
# 接收返回结果
try:
ser_data=client.recv(1024)
print(ser_data.decode('utf-8'))
except UnicodeDecodeError as e:
print(e)
client.close()