首页 > 脚本语言 > python > Python实例浅谈之七socket编程
2015
07-24

Python实例浅谈之七socket编程

一、简介

 

 PythonSocket套接字中的异步、多线程等特性对服务器性能有很大影响,总结一些关键的框架以便开发过程中很快找到提高并发性性能的模型。

 

二、流程图

 

(1)TCP连接流程如下图:

Python实例浅谈之七socket编程 - 第1张  | 大话运维

 SocketTCP服务器编程步骤:1、打开socket,2、绑定到一个地址和端口,3、侦听进来的连接,4、接受连接,5、读写数据,6、关闭socket。
SocketTCP客户端编程步骤:1、打开socket,2、连接到服务器,3、读写数据,4、关闭socket。
(2)UDP连接流程如下图:
Python实例浅谈之七socket编程 - 第2张  | 大话运维
 SocketUDP服务器编程步骤:1、打开socket,2、绑定到一个地址和端口,3、接收客户端数据,4、发送数据,6、关闭。
SocketUDP客户端编程步骤:1、打开socket,2、绑定到地址和端口(可省略),3、发送数据,4、接收数据,5、关闭。

三、详解

 

1、SocketServer模块的Fork方式

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
#!/usr/bin/python
#encoding=utf-8
 
from SocketServer import TCPServer, ForkingMixIn, StreamRequestHandler
import time
 
class Server(ForkingMixIn, TCPServer): #自定义Server类
pass
 
class MyHandler(StreamRequestHandler):
 
def handle(self): #重载handle函数
addr = self.request.getpeername()
print 'Get connection from', addr #打印客户端地址
time.sleep(5) #休眠5秒钟
self.wfile.write('This is a ForkingMixIn tcp socket server') #发送信息
 
host = ''
port = 1234
server = Server((host, port), MyHandler)
 
server.serve_forever() #开始侦听并处理连接

多个连接同时到达服务器端的时候,每个连接主进程都生成一个子进程专门用来处理此连接,而主进程则依旧保持在侦听状态。因主进程和子进程是同时进行的,所以不会阻塞新的连接。但由于生成进程消耗的资源比较大,这种处理方式在有很多连接的时候会带来性能问题。Server类须继承ForkingMixIn和TCPServer两个类。
客户端测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
 
#!/usr/bin/python
#encoding=utf-8
 
import socket
 
s = socket.socket() #生成一个socket对象
server = socket.gethostname()
port = 1234
s.connect((server, port)) #连接服务器
print s.recv(1024) #读取数据
s.close() #关闭连接

 服务器端运行结果:
Python实例浅谈之七socket编程 - 第3张  | 大话运维

2、SocketServer模块的线程方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
#!/usr/bin/python
#encoding=utf-8
 
from SocketServer import TCPServer, ThreadingMixIn, StreamRequestHandler
import time
 
class Server(ThreadingMixIn, TCPServer): #自定义Server类
pass
 
class MyHandler(StreamRequestHandler):
 
def handle(self): #重载handle函数
addr = self.request.getpeername()
print 'Get connection from', addr #打印客户端地址
time.sleep(5) #休眠5秒钟
self.wfile.write('This is a ForkingMixIn tcp socket server') #发送信息
 
host = ''
port = 1234
server = Server((host, port), MyHandler)
 
server.serve_forever() #开始侦听并处理连接


3、SocketServer模块的Threading线程池方式

Python实例浅谈之七socket编程 - 第4张  | 大话运维

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
import SocketServer
class MyTCPHandler(SocketServer.BaseRequestHandler):
def handle(self):
while True:
self.data = self.request.recv(1024).strip()
cur_thread = threading.current_thread()
print cur_thread
if not self.data:
print "client:%s leave!" % self.client_address[0]
break
print "%s wrote:%s" % (self.client_address[0], self.data)
self.request.sendall(self.data.upper())
 
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
server = SocketServer.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()

Python实例浅谈之七socket编程 - 第5张  | 大话运维
 可以使用#telnet 127.0.0.1 1234命令进行测试服务器端。

4、异步I/O方式

