用Python与socket实现的网络服务器

来源:互联网 发布:淘宝爆款 编辑:程序博客网 时间:2024/05/22 17:37
 
    为了验证并行渲染的结构,用python设计了一个简单的网络服务器,客户端是用c++开发的简单渲染应用。希望能够实现服务器控制下,多个客户端同步渲染相同的场景画面,并将画面传回服务器进行图像的合成。
 
    工作流程如下:
1.启动服务器,并在指定的端口进行监听;
2.启动客户端,与服务器建立连接后向其请求配置数据,这样可以保证配置数据只需要保存在服务器上,简单而且便于维护;
3.客户端根据配置数据初始化,告知服务器后即进入渲染循环;
    目前的渲染循环中,客户端的操作都是根据服务器的指令进行,这些指令包括:更新数据、渲染场景、读回图像。
    大多数情况下,客户端与服务器间进行一对一的通讯。在客户端更新应用数据时,比如控制对象或视点的运动,产生更新的客户端将数据发送给服务器,后者在逐一发送给各个客户端,从而保证多个客户端同步更新。
 

服务器与客户端间的通讯流程
    涉及到的python模块包括threading、socket、struct
    涉及到的三个类Server、Node、Connector均继承自threading.Thread,因此各自创建独立的线程。
    其中Server作为服务器容器,负责监听给定端口的连接请求。来自客户端的连接请求被接受后,会创建一个Node对象与之对应,并将该对象放入NodeList中进行管理。
    Node类作为客户端代理,通过其内部的Connector对象实现与客户端的socket通讯。其中Connector线程用于读取socket缓存,并将得到的数据存入FIFO队列中,Node则从另一个线程取出队列中的数据进行解析与处理。由于系统中需要传回每一帧的图像数据,至少为几百k字节,所以这里将socket缓存的读取与数据解析、处理分别放在两个线程中,就是为了避免数据处理阻塞了socket缓存的读取,从而带来socket缓存的溢出问题。

    为了实现更好的封装效果,用python实现多线程时,这里采用继承threading.Thread的方式,而不是采用thread模块方法。这样除了在__init__中的几个规定步骤外,只需要在子类中重载一下run函数就完成了。
    在socket编程方面,这里直接采用多线程下的阻塞模式,配合基于FIFO缓存的线程通讯来实现。结构上比使用asyncore的异步方法要简洁,而且避免了socket阻塞的发生。(尝试过异步模型,没有缓存和多线程的话也不能避免socket缓存溢出问题)
struct模块用来进行消息的解析,它可以将string按照c风格的数据结构进行分解。
 
    在实现的系统里,客户端用了Nehe的lesson4,因为程序简单,而且其中转动的多边形可以验证基于网络的同步更新。
 
图:分别运行的客户端画面
 
 
图:服务器合成的图像画面
 
    目前主要的问题是运行的速度比较慢,毕竟要在两个线程之间进行数据拷贝,特别是很大的图像数据,慢一点倒是早在意料之中,毕竟这只是个验证程序。需要高效率的话用C/C++实现就是了,不过那可没有用python方便了。

 

代码附录:

sc_connector.py

  1. import socket, Queue, threading
  2. REV_BUFFER_SIZE = 65536
  3. MAX_BUFFER_SIZE = 65536
  4. class Connector(threading.Thread):
  5.     def __init__(self, sock, bulkQueue):
  6.         threading.Thread.__init__(self)
  7.         self.sock = sock
  8.         self.sock.setsockopt(socket.SOL_SOCKET,
  9.                                  socket.SO_RCVBUF,
  10.                                  REV_BUFFER_SIZE)
  11.         self.died = False
  12.         self.bulkQueue = bulkQueue
  13.         self.setDaemon(True)
  14.         self.start()
  15.     def run(self):
  16.         while not self.died:
  17.             data = self.sock.recv(MAX_BUFFER_SIZE)
  18.             if not data:    break
  19.             self.bulkQueue.put(data)
  20.         print 'socket 'self.sock.getsockname(), ' is closed.'
  21.         self.sock.shutdown(socket.SHUT_RDWR)
  22.         self.sock.close()
  23.     def send(self, data):
  24.         self.sock.send(data)
  25.     def close(self): pass
  26.       #  self.died = True

