Code Crafting

There are two equivalence classes of idiots: Those who yearn for the past that never was. AND Those who dream of the future that never will be.

0%

python socket编程之---TCP通信

学习过计算机网络,我们就知道传输层依靠TCP与UDP协议进行消息传输,socket是封装了TCP与UDP协议的一个面向程序员的接口。我们在网络编程时,会经常用到socket,下面就讲讲如何用python的socket实现服务端与客户端之间的TCP通信

概述

TCP通信,需要有一个服务端,一个或多个客户端。服务端监听端口,等待连接。客户端访问端口,请求连接。

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 导入socket库
import socket

# 创建一个socket, AF_INET表示使用IPV4,SOCK_STREAM表示使用TCP协议
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 与服务端连接,addr是服务端的地址与端口
addr = ("127.0.0.1", 8888)
s.connect(addr)

# 建立连接后就可以发送消息了
message = "hello"
s.sendall(message.encode())

# 接受消息并打印出来
# recv(max) 表示一次接收多少字节的数据,这里是1024字节,也可以是2048等等。
recv_datagram = s.recv(1024)
if recv_datagram:
recv_message = recv_datagram.decode()
print(f"receive message {recv_message} from server")

s.close()

如果想用ipv6,那么就用socket.AF_INET6

addr是一个tuple,包含两个元素,第一个是服务端的ip地址,第二个是要连接的端口

发送消息可以使用send方法或sendall方法。send方法可能不会一次性发送所有的数据,而是尽力发送尽可能多的数据。返回值是实际发送的字节数。sendall方法会一直发送数据,直到所有数据都被发送完毕或者发生错误。它不返回实际发送的字节数,而是在发送完成或者发生错误时引发异常。

服务端

服务端相比于客户端更加复杂,因为服务端可能会收到来自多个客户端的连接请求,所以需要使用多线程来处理。对每个客户端的连接,都创建一个新的线程

先给出一个完整可运行的服务端代码,下面再做具体的解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 导入socket库
import socket

# 创建一个socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址与端口,在下面会详细提到
addr = ("0.0.0.0", 8888)
s.bind(addr)
# 开始监听,设置队列中可以排队等待的最大连接数为5
s.listen(5)

# 一直监听,如果有新的TCP连接,就进行处理
while True:
try:
# 接受连接
sock, addr = s.accept()
# 创建一个新的线程来处理TCP连接
thread = threading.Thread(target=client_thread, args=(sock, addr))
thread.start()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 实现服务端的具体功能
def client_thread(sock, addr):
while True:
# 接收消息
data = sock.recv(1024)
message = data.decode()

if message == '':
# 没有收到消息,客户端断开连接
break
else:
# 收到消息,进行处理
print(f"receive message:{message} from client")
back = 'receive'
socket.sendall(back.encode())

sock.close()

绑定地址与端口时,”0.0.0.0”表示监听所有可用的网络接口,如果使用”127.0.0.1”则表示只接受来自本机的连接。如果运行代码的服务器有固定的ip地址,可以直接使用该ip地址。

listen方法的参数表示可以在连接队列中等待的最大连接数,这个参数通常被称为backlog。如果队列已满,后续的连接请求可能会被拒绝或者等待,直到队列有空间为止。backlog的设置取决于程序的性质和系统情况。一般来说默认设置为5。

thread创建新线程时,如果只有一个参数,参数需要写成如下形式args=(sock,),从而保证是个tuple

更优雅的服务端写法

有时候,服务端要实现很多功能,需要很多函数和变量。这时候再一个个写函数,就会有些繁琐。可以用面向对象来实现服务端功能。

实现一个ClientThread类,继承Thread类,实现run方法。在创建新线程时,只需要实例化该类并调用start方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import socket
from threading import Thread

server_addr = ("127.0.0.1", 33333)
server_socket = socket.socket(AF_INET, SOCK_STREAM)
server_socket.bind(server_addr)

class ClientThread(Thread):
def __init__(self, client_addr, client_socket):
Thread.__init__(self)
self.client_addr = client_addr
self.client_socket = client_socket

def run(self):
message = ""
while True:
data = self.client_socket.recv(1024)
message = data.decode()

if message == "":
break
else:
self.process_message(message)

def process_message(self, message):
# 处理逻辑
...

while True:
server_socket.listen()
sockt, addr = server_socket.accept()
client_thread = ClientThread(addr, sockt)
client_thread.start()