使用Frok和线程方式,对连接时间长且数据突发的多连接来说占用的资源太多,异步I/O方式很好的解决了这一问题,Python标准库中有asyncore和asynchat模块用来实现这种处理方式,这些框架的实现方式依赖于select和poll方法。
(1)select方法
select方法是对指定的文件描述符进行监视,并在文件描述符集改变的时候做出响应。Python标准库中,具体实现是select模块中的select方法,它是select系统调用的一个接口。select函数有3个必须的参数和1个可选的时间参数,前3个参数为文件描述符列表,分别表示等待输入、输出和错误的文件描述符;可选的时间参数为一个浮点数,用来指定系统监视文件描述符集改变的超时时间,若此参数被忽略,则将会阻塞到至少有一个文件描述符准备好的情况下才返回,若设置为0表示调用时无阻塞。select方法返回一个有3个列表值组成的元组,即为前3个参数中已经准备好的文件描述符,等待时间超时且没有任何已经准备好的文件描述符时则返回由3个空的列表组成的元组。

 

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
 
#!/usr/bin/python
#encoding=utf-8
 
import socket, select
 
s = socket.socket()  #生成socket对象
 
host = socket.gethostname()
port = 1234
s.bind((host, port)) #绑定套接字接口地址
s.listen(5)          #开始服务器端监听
 
inputs = [s]
while True:
rs, ws, es = select.select(inputs, [], []) #使用select方法
for r in rs:
if r is s:
c, addr = s.accept() #处理连接
print 'Get connection from', addr
inputs.append(c)
else:
try:
data = r.recv(1024) #接收数据
disconnected = not data
except socket.error:
disconnected = True
 
if disconnected:
print r.getpeername(), 'disconnected'
inputs.remove(r)
else:
print data #打印接收到的数据

客户端测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
#!/usr/bin/env python
 
from socket import *
 
HOST = 'localhost'
PORT = 1234
BUFSIZ = 1024
ADDR = (HOST, PORT)
 
tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)
 
while True:
data = raw_input('> ')
if not data:
break
tcpCliSock.send(data)
tcpCliSock.close()

运行结果:
Python实例浅谈之七socket编程 - 第6张  | 大话运维
inputs列表变量用来记录需要处理输入的socket对象,可以有多个,也可以加入多sys.stdin的监听。select没有超时时间参数,则会阻塞到3个列表中至少有一个文件描述符准备好。运行多个客户端,可以看到服务器可以处理多连接的情况。
其他的select客户端:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
 
#!/usr/bin/python
#encoding=utf-8
import select
import socket
import Queue
 
#create a socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setblocking(False)
#set option reused
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR  , 1)
 
server_address= ('192.168.1.102',10001)
server.bind(server_address)
 
server.listen(10)
 
#sockets from which we except to read
inputs = [server]
 
#sockets from which we expect to write
outputs = []
 
#Outgoing message queues (socket:Queue)
message_queues = {}
 
#A optional parameter for select is TIMEOUT
timeout = 20
 
while inputs:
print "waiting for next event"
readable , writable , exceptional = select.select(inputs, outputs, inputs, timeout)
 
# When timeout reached , select return three empty lists
if not (readable or writable or exceptional) :
print "Time out ! "
break;
for s in readable :
if s is server:
# A "readable" socket is ready to accept a connection
connection, client_address = s.accept()
print "    connection from ", client_address
connection.setblocking(0)
inputs.append(connection)
message_queues[connection] = Queue.Queue()
else:
data = s.recv(1024)
if data :
print " received " , data , "from ",s.getpeername()
message_queues[s].put(data)
# Add output channel for response
if s not in outputs:
outputs.append(s)
else:
#Interpret empty result as closed connection
print "  closing", client_address
if s in outputs :
outputs.remove(s)
inputs.remove(s)
s.close()
#remove message queue
del message_queues[s]
for s in writable:
try:
next_msg = message_queues[s].get_nowait()
except Queue.Empty:
print " " , s.getpeername() , 'queue empty'
outputs.remove(s)
else:
print " sending " , next_msg , " to ", s.getpeername()
s.send(next_msg)
 
