Socket 编程是一种技术,我们可以在其中在网络中通信的两个节点之间进行通信,其中服务器节点监听来自客户端节点的传入请求。
在 Python 中,使用 socket 模块来进行 socket 编程。标准库中的 socket 模块包含了硬件级别上服务器与客户端之间通信所需的功能。
该模块提供了对 BSD socket 接口的访问。它适用于所有操作系统,如 Linux、Windows、MacOS。
什么是 Socket?
Socket 是双向通信信道的端点。Socket 可以在进程内、同一机器上的进程之间或者不同大陆上的进程之间进行通信。
一个 Socket 由 IP 地址和端口号的组合来识别。通信开始前两端应正确配置。
Socket 可能实施于不同的通道类型之上:Unix 域套接字、TCP、UDP 等等。套接字库提供了处理常见传输的具体类以及处理其余部分的通用接口。
术语 Socket 编程意味着编程设置 Socket 以便能够发送和接收数据。
有两种类型的通信协议:
TCP 或者传输控制协议是一种面向连接的协议。数据由服务器以包的形式传输,并由接收方按照传输顺序组装。由于通信两端的 Socket 需要在开始前设置,因此这种方法更加可靠。
UDP 或用户数据报协议是无连接的。该方法不可靠,因为 Socket 不需要建立任何连接和终止过程来传输数据。
Python socket 模块
socket 模块用于创建和管理网络中连接节点的 Socket 编程。socket 模块提供了一个 socket 类。你需要使用 socket.socket() 构造函数来创建一个 Socket。
socket 类的对象表示主机名和端口号的配对。
语法
以下是 socket.socket() 构造函数的语法:
socket.socket(socket_family, socket_type, protocol=0)
参数
-
family - 默认为 AF_INET。其他值 - AF_INET6(八组四位十六进制数字)、AF_UNIX、AF_CAN(控制器局域网)或 AF_RDS(可靠数据报套接字)。
-
socket_type - 应为 SOCK_STREAM(默认),SOCK_DGRAM、SOCK_RAW 或者可能是其他 SOCK_ 常量之一。
-
返回类型
此方法返回一个 socket 对象。
一旦有了 socket 对象,那么就可以使用所需的方法来创建你的客户端或服务器程序。
服务器 Socket 方法
在服务器上实例化的 Socket 被称为服务器 Socket。以下方法可用于服务器上的 Socket 对象:
-
bind() 方法 - 此方法将 Socket 绑定到指定的 IP 地址和端口号。
-
listen() 方法 - 此方法启动服务器并运行监听循环以查找来自客户端的连接请求。
-
accept() 方法 - 当连接请求被服务器截获时,此方法接受它并标识客户端 Socket 及其地址。
要创建一个服务器上的 Socket,请使用以下代码片段:
import socket
server = socket.socket()
server.bind(('localhost', 12345))
server.listen()
client, addr = server.accept()
print("connection request from: " + str(addr))
默认情况下,服务器绑定到本地机器的 IP 地址 'localhost' 并监听任意空闲端口。
客户端 Socket 方法
类似地,在客户端也设置了 Socket。它主要向在其 IP 地址和端口号上监听的服务器 Socket 发送连接请求。
-
connect() 方法 - 此方法接受一个两元素的元组对象作为参数。两个元素分别是服务器的 IP 地址和端口号。
obj = socket.socket()
obj.connect((host, port))
一旦连接被服务器接受,两个 Socket 对象都可以发送和/或接收数据。
-
send() 方法 - 服务器使用其截取的地址将数据发送给客户端。客户端 Socket 将数据发送给与其建立了连接的 Socket。
-
sendall() 方法 - 类似于 send()。但是,与 send() 不同的是,此方法会继续发送 bytes 直到所有数据都被发送或出现错误。成功时返回 None。
-
sendto() 方法 - 此方法仅在 UDP 协议的情况下使用。
-
recv() 方法 - 此方法用于检索发送给客户端的数据。对于服务器来说,它使用已接受请求的远程 Socket。
-
recvfrom() 方法 - 此方法仅在 UDP 协议的情况下使用。
Python - Socket 服务器
为了编写 Internet 服务器,我们在 socket 模块中使用 socket 函数来创建一个 socket 对象。然后使用该 socket 对象调用其他函数来设置 socket 服务器。
现在调用 bind(hostname, port) 函数来指定在给定主机上的端口用于你的服务。
接下来,调用返回对象的 accept 方法。此方法等待直到客户端连接到你指定的端口,然后返回一个代表与该客户端连接的连接对象。
服务器 Socket 示例
import socket
host = "127.0.0.1"
port = 5001
server = socket.socket()
server.bind((host, port))
server.listen()
conn, addr = server.accept()
print("Connection from: " + str(addr))
while True:
data = conn.recv(1024).decode()
if not data:
break
data = str(data).upper()
print(" from client: " + str(data))
data = input("type message: ")
conn.send(data.encode())
conn.close()
Python - Socket 客户端
让我们编写一个非常简单的客户端程序,它打开一个连接到给定端口 5001 和给定本地主机的连接。使用 Python 的 socket 模块函数创建一个 socket 客户端非常简单。
socket.connect(hosname, port) 打开到 hostname 上端口的 TCP 连接。一旦你打开了一个 socket,你可以像处理任何 IO 对象一样从中读取数据。完成后,记得关闭它,就像关闭一个文件一样。
客户端 Socket 示例
import socket
host = '127.0.0.1'
port = 5001
obj = socket.socket()
obj.connect((host, port))
message = input("type message: ")
while message != 'q':
obj.send(message.encode())
data = obj.recv(1024).decode()
print('Received from server: ' + data)
message = input("type message: ")
obj.close()
首先运行服务器代码。它开始监听。
然后启动客户端代码。它发送请求。
请求被接受。客户端地址被识别。
输入一些文本并按 Enter 键。
接收到的数据被打印。发送数据到客户端。
从服务器接收的数据。
当输入 'q' 时,循环终止。
下面显示了服务器-客户端的交互:
我们已经在本地机器上使用 socket 模块实现了客户端-服务器通信。要把服务器和客户端代码放在网络上的两台不同机器上,我们需要找到服务器机器的 IP 地址。
在 Windows 上,你可以通过运行 ipconfig 命令来找到 IP 地址。Ubuntu 上的 ifconfig 命令是等效命令。
更改服务器和客户端代码中的主机字符串为 IPv4 地址值,并像以前一样运行它们。
Python 文件传输与 Socket 模块
下面的程序演示了如何使用 socket 通信从服务器传输文件到客户端。
服务器代码
建立连接的代码与之前相同。在接受连接请求之后,服务器上以二进制模式打开一个文件进行读取,并逐次读取字节并发送到客户端流,直到文件结束。
import socket
host = "127.0.0.1"
port = 5001
server = socket.socket()
server.bind((host, port))
server.listen()
conn, addr = server.accept()
data = conn.recv(1024).decode()
filename = 'test.txt'
f = open(filename, 'rb')
while True:
l = f.read(1024)
if not l:
break
conn.send(l)
print('Sent ', repr(l))
f.close()
print('File transferred')
conn.close()
客户端代码
在客户端,以 wb 模式打开一个新文件。从服务器接收的数据流写入到文件中。随着流的结束,输出文件关闭。将在客户端机器上创建一个新文件。
import socket
s = socket.socket()
host = "127.0.0.1"
port = 5001
s.connect((host, port))
s.send("Hello server!".encode())
with open('recv.txt', 'wb') as f:
while True:
print('receiving data...')
data = s.recv(1024)
if not data:
break
f.write(data)
f.close()
print('Successfully received')
s.close()
print('connection closed')
Python 标准库中的 socketserver 模块
Python 标准库中的 socketserver 模块是一个框架,用于简化编写网络服务器的任务。模块中有以下类代表同步服务器:
这些类与相应的 RequestHandler 类一起工作以实现服务。BaseServer 是模块中所有 Server 对象的超类。
TCPServer 类使用互联网 TCP 协议,在客户端和服务器之间提供连续的数据流。构造函数自动尝试调用 server_bind() 和 server_activate()。其他参数传递给 BaseServer 基类。
你还必须创建 StreamRequestHandler 类的子类。它提供 self.rfile 和 self.wfile 属性来读取或写入获取请求数据或将数据返回给客户端。
UDPServer 和 DatagramRequestHandler — 这些类是用于 UDP 协议的。
DatagramRequestHandler 和 UnixDatagramServer — 这些类使用 Unix 域套接字;它们在非 Unix 平台上不可用。
服务器代码
你必须编写一个 RequestHandler 类。每当有连接到服务器时,都会实例化一次,并且必须覆盖 handle() 方法来实现与客户端的通信。
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
self.data = self.request.recv(1024).strip()
host, port = self.client_address
print("{}:{} wrote:".format(host, port))
print(self.data.decode())
msg = input("enter text .. ")
self.request.sendall(msg.encode())
在服务器分配的端口号上,TCPServer 类的对象调用 forever() 方法将服务器置于监听模式并接受来自客户端的传入请求。
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
server.serve_forever()
客户端代码
在使用 socketserver 时,客户端代码与 socket 客户端应用程序或多或少是相似的。
import socket
import sys
HOST, PORT = "localhost", 9999
while True:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((HOST, PORT))
data = input("enter text .. ")
sock.sendall(bytes(data + "\n", "utf-8"))
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
在其中一个命令提示符终端中运行服务器代码。为客户端实例打开多个终端。你可以模拟服务器与多个客户端之间的并发通信。
服务器 | 客户端-1 | 客户端-2
D:\socketsrvr>python myserver.py
127.0.0.1:54518 wrote:
from client-1
enter text ..
hello
127.0.0.1:54522 wrote:
how are you
enter text ..
fine
127.0.0.1:54523 wrote:
from client-2
enter text ..
hi client-2
127.0.0.1:54526 wrote:
good bye
enter text ..
bye bye
127.0.0.1:54530 wrote:
thanks
enter text ..
bye client-2
D:\socketsrvr>python myclient.py
enter text .. .
from client-1
Sent:
from client-1
Received: hello
enter text .. .
how are you
Sent:
how are you
Received: fine
enter text .. .
good bye
Sent: good bye
Received: bye bye
enter text .. .
D:\socketsrvr>python myclient.py
enter text .. .
from client-2
Sent:
from client-2
Received: hi client-2
enter text .. .
thanks
Sent: thanks
Received:
bye client-2
enter text .. .