sc_node.py

  1. import socket, struct, Queue, threading
  2. from sc_connector import *
  3. MSG_CONFIG,MSG_UPDATE,MSG_DRAW,MSG_READBACK = range(10001004)
  4. MSG_REQUEST_CONFIG, /
  5. MSG_CONFIG_FINISHED, /
  6. MSG_DRAW_FINISHED, /
  7. MSG_READBACK_FINISHED, /
  8. MSG_RS_EXIT = range(20002005)
  9. frust1 = [-0.120.04, -0.060.060.1100.0]
  10. frust2 = [0.040.2, -0.060.060.1100.0]
  11. bulkQueue = Queue.Queue()
  12. class Node(threading.Thread):
  13.     def __init__(self, sock, server):
  14.         threading.Thread.__init__(self)
  15.         self.connector = Connector(sock, bulkQueue)
  16.         self.server = server
  17.         self.counter = 0
  18.         self.died = False
  19.         
  20.         self.setDaemon(True)
  21.         self.start()
  22.     def run(self):
  23.         revdData = ''
  24.         while not self.died:
  25.                 while True:
  26.                         if not bulkQueue.empty():
  27.                                 revdData += bulkQueue.get()
  28.                                 bulkQueue.task_done()
  29.                                 break
  30.                 self.msgHeader = revdData[:8]
  31.                 revdData = revdData[8:]
  32.                 self.msgID, self.msgSize = struct.unpack('ii'self.msgHeader)
  33.                 if self.msgID<0 or self.msgID>5000:
  34.                         print 'Error msg ID.'
  35.                         break
  36.                 
  37.                 dataSize = self.msgSize - struct.calcsize('ii')
  38.                 # not enough data, wait for the remain.
  39.                 while dataSize>len(revdData):
  40.                         if not bulkQueue.empty():
  41.                                 revdData += bulkQueue.get()
  42.                                 bulkQueue.task_done()
  43.                 # enough data
  44.                 self.msgData = revdData[:dataSize]
  45.                 revdData = revdData[dataSize:]
  46.                 # processing messages.
  47.                 if self.msgID == MSG_REQUEST_CONFIG:
  48.                         data = self.setupConfig()
  49.                         self.connector.send(data)
  50.                 elif self.msgID == MSG_CONFIG_FINISHED:
  51.                         print 'config finished'
  52.                         data = struct.pack('ii', MSG_DRAW, struct.calcsize('ii'))
  53.                         self.connector.send(data)
  54.                 elif self.msgID == MSG_DRAW_FINISHED:
  55.                         data = struct.pack('ii', MSG_READBACK, struct.calcsize('ii'))
  56.                         self.connector.send(data)
  57.                 elif self.msgID == MSG_READBACK_FINISHED:
  58.                         data = struct.pack('ii', MSG_DRAW, struct.calcsize('ii'))
  59.                         self.connector.send(data)
  60.                 elif self.msgID == MSG_UPDATE: 
  61.                         self.server.sendAll(self.msgHeader + self.msgData)
  62.                 elif self.msgID == MSG_RS_EXIT:
  63.                         self.connector.close()
  64.                         break
  65.     # send projection params to the accepted render server.
  66.     def setupConfig(self):
  67.             hint = 'iiffffff'
  68.             if ( self.counter%2 == 0 ):
  69.                     data = struct.pack(hint, MSG_CONFIG, struct.calcsize(hint), 
  70.                             frust1[0], frust1[1], frust1[2], frust1[3], frust1[4], frust1[5])
  71.             else:
  72.                     data = struct.pack(hint, MSG_CONFIG, struct.calcsize(hint), 
  73.                             frust2[0], frust2[1], frust2[2], frust2[3], frust2[4], frust2[5])
  74.             self.counter += 1
  75.             return data
  76.     def close(self):
  77.         self.connector.close()
  78.         self.died = True
  79.         self.server.nodeList.remove(this)

sc_server.py

  1. import socket, threading
  2. from sc_node import *
  3. class Server(threading.Thread):
  4.         def run(self):
  5.                 print 'thread run'
  6.                 while not self.closeAll:
  7.                         self.AcceptConnection()
  8.                 
  9.         def sendAll(self, data):
  10.                 for node in self.nodeList:
  11.                         node.connector.send(data)
  12.                
  13.         def __init__(self, host, port):
  14.                 threading.Thread.__init__(self)
  15.                 
  16.                 self.host = host
  17.                 self.port = port
  18.                 try:
  19.                         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  20.                 except socket.error:
  21.                         print 'Server error: Unable to open socket.'
  22.                 self.sock.bind((host, port))
  23.                 self.sock.listen(5)
  24.                 self.nodeList = []
  25.                 self.closeAll = False
  26.                 
  27.                 self.setDaemon(True)
  28.                 self.start()
  29.         def AcceptConnection(self):
  30.                 clisock, address = self.sock.accept()
  31.                 print 'A new connection from', address, 'is accepted.'
  32.                 node = Node(clisock, self)
  33.                 self.nodeList.append(node)
  34.         def CloseConnection(self):
  35.                 self.closeAll = True
  36.                 print 'going to close sockets.'

main.py

  1. from sc_server import *
  2. def main():
  3.         sysClient = Server("localhost"50202)
  4.         # wait for the input to exit.
  5.         counter = 0
  6.         while 1:
  7.                 data = raw_input('-->')
  8.                 if data == 'exit'break
  9.         sysClient.CloseConnection()
  10. if __name__ == "__main__":
  11.         main()

 

 

原创粉丝点击