学习过计算机网络,我们就知道传输层依靠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
| import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
addr = ("127.0.0.1", 8888) s.connect(addr)
message = "hello" s.sendall(message.encode())
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
| import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
addr = ("0.0.0.0", 8888) s.bind(addr)
s.listen(5)
while True: try: sock, addr = s.accept() 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()
|