for s in exceptional:
print " exception condition on ", s.getpeername()
#stop listening for input on the connection
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
#Remove message queue
del message_queues[s]

 Client端创建多个socket进行server的测试程序:

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
 
#!/usr/bin/python
#encoding=utf-8
 
import socket
messages = ["This is the message" ,
"It will be sent" ,
"in parts "]
 
print "Connect to the server"
server_address = ("192.168.1.102",10001)
#Create a TCP/IP sock
socks = []
for i in range(10):
socks.append(socket.socket(socket.AF_INET,socket.SOCK_STREAM))
for s in socks:
s.connect(server_address)
 
counter = 0
for message in messages :
#Sending message from different sockets
for s in socks:
counter+=1
print "  %s sending %s" % (s.getpeername(),message+" version "+str(counter))
s.send(message+" version "+str(counter))
#Read responses on both sockets
for s in socks:
data = s.recv(1024)
print " %s received %s" % (s.getpeername(),data)
if not data:
print "closing socket ",s.getpeername()
s.close()

(2)poll方法

 poll方法应用很广泛 ,在需要同时为很多连接服务的时候比较有用。以为select方法采用的是一种位图索引的方式来处理文件描述符而poll方法则仅仅只需要处理感兴趣的文件描述符,所以select与最大的文件描述符是一致的而poll与文件描述符的个数是一致的,poll方法的这种特点可以有效的降低服务器的处理负担。       pool方法在select模块中,当调用poll方法时将得到一个Polling类对象,该对象有register、unregister和poll三个方法,poll方法有一个可选的超时参数,若被忽略、为负数或为0,则调用此方法将阻塞到至少有一个事件到达。poll方法将返回(fd,event)对的列表,其中fd为文件描述符,event用来指示发生的事件,event是一个位掩码通过一个整数的位来对应特定的事件信息,若需要知道特定的事件是否发生,可以使用&操作符。

 poll方法的事件信息:

Python实例浅谈之七socket编程 - 第7张  | 大话运维

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
34
35
36
37
38
39
 
#!/usr/bin/python
#encoding=utf-8
 
import socket, select
 
s = socket.socket()   #生成socket对象
 
host = socket.gethostname()
port = 1235
s.bind((host, port))  #绑定套接字接口地址
 
fd_dict = {s.fileno(): s}
 
s.listen(5)           #开始服务器端监听
p = select.poll()     #生成Polling对象
p.register(s)         #注册socket对象
 
while True:
events = p.poll() #获取准备好的文件对象
for fd, event in events:
st = fd_dict[fd]
if st is s:
c, addr = s.accept()          #处理连接
print 'Got connection from', addr
p.register(c)
fd_dict[c.fileno()] = c       #加入连接socket
 
elif event & select.POLLIN:
data = fd_dict[fd].recv(1024) #接收时间
if not data:
print fd_dict[fd].getpeername(), 'disconnected'
p.unregister(fd)          #取消注册
del fd_dict[fd]
else:
print data                #打印数据
 
<img src="http://img.blog.csdn.net/20150415162636050?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGFpeWFuZzE5ODc5MTI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" width="372" height="109" />
<span class="Apple-converted-space"> </span>其他的poll的server端:

#!/usr/bin/python
#encoding=utf-8
import socket
import select
import Queue

# Create a TCP/IP socket, and then bind and listen
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_address = (“192.168.1.102”, 10001)

print  “Starting up on %s port %s” % server_address
server.bind(server_address)
server.listen(5)
message_queues = {}
#The timeout value is represented in milliseconds, instead of seconds.
timeout = 1000
# Create a limit for the event
READ_ONLY = ( select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR)
READ_WRITE = (READ_ONLY|select.POLLOUT)
# Set up the poller
poller = select.poll()
poller.register(server,READ_ONLY)
#Map file descriptors to socket objects
fd_to_socket = {server.fileno():server,}
while True:
print “Waiting for the next event”
events = poller.poll(timeout)
print “*”*20
print len(events)
print events
print “*”*20
for fd ,flag in  events:
s = fd_to_socket[fd]
if flag & (select.POLLIN | select.POLLPRI) :
if s is server :
# A readable socket is ready to accept a connection
connection , client_address = s.accept()
print ” Connection ” , client_address
connection.setblocking(False)

