Jacky Liu's Blog
Python socket 基本传输实验
---- 电脑互联以后的文件共享、samba 这些只是为了开发时的方便,系统运行时的内部信息传输还是靠底层的网络传输机制,或者说 socket。对我这样的网络白痴来说,一切得从零开始。幸好 Python 用起来很方便,短时间内(不考虑犯懒的因素)把 socket 摸索到实际能用的程度也不是幻想。
---- 下面一个测试程序算是个总结,除了演示 socket 基本的通信以外,也演示了客户端或者服务端挂掉以后会发生什么。先说一下两个基本考虑:
第一,用 TCP socket 而不用 UDP socket。原因不总结了,我也不太懂,反正前一个用的最多。
第二,用默认的 blocking socket,对每一个连接都开一个发送线程和一个接收线程,Windows 和 Linux 都是,不用 non-blocking socket + select() 的方式。(参考 Python 文档里的 "socket programming howto")。如果用后一种就要给 select() 函数设 timeout,不设肯定会吃掉 100% CPU,系统还有许多其它事要做,不能让网络传输占掉太多资源。但是设的话又不知设多少是好,如果要智能控制——有流量时加快,没流量时减慢,又太麻烦, 而且不一定好得过系统的线程调度。相比之下,前一种方式简单而且容易管理得多。(补记:这里想差了。如果把 select() 放进一个单独的线程里,那么 timeout 设多少可能是无关紧要的,但还是 blocking socket 比较简单。)
---- 测试程序:
# -*- encoding: utf-8 -*- ''' 实验平台: Ubuntu 12.04 LTS / Linux 3.2.0-25-generic-pae 实验目标: 模拟客户端程序异常终止的情况,检视服务端 socket 会有什么样的行为 实验过程: 1. 开一个监听进程,使用全局 server socket,执行无限循环监听指定端口。监听到连接后会开启一个接收线程和一个 发送线程对连接进行操作。 2. 主进程开启一个客户线程模拟客户端,连上服务端 socket 以后进行一次发送和一次接收操作,然后在未关闭 socket 的情况下终止运行,模拟客户端异常终止的情况。 3. 服务端的发送线程和接收线程分别终止以后,在主进程里关闭全局 server socket,使监听进程终止,整个实验程序 终止运行。 实验结果: 客户端程序异常终止以后,服务端 socket 的 sendall() 操作会抛出异常导致发送线程终止,recv() 操作返回 b'', 表示连接已损坏,导致接收线程终止。 ''' import socket import multiprocessing import time import threading __host_server__= 'localhost' __port_server__= 9000 __host_client__= 'localhost' __port_client__= 9001 #==================================== ↓ 以下是服务端 ↓ ==================================== # 全局 socket,最后在主进程内关闭。 sock_server= socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_server.bind((__host_server__, __port_server__)) sock_server.listen(1) # 开始监听 def _服务端监听进程(): while True: # 此循环只执行一次。 try: conn, addr= sock_server.accept() except: print('_服务端监听进程() -- server socket 已关闭,监听进程退出 ...') break print('_服务端监听进程() -- 接收到来自 ' + str(addr) + ' 的连接。') 发送线程= threading.Thread(target=_服务端发送线程, name='发送线程', kwargs={'conn':conn}) 发送线程.start() 接收线程= threading.Thread(target=_服务端接收线程, name='接收线程', kwargs={'conn':conn}) 接收线程.start() def _服务端发送线程(conn): __msg__= b'0123456789' count= 0 while True: try: conn.sendall(__msg__) except: print('_服务端发送线程() -- 连接已损坏,发送线程终止。') break count += 1 print('_服务端发送线程() -- 已发 ' + str(count) + ' 条。') time.sleep(0.1) # 间隔 0.1 秒发送 def _服务端接收线程(conn): while True: data= conn.recv(8192) if data: print('_服务端接收线程() -- 接收到客户端信息: ' + data.decode('utf-8')) else: print('_服务端接收线程() -- 连接已损坏,接收线程终止。') break try: conn.shutdown(socket.SHUT_RDWR) except: pass conn.close() #==================================== ↓ 以下是客户端 ↓ ==================================== def _客户端线程(): ''' ''' sock_client= socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_client.bind( (__host_client__, __port_client__) ) sock_client.connect( (__host_server__, __port_server__) ) print('_客户端线程() -- 客户端已连接,开始发送 ...') sock_client.sendall(b'Hello, World !') data= sock_client.recv(8192) print('_客户端线程() -- 接收到服务端发来的数据: ' + data.decode('utf-8')) # XXX: 模拟客户端挂掉,无预警退出 print('_客户端线程() -- 本客户端非正常退出 ...') #==================================== ↓ 以下是主进程 ↓ ==================================== # 启动监听进程 监听进程= multiprocessing.Process(name='监听进程', target=_服务端监听进程) 监听进程.start() time.sleep(1) 客户线程= threading.Thread(target=_客户端线程, name='客户线程') 客户线程.start() # 关闭监听进程,不然整个程序没法退出。 time.sleep(1) sock_server.shutdown(socket.SHUT_RDWR) sock_server.close()
---- 下面是运行结果:
_客户端线程() -- 客户端已连接,开始发送 ...
_客户端线程() -- 接收到服务端发来的数据: "0123456789"
_客户端线程() -- 本客户端非正常退出 ...
_服务端监听进程() -- 接收到来自 ('127.0.0.1', 9001) 的连接。
_服务端接收线程() -- 接收到客户端信息: "Hello, World !"
_服务端接收线程() -- 连接已损坏,接收线程终止。
_服务端发送线程() -- 已发 1 条。
_服务端发送线程() -- 连接已损坏,发送线程终止。
_服务端监听进程() -- server socket 已关闭,监听进程退出 ...