网络交互离不开socket,浏览器访问数据是请求-应答模式,服务器收到数据后会根据请求地址解析并返回对应文件内容(io)。本文以tcp为例,使用python3模拟服务端,使用浏览器作为客户端,来实现一个线程为多个浏览器客户端服务的示例。
先来看下通常的服务端启动程序,服务端根据host和port创建套接字,注意这里要调用listen方法开始监听,然后才能accept接收客户端连接。
(hostport): server = socket.socket(socket.AF_INETsocket.SOCK_STREAM) server.bind((hostport)) server.listen() server
传统的做法是服务端socket在无限循环中accept,然而accept方法默认是阻塞的,会阻塞在那里直到有客户端来连接,所以单线程时无法为多个客户端同时服务。
: clientaddr = server.accept()
如果能解阻塞,那么线程自然可以继续向下执行提供服务,我们可以将服务端socket设置为非阻塞。注意这里要捕获异常,防止没有客户端连接到来导致程序异常退出。
server.setblocking(False)
: : clientaddr = server.accept() : : (% addr)
以上我们就解决了accept阻塞的问题,接下来就可以根据客户端socket进行相应的读写处理了。使用recv方法进行数据读取,这里简单设置为每次读取1024字节。
data = client.recv(1024)
因为客户端socket默认也是阻塞的,所以recv方法会阻塞等待数据到来,所以想要线程继续向下执行,我们要解决recv阻塞问题。同服务端socket设置,将客户端socket设置为非阻塞。
server.setblocking(False)
: : clientaddr = server.accept() : : (% addr) client.setblocking() : data = client.recv() : : data: (% ((addr)data.decode())) : client.close()
以上我们就实现了客户端非阻塞,这样客户端socket接收数据也变成非阻塞的了,注意同样需要捕获异常,防止程序异常退出。现在可以接收多个客户端请求了,但是无法为客户端服务,因为服务端只接受了客户端数据,还没有做出响应。先来实现一个响应客户端的方法。
(client): response = response += response += response += % random.randint() client.send(response.encode()) client.close()
响应有了,依然要考虑新的问题,服务端也不知道服务哪个client,所以我们需要找一个地方把连接进来的客户端socket保存起来然后循环迭代处理。最终整理如下,把阻塞的地方消灭掉,使得线程可以继续向下执行,完成一个进程下一个线程为多个客户端服务。
randomsocket(hostport): server.setblocking() serverclients = ()(): server = bind() : : clientaddr = server.accept() : : (% addr) client.setblocking() clients.append(client) cli clients: : data = cli.recv() : : data: (% ((addr)data.decode())) send(cli) : clients.remove(cli) cli.close()__name__ == : main()
下面我们使用浏览器访问一下,http://127.0.0.1:8000,效果如下: