前言
在现代计算机网络中,Socket(套接字)是实现进程之间通信的重要工具。在网络应用中,Socket 充当了进程间数据传输的搬运工,负责进程之间的网络数据传输。无论是服务器还是客户端,二者通过 Socket 进行通信,形成了网络应用的基础。本章详细讲解了Socket服务端开发以及Socket客户端开发。
一、Socket网络编程
Socket 是网络编程中用于建立和管理网络连接的一种抽象,主要用于服务端和客户端之间的通信。
①Socket服务端
- 监听请求: 服务端负责等待并监听来自客户端的连接请求。
- 接受连接: 每当有客户端连接时,服务端使用 accept() 方法接受该连接,并为该特定连接创建一个新的 Socket 对象。这个连接对象用于与连接的特定客户端进行通信。
- 处理多个客户端: 服务端可以同时处理多个客户端的请求,每个连接都由一个独立的 conn 对象管理。
②Socket客户端
- 发起连接: 客户端的作用是发起与服务端的连接请求。它通常只有一个主要的 Socket 对象,用于与服务端进行通信。
- 一次一连接: 一般来说,客户端一次只会与一个服务端建立连接。
二、Socket服务端编程
①导包并创建socket对象
import socket
socket_server=socket.socket()
②绑定socket_server到指定IP和地址
socket_server.bind((host, port))
bind()
方法:是 socket 对象的方法,用于将一个地址(主机名和端口号)绑定到一个 Socket 上,通常用于服务器端。- 参数
(host, port)
: 一个元组,其中 host 通常是 IP 地址,port 是整数形式的端口号。
【示例】
代码语言:python代码运行次数:0复制# 绑定socket_server到指定IP和地址
socket_server.bind(("localhost",8888))
localhost
:一个特殊的主机名,表示本地计算机。它允许服务端在本机上进行测试而不需要连接外部网络。8888
:要绑定的端口号。端口号用于标识特定的服务或进程。在这个例子中,服务端将在本地的 8888 端口上监听来自客户端的连接请求。
③服务端开始监听端口
socket_server.listen(backlog)
listen
方法:用于将 Socket 设置为被动模式,以便接收来自客户端的连接请求。# 服务端开始监听端口 socket_server.listen(1) # listen()方法内接收一个整数传参数,表示接受的链接数量- 参数
backlog
:为 int 整数,表示允许的连接请求数量。如果同时有多个客户端请求连接,超出该数量的请求将被拒绝。该参数是可选的,如果不指定,系统会自动设置一个合理的默认值。 【示例】
④接收客户端连接,获得连接对象
socket_server.accept()
accept
方法:一个阻塞方法,用于在服务器端接受客户端的连接请求。调用该方法后,如果没有客户端尝试连接,accept() 方法会阻塞并等待直到有一个客户端尝试连接。
该方法返回一个二元元组(conn, address)
,其中:
conn
:一个新的 Socket 对象,用于与连接的客户端进行通信。address
:客户端的地址(IP 地址和端口),提供了连接的客户端的信息。
【示例】
代码语言:python代码运行次数:0复制# 等待客户端链接
conn,address=socket_server.accept()
"""
上面这行代码等价于
result:tuple=socket_server.accept()
conn=result[0]
address=result[1]
"""
【分析】
accept
方法返回的是二元元组(conn, address)
,可以通过变量1,变量2=socket_server.accept()
的形式直接接收二元元组内的两个元素。
⑤接收客户端发送的消息
conn.recv(bytes)
recv
方法:一个阻塞方法,用于接收连接中发来的数据。如果接收到的数据小于指定的字节数,方法将返回已接收的数据;如果没有数据可接收,程序会等待,直到有数据到达为止。- 参数
bytes
: 一个 int 整数,指定要接收的最大字节数。
【注意】
recv
方法通过conn
调用而不是socket_server
调用。因为conn
是专用于与特定客户端进行通信的 Socket 对象,而conn
是用于监听和接受连接的服务器 Socket。
【示例】
代码语言:python代码运行次数:0复制data=conn.recv(1024).decode("UTF-8")
- recv()方法接收的参数为缓冲区大小,一般给1024即可。
- recv()方法的返回值为一个字节数组,即bytes对象,不是字符串,可以通过decode()方法进行UTF-8编码,将字节数组转换为字符串对象。
⑥回复客户端信息
conn.send(data)
send
方法:用于将数据发送给连接的客户端# 通过conn(客户端当次连接对象),调用send方法可以回复消息 conn.send(msg.encode("UTF-8"))在这个示例中,conn.send() 方法接收的参数是通过 encode("UTF-8") 将字符串转换为字节后的结果。这样,客户端才能正确接收和处理该信息。- 参数
data
: 一个字节序列(bytes 对象),表示要发送的数据。 【示例】
⑦关闭连接
conn(客户端当次连接对象)和socket_server对象调用close方法,关闭连接。
conn.close()
socket_server.close()
⑧测试
下载网络调试助手作为客户端进行测试。
下载地址:https://github.com/nicedayzhu/netAssist/releases
服务端代码示例:
代码语言:python代码运行次数:0复制# 导包
import socket
# 创建socket对象
socket_server=socket.socket()
# 绑定socket_server到指定IP和地址
socket_server.bind(("localhost",8888))
# 服务端开始监听端口
socket_server.listen(1) # listen()方法内接收一个整数传参数,表示接受的链接数量
# 等待客户端链接
conn,address=socket_server.accept()
print(f"接收到了客户端的链接,客户端信息为{address}")
while True:
# 接收客户端连接,获得连接对象
data=conn.recv(1024).decode("UTF-8")
print(f"客户端发来的消息:{data}")
# 通过conn(客户端当次连接对象),调用send方法可以回复消息
msg=input("请输入回复客户端的信息:")
if msg=='exit':
break
conn.send()
# 关闭连接
conn.close()
socket_server.close()
运行服务端代码
双击打开安装好的网络调试助手netAssist
配置netAssist相关环境,点击“开始连接”
连接成功
客户端发送消息
服务端成功接收消息
输入 "exit",代码中的 break 语句会被执行,从而终止当前的循环,服务端会停止向客户端发送消息,并最终结束程序。
三、Socket客户端编程
主要分为如下几个步骤:
①导包并创建socket对象
import socket
socket_client=socket.socket()
②连接到服务端
socket_client.connect((host, port))
connect()
方法:一个阻塞方法,用于客户端连接到服务器。如果服务器没有响应,connect() 会阻塞,直到连接成功或发生超时。
【示例】
代码语言:python代码运行次数:0复制# 绑定socket_server到指定IP和地址
socket_client.connect(("localhost",8888))
③发送消息
socket_client.send(data)
# 发送消息
socket_client.send("你好呀".encode("UTF-8"))
④接收服务端消息
socket_client.recv(bytes)
# 接收返回信息
recv_data=socket_client.recv(1024) # 1024为缓冲区大小,一般给1024即可
⑤关闭链接
socket_client.close()
四、服务端与客户端相互通讯
服务端示例代码:
代码语言:python代码运行次数:0复制# 导包
import socket
# 创建socket对象
socket_server=socket.socket()
# 绑定socket_server到指定IP和地址
socket_server.bind(("localhost",8888))
# 服务端开始监听端口
socket_server.listen(1) # listen()方法内接收一个整数传参数,表示接受的链接数量
# 等待客户端链接
conn,address=socket_server.accept()
print(f"接收到了客户端的链接,客户端信息为{address}")
while True:
# 接收客户端连接,获得连接对象
data=conn.recv(1024).decode("UTF-8")
print(f"客户端发来的消息:{data}")
# 通过conn(客户端当次连接对象),调用send方法可以回复消息
msg=input("请输入回复客户端的信息:")
if msg=='exit':
break
conn.send()
# 关闭连接
conn.close()
socket_server.close()
客户端示例代码:
代码语言:python代码运行次数:0复制# 导包
import socket
# 创建socket对象
socket_client=socket.socket()
# 绑定socket_server到指定IP和地址
socket_client.connect(("localhost",8888))
# 发送消息
socket_client.send("你好呀".encode("UTF-8"))
while True:
# 发送消息
msg = input("请输入要给服务端发送的消息:")
if msg == 'exit':
break
socket_client.send(msg.encode("UTF-8"))
# 接收返回消息
recv_data = socket_client.recv(1024) # 1024是缓冲区的大小,一般1024即可。 同样recv方法是阻塞的
print(f"服务端回复的消息是:{recv_data.decode('UTF-8')}")
# 关闭链接
socket_client.close()
先启动服务端再启动客户端。
服务端接收到客户端发来的信息并回复客户端。
客户端接收到服务端发来的信息并回复服务端。
输入 "exit",代码中的 break 语句会被执行,从而终止当前的循环,服务端会停止向客户端发送消息,并最终结束程序。
思考:为什么服务端会比客户端多一个conn对象?
回答:服务端与客户端在通信中扮演着不同的角色。服务端需要接收来自多个客户端的连接,每当一个客户端连入时,服务端通过accept()方法创建一个新的连接对象conn,用于与该特定客户端进行数据交换。这样,服务端可以同时处理多个客户端的请求,而客户端每次只与一个服务端连接。