在这里简单实现一个TCP服务器,用于监听来自客户端的连接,接收客户端发送的消息,并向客户端发送响应消息。下面我将详细解释这个代码的工作原理和各个部分的作用。
首先建立一个客户端(client),一个服务端(server),在客户端进行连接到运行在本地机器(IP地址为127.0.0.1
)上的TCP服务器,该服务器监听8080
端口。客户端通过标准输入接收用户输入的消息,将这些消息发送给服务器,并接收来自服务器的响应。
client:
- 导入必要的模块:
import socket, json
:这里导入了socket
模块用于网络通信,同时也导入了json
模块,尽管在这个示例中json
模块并未被使用。如果客户端和服务器之间需要交换复杂的数据结构(如字典或列表),则可以使用json
模块来序列化和反序列化这些数据。
- 创建socket套接字:
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
:创建了一个socket对象sk
,用于IPv4 (AF_INET
) 和TCP (SOCK_STREAM
) 通信。
- 连接到服务器:
sk.connect(("127.0.0.1", 8080))
:客户端通过connect
方法连接到服务器的IP地址和端口号。这里使用的是本地回环地址127.0.0.1
和端口号8080
。
- 发送和接收消息:
- 客户端进入一个无限循环,等待用户输入消息。
- 使用
input
函数获取用户输入的消息,并通过sk.send(msg.encode("utf-8"))
将消息编码为UTF-8格式后发送给服务器。 - 发送消息后,客户端通过
sk.recv(10241024)
尝试从服务器接收响应。但这里有一个错误:recv
方法的参数应该是单个整数,表示要接收的最大字节数。10241024
被解释为1024
(因为Python会尝试将这样的数字作为整数处理,而10241024
超出了int
类型的表示范围,但Python会在这里静默地截断它),这显然不是预期的行为。正确的做法应该是指定一个合理的字节数,比如1024
或4096
,这取决于您期望接收的消息大小。 - 接收到的数据通过
res.decode("utf-8")
解码为UTF-8格式的字符串,并打印出来。
- 关闭套接字:
sk.close()
:关闭socket连接。然而,在这个示例中,sk.close()
被放在了无限循环之外,这意味着它实际上永远不会被执行,因为程序会一直在循环中等待用户输入。在实际情况中,如果您希望在发送完所有消息后关闭连接,您可能需要将sk.close()
放在一个能够触发关闭的条件块中,比如一个接收到特定退出消息的分支内,或者在使用了足够长时间后通过其他方式触发关闭。
import socket
# 创建socket套接字
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接 connect
sk.connect(("127.0.0.1", 8080))
print("链接成功")
while True:
msg = input("输入要对服务器端说的话:")
sk.send(msg.encode("utf-8"))
print("发送成功\n请等待服务器响应……")
res = sk.recv(1024)
res1 = res.decode("utf-8")
print("服务器端说了",res1)
# 关闭套接字 close()
sk.close()
然后建立一个服务端(server)监听本地机器上的8080端口,等待客户端的连接,并与连接的客户端进行双向通信
server:
- 导入必要的模块:
import socket
:导入Python的socket
模块,用于网络通信。import json
:尽管在这个示例中并未使用到json
模块,但它通常用于数据的序列化和反序列化,以便在网络中传输复杂的数据结构(如字典、列表等)。
- 创建TCP/IP套接字:
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
:创建一个socket对象sk
,指定使用IPv4 (AF_INET
) 和TCP (SOCK_STREAM
) 协议。
- 绑定套接字到本地地址和端口:
sk.bind(("127.0.0.1", 8080))
:将sk
套接字绑定到本地回环地址127.0.0.1
的8080端口上。这样,服务器就只能在本地机器上监听该端口上的连接请求。
- 监听连接:
sk.listen()
:使服务器进入监听状态,准备接受客户端的连接请求。此时,服务器可以处理多个连接请求,但每次只处理一个,直到该连接关闭。
- 通知用户服务器已启动:
print("服务器启动完成,等待连接……")
:在控制台上打印一条消息,通知用户服务器已经启动并等待客户端的连接。
- 接受客户端连接:
- 服务器进入一个无限循环,不断使用
sk.accept()
方法等待并接受客户端的连接。accept()
方法会阻塞,直到一个连接到达。当连接到达时,它返回一个包含新连接的socket (conn
) 和连接客户端的地址 (addr
) 的元组。
- 服务器进入一个无限循环,不断使用
- 与客户端通信:
- 使用
conn.recv(1024)
从客户端接收最多1024字节的数据,并将其解码为UTF-8格式的字符串。 - 检查接收到的数据是否为空字符串(
""
),如果是,则认为客户端已经关闭了连接,并继续循环等待下一个连接。 - 如果接收到非空字符串,则打印该字符串,表示客户端发送的消息。
- 使用
input()
函数从标准输入获取要发送给客户端的消息,并将其编码为UTF-8格式后发送给客户端。
- 使用
- 关闭套接字:
- 需要注意的是,
sk.close()
被放在了无限循环之外,这意味着它实际上永远不会被执行,因为程序会一直在循环中等待客户端连接。在真实的应用场景中,sk.close()
应该被放置在能够确保服务器不再需要接受新连接的位置,比如接收到一个特定的退出命令后,或者在服务器被外部事件(如接收到SIGINT信号)强制关闭时。 - 然而,对于每个客户端连接,当通信完成后,应该关闭与之关联的
conn
套接字,以释放资源。在这个示例中,conn.close()
应该在发送完消息给客户端之后被调用,但在循环的末尾之前(否则循环将无法继续接受新的连接)。然而,由于这个示例没有实现客户端断开连接的明确检测(除了检查空字符串外),因此在实际应用中可能需要更复杂的逻辑来处理客户端的断开和套接字的关闭。
- 需要注意的是,
# 导入socket模块,用于网络连接和通信
import socket
# 导入json模块,可能用于数据的序列化和反序列化,尽管在示例中未使用
import json
# 创建一个TCP/IP socket
sk = socket.socket(family=socket.AF_INET,
type=socket.SOCK_STREAM)
# 绑定socket到本地IP地址和端口号,用于监听进来的连接
# 绑定端口 bind()
sk.bind(("127.0.0.1", 8080))
# 开始监听socket,允许进来的连接排队等待处理
# 监听 listen()
sk.listen()
# 通知用户服务器已经启动并等待连接
print("服务器启动完成,等待连接……")
# 进入无限循环,等待客户端连接
while True:
# 接受一个客户端连接,返回一个新的socket用于与客户端通信
# 接收连接 accept()
conn, addr = sk.accept()
# 通知用户客户端连接成功
print(f"{addr}连接成功")
# 从客户端接收数据,假设数据是UTF-8编码的文本
res = conn.recv(1024)
res = res.decode("utf-8")
# 检查客户端是否已断开连接,如果是,则重新开始循环等待下一个客户端
if res == "":
print("断开连接")
continue
# 打印客户端发送的消息
else:
print("客户端说了:", res)
# 从用户输入获取要发送给客户端的消息
msg = input("请输入要对客户端说的话:")
# 将消息编码为UTF-8并发送给客户端
conn.send(msg.encode("utf-8"))
# 通知用户消息发送成功,等待客户端响应
print("发送成功!\n请等待客户端响应……")
# 关闭socket,结束程序
# 关闭套接字 close()
sk.close()