fd_to_socket[connection.fileno()] = connection
poller.register(connection,READ_ONLY)

#Give the connection a queue to send data
message_queues[connection]  = Queue.Queue()
else :
data = s.recv(1024)
if data:
# A readable client socket has data
print ”  received %s from %s ” % (data, s.getpeername())
message_queues[s].put(data)
poller.modify(s,READ_WRITE)
else :
# Close the connection
print ”  closing” , s.getpeername()
# Stop listening for input on the connection
poller.unregister(s)
s.close()
del message_queues[s]
elif flag & select.POLLHUP :
#A client that “hang up” , to be closed.
print ” Closing “, s.getpeername() ,”(HUP)”
poller.unregister(s)
s.close()
elif flag & select.POLLOUT :
#Socket is ready to send data , if there is any to send
try:
next_msg = message_queues[s].get_nowait()
except Queue.Empty:
# No messages waiting so stop checking
print s.getpeername() , ” queue empty”
poller.modify(s,READ_ONLY)
else :
print ” sending %s to %s” % (next_msg , s.getpeername())
s.send(next_msg)
elif flag & select.POLLERR:
#Any events with POLLERR cause the server to close the socket
print ”  exception on” , s.getpeername()
poller.unregister(s)
s.close()
del message_queues[s]

5、asyncore模块

asyncore模块同样可以实现异步通信方式,该模块中提供了用来构建异步通信方式的客户端和服务器端的基础架构,特别适用于聊天类的服务器和协议的实现。其基本思想是创建一个或者多个网络信道(它是socket对象的一个封装),当信道创建后通过调用loop方法来激活网络信道的服务,直到最后一个网络信道关闭。
 asyncore模块中,主要用于网络事件循环检测的loop方法是其核心,在loop方法中将会通过select方法来检测特定的网络信道,当select方法返回有事件的socket对象后,loop方法检查此事件和套接字状态并创建一个高层次的事件信息,然后针对该信息调用相应的方法,asyncore提供了底层的API用来创建服务器。
Python帮助手册中的代码,演示了asyncore模块的使用方法:

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
34
 
#!/usr/bin/python
#encoding=utf-8
 
import asyncore, socket
 
class HttpClient (asyncore.dispatcher): #定义了一个HttpClient类
 
def __init__(self, host, path): #类的构造函数
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) #创建socket对象
self.connect( (host, 80) )
self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path
 
def handle_connect(self): #连接调用接口
pass
 
def handle_close(self): #接口关闭函数
self.close()
 
def handle_read(self): #读取数据
print self.recv(1024)
 
def handle_write(self): #写入数据
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
 
def writable(self): #判断是否写入数据
return (len(self.buffer) > 0)
 
if __name__ ==__main__’:
c = HttpClient('www.python.org', '/')
 
asyncore.loop() #开始异步通信处理方式

6、Twisted网络框架

Twisted框架是一个面向对象基于事件驱动的顶级通信框架,可以完成大部分的网络应用任务;同时Twisted框架具有良好的网络性能,提供了异步通信机制,可与C++的ACE(自适应网络通信环境)网络架构媲美。
twisted 框架编写的服务器有几个基本的元素:a、应用程序对象(application),管理应用程序资源的对象,一个应用程序可以管理多个service对象;b、服务(service),服务对象启动监听的端口;c、协议工厂(factory),当客户端连接到服务器时,用来创建协议对象;d、协议(protocol),每个协议对象对应一个网络连接,协议类处理网络协议(如http,ftp,自定义协议等)。twisted框架内部运行依赖的元素:a、reactor异步事件的主要循环处理类,负责监控事件,调用注册的回调函数提供服务(在linux上主要使用epoll/select来实现);b、defer异步回调序列,当序列被执行的时候,顺序执行注册的回调函。
Twisted框架中,已经提供了许多可重用的协议和接口,安装完毕后,在site-packages目录(/usr/lib/python2.6)下将会生成一个twisted的目录,在其protocols目录下有这些协议的实现。Twisted由模块化的组件组成,模块化的元素包括协议、工厂、反应器和Deferred对象等,工厂用来产生一个新的实例,一个实例可以产生一个类型的协议,这些协议定义了如何和服务器交换数据,在运行的时候每次连接都会产生一个协议实例。
(1)Twisted框架下服务器的实现:

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
 
