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

 

 

 




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee