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 已关闭,监听进程退出 ...