#!/usr/bin/python
#encoding=utf-8
 
from twisted.internet import reactor
from twisted.internet.protocol import Protocol, Factory
 
class EchoServer(Protocol):
 
def connectionMade(self):             #连接建立的时候
print 'Get connection from', self.transport.client
self.factory.numProtocols = self.factory.numProtocols+1
if self.factory.numProtocols > 2: #当连接超过2个的时候,断开连接
self.transport.write("Too many connections, try later\n")
self.transport.loseConnection()
return
print 'Get connection from', self.transport.client
 
def connectionLost(self, reason):     #断开连接
self.factory.numProtocols = self.factory.numProtocols-1
 
def dataReceived (self, data):        #将收到的数据返回给客户端
self.transport.write(data)
print data
 
factory = Factory()
factory.numProtocols = 0
factory.protocol = EchoServer
 
port = 1200
reactor.listenTCP(port, factory)
 
reactor.run() #进入循环

 使用命令#telnet 127.0.0.1 1200为客户端进行测试:
Python实例浅谈之七socket编程 - 第8张  | 大话运维Python实例浅谈之七socket编程 - 第9张  | 大话运维
reactor是整个Twisted应用的核心,Protocol和Factory类是为协议实现接口。Protocol中实现了通信协议的基本框架,并定义了相关的通信接口,这些接口将在通信时的特定事件中被触发。dataReceived方法是将接收到的数据全部照原样输出来。反应器的run方法进入循环,监听端口。
若服务器资源有限,可以限制连接的客户端的数目,numProtocols保存现在已有的连接数目,每次新的连接数目加一,每次断开连接数目减一,transport对象的loseConnection方法中断此连接。达到限制数目时,只有原来的客户端退出后,新的客户端才能重新连接。

(2)Twisted框架的官方例子:

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
34
35
36
37
38
39
40
41
 
#!/usr/bin/env python
# coding: utf-8
 
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet import reactor
 
class Echo(Protocol):
'''协议类实现用户的服务协议,例如 http,ftp,ssh 等'''
def __init__(self, factory):
self.factory = factory
 
def connectionMade(self):
'''连接建立时被回调的方法'''
self.factory.numProtocols = self.factory.numProtocols + 1
self.transport.write("Welcome! There are currently %d open connections.\n" %(self.factory.numProtocols,))
 
def connectionLost(self, reason):
'''连接关闭时被回调的方法'''
self.factory.numProtocols = self.factory.numProtocols - 1
 
def dataReceived(self, data):
'''接收数据的函数,当有数据到达时被回调'''
self.transport.write(data)
 
 
class EchoFactory(Factory):
'''协议工厂类,当客户端建立连接的时候,创建协议对象,协议对象与客户端连接一一对应'''
numProtocols = 0
#protocol = Echo
def buildProtocol(self, addr):
return Echo(self)
 
 
if __name__ == '__main__':
# 创建监听端口
FACTORY = EchoFactory()
reactor.listenTCP(8007, FACTORY)
# 开始监听事件
reactor.run()

 协议工厂继承自twisted.internet.protocol.Factory,需实现buildProtocol方法,协议工厂负责实例化协议类,不应该保存于连接相关的状态信息,因为协议工厂类仅创建一个。协议类继承自twisted.internet.protocol.Protocol,需实现dataReceived等方法,在协议类中实现应用协议,每一个客户端连接都会创建一个新的协议类对象。transport就是连接对象,通过它进行网络写数据。
Twisted框架使用daemon方式运行程序:
使用守护进程的方式运行服务,需要提供一个tac配置文件(这就是一个python文件,只是扩展名不同),并且在这个文件中需要创建一个应用程序对象,对象名必须是application。
首先创建一个echo.tac文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
#!/usr/bin/env python
# coding: utf-8
 
from twisted.application import service, internet
from echoServ import EchoFactory
 
# 创建应用程序对象
application = service.Application('Echo 服务程序')
 
