Socket编程
一个简单的Socket 编程
Socket通信流程如下图:
下面写一个最简单的Socket通信程序,假设服务器与客户端都在本机(127.0.0.1),模拟发送信息的过程,sever.py
表示服务端,client.py
表示客户端。需要注意,在Python3中,Socket的send方法只能发送bytes数据,所以需要把str数据强制转换为bytes类型,编码类型为UTF-8。
sever.py
代码如下:
# sever.py
import socket
sk = socket.socket()
address = ('127.0.0.1', 8848) # bind方法的参数是一个元组,由IP地址和端口号(自己任意添加)组成
sk.bind(address) # 为Socket绑定IP和端口号
sk.listen(3) # 监听设置端口,等待客户端的请求。在服务端与某客户端连接时,最多有3个客户端可以为等待状态
print('Waiting...')
conn, addr = sk.accept() # Accept阻塞,直到有客户端连接进来
print('Connected!')
inp = input()
conn.send(bytes(inp, encoding='utf8')) # 把发送的数据转为bytes类型,编码格式为UTF-8
print('服务端向客户端发送信息')
data = conn.recv(1024) # 1024表示接受的最大数据量
print(str(data, encoding='utf8'))
client.py
代码如下:
# client.py
import socket
sk = socket.socket()
address = ('127.0.0.1', 8848) # 地址与服务端相同
sk.connect(address)
data = sk.recv(1024)
print(str(data, encoding='utf8'))
inp = input()
sk.send(bytes(inp, encoding='utf8'))
print('客户端向服务端发送信息')
首先运行sever,显示:Waiting…
运行client之后,再看sever,显示:Connected!
这时在sever编写一条信息发送,sever和client的显示分别如下:
这时在client编写一条信息,发送后,sever和client显示分别如下:
在程序中要注意的问题是,客户端向服务端发送信息,用自己的 sk.send() 就可以,而服务端向客户端发送信息要使用由 sk.accept() 得到的 conn的send方法来发送。因为服务器会和多个客户端连接,每个连接都会生成新的Socket,在上面的程序中conn就是代表这个新的Socket,它表示服务器与客户端的连接,如果要断开这个连接,调用 conn.close() 方法。他们的关系可以这样表示:
不间断聊天
# ---------- sever.py ----------
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8848))
sk.listen(3) # 最多有3个客户端在等待
print('Sever waiting...')
while True:
conn, addr = sk.accept() # 连接客户端
print('Connected!')
while True:
client_data = conn.recv(1024)
client_data = str(client_data, encoding='utf8')
if client_data == 'exit':
break
print(client_data)
sever_response = input('>>>')
if sever_response == 'exit':
break
conn.send(bytes(sever_response, encoding='utf8'))
conn.close()
# ====================================================================
# ---------- client.py ----------
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8848))
while True:
inp = input('>>>')
sk.send(bytes(inp, encoding='utf8'))
if inp == 'exit':
break
sever_response = sk.recv(1024)
print(str(sever_response, encoding='utf8'))
多次运行client,最多可以同时运行4个(1个与服务端连接,另外3个在等待),再多就会报错。在一个client断开连接时,排队的client依次接上。
发送命令
发送cmd命令实质没有新内容,只需要掌握如何用 subprocess 模块来执行cmd命令就可以了,看一个简单的例子:
import subprocess
cmd = input('>>>').strip()
cmd_call = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
cmd_result = cmd_call.stdout.read()
print(str(cmd_result, encoding='gbk'))
远程发送命令结果,把 cmd_result 发送即可,需要注意命令行编码格式为GBK,所以强制转换时类型应为GBK。
粘包现象
假设在程序中有这么一段代码:
# 发送端
conn.sendall(result_len) # 期望作为 int 类型
conn.sendall(result) # 期望作为 str 类型
# 接收端
result_len = int(str(sk.recv(1024), encoding='utf8'))
result = str(sk.recv(1024), encoding='utf8')
而在接收端会把 result_len 当做 int 类型来处理,但是当强制转化为 int 时可能出错,因为在sendall时会等待一段很小的时间,如果还有数据会一起发送,所以接收端收到的其实是一个 result_len + result 的字符串。这就称为粘包现象,要解决这个问题,在两次sendall中间加一个recv(),把两个sendall分隔开就可以了。修改后的代码:
# 发送端
conn.sendall(result_len) # 期望作为 int 类型
conn.recv(1024)
conn.sendall(result) # 期望作为 str 类型
# 接收端
result_len = int(str(sk.recv(1024), encoding='utf8'))
sk.send(bytes('111', encoding='utf8'))
result = str(sk.recv(1024), encoding='utf8')