# 创建 service 对象
myServices = internet.TCPServer(8007, EchoFactory())
 
# 设置 application 为 service 的父元素
myservices.setServiceParent(application)

然后用守护进程方式运行服务,运行命令:#twistd -y echo.tac。
若想要停止服务:#kill -15 (pid)。
(3)一个最简单的聊天服务器

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 
#!/usr/bin/python
#encoding=utf-8
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
 
class Chat(LineReceiver):
def __init__(self, users):
self.users = users
self.name = None
self.state = "GETNAME"
 
def connectionMade(self):
self.sendLine("What's your name?")
 
def connectionLost(self, reason):
if self.users.has_key(self.name):
del self.users[self.name]
 
def lineReceived(self, line):
if self.state == "GETNAME":
self.handle_GETNAME(line)
else:
self.handle_CHAT(line)
 
def handle_GETNAME(self, name):
if self.users.has_key(name):
self.sendLine("Name taken, please choose another.")
return
self.sendLine("Welcome, %s!" % (name,))
self.name = name
self.users[name] = self
self.state = "CHAT"
 
def handle_CHAT(self, message):
message = "<%s> %s" % (self.name, message)
for name, protocol in self.users.iteritems():
if protocol != self:
protocol.sendLine(message)
 
class ChatFactory(Factory):
 
def __init__(self):
self.users = {}    # maps user names to Chat instances
 
def buildProtocol(self, addr):
return Chat(self.users)
 
if __name__ == '__main__':
reactor.listenTCP(8123, ChatFactory())
reactor.run()

telnet下的运行结果:
Python实例浅谈之七socket编程 - 第10张  | 大话运维Python实例浅谈之七socket编程 - 第11张  | 大话运维Python实例浅谈之七socket编程 - 第12张  | 大话运维

 上述是聊天记录,第三个用户是中途加入的,只能接受到加入后的聊天记录。
(4)Twisted框架中对文件的操作
工厂有startFactory和stopFactory两种方式来执行相关应用的创建与销毁,下述代码从客户端接收到的信息都会被写入文件中。

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
 
#!/usr/bin/env python
# coding: utf-8
 
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
 
class LoggingProtocol(LineReceiver):
def lineReceived(self, line):
self.factory.fp.write(line+'\n')
self.factory.fp.flush()
 
class LogfileFactory(Factory):
protocol = LoggingProtocol
def __init__(self, fileName):
self.file = fileName
def startFactory(self):
self.fp = open(self.file, 'a')
def stopFactory(self):
self.fp.close()
 
if __name__ == '__main__':
# 创建监听端口
FACTORY = LogfileFactory("/tmp/log.file")
reactor.listenTCP(8007, FACTORY)
# 开始监听事件
reactor.run()

7、socket模拟ssh协议

模拟ssh协议,服务器端:

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
 
import socket
import os
import commands
 
HOST = '127.0.0.1'
PORT = 50007
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
 
while True:
conn, addr = s.accept()
print 'connected by', addr
while True:
data = conn.recv(1024)
if not data: break;
print 'command reveived from:', addr, data
#cmd_result=os.popen('data;echo $?').read()
status, cmd_result = commands.getstatusoutput(data)
if len(cmd_result.strip()) != 0:
conn.sendall(cmd_result)
else:
conn.sendall("DONE")
conn.close()

Python实例浅谈之七socket编程 - 第13张  | 大话运维

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
import socket
import time
 
HOST='127.0.0.1'
PORT=50007
 
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
 
while True:
cmd = raw_input("your command:").strip()
if len(cmd) == 0 : continue
s.sendall(cmd)
data=s.recv(8096)
print 'client reveived:', data
s.close()

Python实例浅谈之七socket编程 - 第14张  | 大话运维

8、SocketServer模拟FTP文件传输

类似FTP软件的发送命令和传递文件,服务器:

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
34
35
 
#!/usr/bin/python
#-*- coding: utf-8 -*-
 
import SocketServer
import commands
import time
 
class MyTCPHandler(SocketServer.BaseRequestHandler):   #并发
def handle(self):
while True:
cmd_result = ''
self.data = self.request.recv(1024).strip()
if not self.data:          #客户端离开
print "client:%s leave!" % self.client_address[0]
break
 
user_input = self.data.strip().split()
if user_input[0] == 'get':
with open(user_input[1], 'rb') as f:
self.request.sendall(f.read())     #发送文件
time.sleep(0.5)                        #sleep一段时间
self.request.send("FILETRANSFERDONE")  #发送文件结束标志
continue
print "%s wrote:%s" % (self.client_address[0], self.data)
status, cmd_result = commands.getstatusoutput(self.data)  #处理命令
if len(cmd_result.strip()) != 0:
self.request.sendall(cmd_result)
else:
self.request.sendall('Done')
 
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
server = SocketServer.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()

类似FTP软件的发送命令和传递文件,客户端:

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
<ol class="dp-xml" start="1">
	<li class="alt">import socket</li>
	<li class="">import time</li>
	<li class="alt"></li>
	<li class=""><span class="attribute">HOST</span>=<span class="attribute-value">'127.0.0.1'</span></li>
	<li class="alt"><span class="attribute">PORT</span>=<span class="attribute-value">9999</span></li>
	<li class=""></li>
	<li class="alt"><span class="attribute">s</span>=<span class="attribute-value">socket</span>.socket(socket.AF_INET, socket.SOCK_STREAM)</li>
	<li class="">s.connect((HOST, PORT))</li>
	<li class="alt"></li>
	<li class="">while True:</li>
	<li class="alt">  <span class="attribute">cmd</span> = <span class="attribute-value">raw_input</span>("your command:").strip()</li>
	<li class="">  if len(cmd) == 0: continue</li>
	<li class="alt">  s.sendall(cmd)</li>
	<li class=""></li>
	<li class="alt">  if (<span class="attribute">cmd</span> == 'quit' or <span class="attribute">cmd</span> == 'exit')break;</li>
	<li class="">  if cmd.split()[0] == 'get':    #transfer file</li>
	<li class="alt">      with open(cmd.split()[1].split("/")[-1], 'wb') as f:  #use os.path.basename</li>
	<li class="">          while True:</li>
	<li class="alt">              <span class="attribute">data</span> = <span class="attribute-value">s</span>.recv(1024)</li>
	<li class="">              #if not data: break   #different from server</li>
	<li class="alt">              if <span class="attribute">data</span> == 'FILETRANSFERDONE': break</li>
	<li class="">              f.write(data)</li>
	<li class="alt">      continue</li>
	<li class="">  else:    #send cmd or data</li>
	<li class="alt">      <span class="attribute">data</span>=<span class="attribute-value">s</span>.recv(8096)</li>
	<li class="">      print 'client reveived:', data</li>
	<li class="alt">s.close()</li>
</ol>

Python实例浅谈之七socket编程 - 第15张  | 大话运维
Python实例浅谈之七socket编程 - 第16张  | 大话运维
客户端的当前路径下会得到服务器传递的文件,服务器可以使用相对路径(注意服务器打开文件的目录不能和客户端的当前目录一样,否则文件会被覆盖清空)。

Python实例浅谈之七socket编程 - 第17张  | 大话运维

9、多线程的客户端代码

为了提高客户端的并行度,可以使用多线程方式调用。

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
 
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import socket
import threading
import SocketServer
 
def client(ip, port, message):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
try:
sock.sendall(message)
response = sock.recv(1024)
print "Received: {0}".format(response)
finally:
sock.close()
 
if __name__ == "__main__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0
th1 = threading.Thread(target=client, args=(ip, port, "Hello World 1",))
th2 = threading.Thread(target=client, args=(ip, port, "Hello World 2",))
th3 = threading.Thread(target=client, args=(ip, port, "Hello World 3",))
th1.start()
th2.start()
th3.start()
 
th1.join()
th2.join()
th3.join()

四、总结

(1)Python的Twisted框架内部有丰富的协议工厂和协议,可以很方便的实现简单的http、ftp、ssh等服务器程序。
(2)可以参考Twisted相关文档和官方的例子,构成功能更加丰富服务器。

最后编辑:
作者:saunix
大型互联网公司linux系统运维攻城狮,专门担当消防员

留下一个回复