Archive

标签为 ‘python’的文章

聊天服务器的思考

 

这blog开了也快到一年了 放了个春节 都忘记了

 

在游戏开发中,讲究个模块分离,低耦合高内聚
聊天一般都会作为一个单独的服务器,分离开来

 

从网上搜过不少资料,注意到这个聊天服务器跟逻辑服务器时完全分开的
我问题来了,那像禁言,gm发话这样的功能,还有像喇叭全服/所有服等逻辑功能
如何实现呢
真的能独立开来,我指逻辑服务器和聊天服务器之间通讯

 

闲来无事之时,我想过n种理由,结果还是觉得它们两个一定有奸情[没有的话,还麻烦告知一下,人总是有思维盲点的]

 

好,那我们来对这个奸情展开调查,首先看看聊天服务器有啥功能

 

首先,聊天服务器的最大用途就是消息广播给所有玩家,可以是喊话,可以是怪物出现通告,可以是我和某女侠约会打怪掉落n多物品的奖励通报。好吧,停机维护紧急通告也是,这都是系统[无非就是系统发广播或者玩家发广播]
第二,小范围玩家的广播,例如组队,家族,或者本地图[多指mmo]
第三,二人世界[私聊]

 

另外,其实是我设想的,如果我想看看女侠的角色资料,那我也可以通过聊天服务器来获得资料[不过这样子聊天服务器就变得不单纯是聊天服务器]

 

以上,就是我总结的聊天服务器的功能,以上可以看出,逻辑服务器很多时候会通过聊天服务器做广播,丫的,我是系统,你给我老老实实的广播消息……

 

接下来,逻辑服务器该怎么对待聊天服务器呢,它会怎么勾引聊天服务器,这主要是非全局广播需要对非全局的判断
你进进出出某个队伍,队长聊天是要发给谁呢,什么范围的人呢,聊天服务器总该知道吧
所以,聊天服务器要有一张表,谁在哪个队伍,这个队伍有谁,人员一变动,逻辑服务器就要通知聊天服务器,慢慢的逻辑和聊天就产生了暧昧的关系。

 

回过头来!
针对现在自己所有的项目,聊天是做在逻辑里面的,虽然现在人数少的时候是没多大感觉的,但是我担心人一多,例如2k人在线,那就会租售到逻辑处理[哦,这个项目的逻辑就写在一个进程的一个线程里面……原因不提了]

 

所以我进行了设计构思:
首先,我是想把组队人员信息[非全局广播区域]写到memcache里面,然后聊天服务器进行读取操作,团队同事建议,针对项目实际情况,使用进程管道进行通信,这个也行,反正目的达到就可以了。
不过,回头想想,要是聊天服务挂掉了,那之前的信息就消失了,所以我还是觉得放到memcache中或者其他方式来处理好些……
接下来,就是客户端[这里是flash客户端]完全可以把聊天模块独立开来[其实广播功能模块都可以独立的拉],连接到聊天服务器,也便于复用

 

demo我大致写好了
聊天服务器我使用twisted[twisted的东西,有空在另外讨论]进行简单的广播,客户端的,从某人拿来flex代码一看,太复杂了,自己写了一个[实现flex主要是便于以后获取测试流量,我可以把客户端隐藏在游戏页面中,获得真正的测试环境]

 

代码如下:[进行了很多删减,存在少量错误]

 

服务器端:

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
 
#[logic.py]
import multiprocessing
logicConn,chatConn=multiprocessing.Pipe()
multiprocessing.Process(target=Chat_Engine_Start,args=(chatConn,))
while True:
    logicConn.send("team info");
 
 
#[Chat_Main.py]
'''
Created on 2010-3-16
 
@author: ZQuan
'''
import threading
import time
from Chat_Server import chatstart,chatstop
 
InfoPoolDict=[]
 
class pipeThread(threading.Thread):
 
    pipeThreadLoopState = True
    pipeThreadConn=None
 
    def __init__(self,conn):
        threading.Thread.__init__(self)
        self.pipeThreadConn = conn
        pass
 
    def run(self):
        while self.pipeThreadLoopState:
            data=self.pipeThreadConn.recv()
            InfoPoolDict.append(data)
            time.sleep(0.001)
            pass
        pass
 
    def stop(self):
        self.pipeThreadLoopState = False
        pass
 
    pass
 
class chatThread(threading.Thread):
 
    def __init__(self):
        threading.Thread.__init__(self)
        pass
 
    def run(self):
        chatstart(InfoPoolDict)
        pass
 
    def stop(self):
        chatstop()
        pass
    pass
 
def Chat_Engine_Start(conn):
 
    chatThread().start()
    pipeThread().start()
 
    pass
 
 
 
 
#[Chat_Server.PY]
#!/usr/bin/env python
#coding=utf-8
'''
Created on 2010-3-17
 
@author: ZQuan
'''
 
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineOnlyReceiver
from twisted.internet import reactor
 
FLASH_SOCKET_VERIFY_REPLY = "";
 
class Chat(LineOnlyReceiver):
    def lineReceived(self, data):
        self.factory.sendAll("%s: %s" % (self.getId(), data))
 
    def getId(self):
        IDS = self.transport.getPeer()
        return str(IDS[1])+":"+str(IDS[2])
 
    def connectionMade(self):
        print "New connection from", self.getId()
        self.transport.write(FLASH_SOCKET_VERIFY_REPLY)
        self.factory.addClient(self)
        self.factory.sendAll("Welcome to the chat server, %s\n" % self.getId())
 
    def connectionLost(self, reason):
        self.factory.delClient(self)
        self.factory.sendAll("%s: %s" % (self.getId(), " leave..."))
 
class ChatFactory(Factory):
    protocol = Chat
 
    def __init__(self,InfoPoolDict):
        self.clients = []
        self.InfoPool=InfoPoolDict
 
    def addClient(self, newclient):
        self.clients.append(newclient)
 
    def delClient(self, client):
        self.clients.remove(client)
 
    def sendAll(self, message):
        for proto in self.clients:
            proto.transport.write(message + "\n")
            pass
        pass
 
    def msgHandler(self, message):
        if message.source in self.InfoPool:
            self.sendAll(message)
            pass
        pass
def chatstart(InfoPoolDict):
    reactor.listenTCP(51427, ChatFactory(InfoPoolDict))
    reactor.run()
    pass
 
def chatstop():
    reactor.stop()
    pass

客户端:

<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”absolute”
    creationComplete=”initSock()” enterFrame=”autoAction()” width=”431″ height=”259″
    verticalScrollPolicy=”off” horizontalScrollPolicy=”off”>
    <mx:Script>
        <![CDATA[
            import flash.sampler.NewObjectSample;
            [Bindable]
            private var _IpArray:Array = ["192.168.3.140","127.0.0.1"];
            [Bindable]
            private var _PottArray:Array = ["51423","51425"];
            
            import flash.net.Socket; //导入类包
            import flash.utils.ByteArray;//ByteArray在读取数据时使用
            private var socket:Socket;//定义socket
            private var lastLoopTime:Number;
            
            public function initSock():void
            {
                trace(“initSock”);
                //初始话操作
                socket = new Socket();
                
                //注册监听器:连接
                socket.addEventListener(Event.CONNECT,handlerConn)
                //注册监听器:接收
                socket.addEventListener(ProgressEvent.SOCKET_DATA,handlerData)
                //注册监听器:断开
                socket.addEventListener(Event.CLOSE,handlerClose)
                //注册监听器:io错误
                socket.addEventListener(IOErrorEvent.IO_ERROR,handlerIOError);
                
                lastLoopTime=(new Date()).getTime();
                /* //var policyFile:String = “xmlsocket://”+(ipComb.selectedItem as String)+”:”+(portComb.selectedItem as String);
                var policyFile:String = “xmlsocket://”+(ipComb.selectedItem as String);
                Security.loadPolicyFile(policyFile);
                Security.allowDomain( “*” );
                Security.allowInsecureDomain(“*”); */
                myInput.addEventListener(KeyboardEvent.KEY_DOWN,keyDownhandler);
            }
            
            public function startConn():void
            {
                //发起连接
                //var host:String=”127.0.0.1″;
                //var port:int=51423;
                
                var host:String = ipComb.selectedItem as String;
                var port:int = int(portComb.selectedItem);
                
                socket.connect(host,port);
            }
            
            public function handlerConn(event:Event):void
            {
                trace(socket.endian.toString());
                //处理连接成功
                if(socket.connected)
                {
                    chatInfoArea.htmlText+=”连接成功\n”;
                    conbtn.enabled=false;
                    sendbtn.enabled=true;
                }
            }
            
            public function sendMessage(msg:String):void
            {
                //发送数据
                
                //新建一个ByteArray来存放数据
                var message:ByteArray=new ByteArray();
                //写入数据,使用writeUTFBytes以utf8格式传数据,避免中文乱码
                message.writeMultiByte(msg+”\r\n”,”utf8″);
                //写入socket的缓冲区
                socket.writeBytes(message,0,message.length);
                //调用 flush方法发送信息
                socket.flush();
                //清空消息框
                myInput.text=”";
                myInput.setFocus();
            }
            
            public function handlerData(event:ProgressEvent):void
            {
                //收到数据处理
                
                var msg:String=”";
                //循环读取数据,socket的bytesAvailable对象存放了服务器传来的所有数据
                while(socket.bytesAvailable)
                {
                    //强制使用utf8格式,避免中文乱码
                    msg+=socket.readMultiByte(socket.bytesAvailable,”utf8″);
                    //使用n换行符号把信息切开
                    var arr:Array=msg.split(‘\n’);
                    for(var i:int=0;i<arr.length;i++)
                    {
                        if(arr[i].length>1)
                        {
                            //正则表达式,回车符
                            var myPattern:RegExp=/r/;
                            //删除回车符
                            arr[i]=arr[i].replace(myPattern,”);
                            //在聊天框中输出
                            chatInfoArea.htmlText+=arr[i]+”\n”;
                        }
                    }
                    //滚动到最下面
                    chatInfoArea.verticalScrollPosition=chatInfoArea.maxVerticalScrollPosition;
                }
            }
            
            public function handlerClose(event:Event):void
            {
                //断开操作
                conbtn.enabled=true;
                sendbtn.enabled=false;
            }
            private function handlerIOError(event:Event):void
            {
                //this.dispatchEvent(new Event(SocketConnection.CONNECTION_ERROR));
                chatInfoArea.htmlText+=event.toString()+”\n”;
                conbtn.enabled=true;
                sendbtn.enabled=false;
            }
            public function autoAction():void
            {
                //trace(“autoAction”);
                var now:Number = (new Date()).getTime();
                //trace(timerTxt.text);
                if(now<lastLoopTime+int(timerTxt.text) || socket==null)return;
                
                if(!socket.connected)
                {
                    //trace(“startConn”);
                    startConn();
                }
                else
                {
                    //trace(“sendMessage”);
                    sendMessage(“女侠 别累坏了 多喝水!”);
                }
                //trace(lastLoopTime);
                
                lastLoopTime=(new Date()).getTime();
            }
            private function keyDownhandler(e:KeyboardEvent):void
            {
                if (13 == e.charCode)
                {
                    sendMessage(myInput.text);
                }
            }
        ]]>
    </mx:Script>
    <mx:HBox top=”10″ left=”10″ height=”42″ width=”411″ horizontalGap=”20″>
        <mx:ComboBox id=”ipComb” dataProvider=”{_IpArray}” width=”132″ height=”34″/>
        <mx:ComboBox id=”portComb” dataProvider=”{_PottArray}” width=”79″ height=”34″/>
        <mx:TextInput id=”timerTxt” text=”100000″ height=”34″ width=”77″ fontSize=”12″/>
        <mx:Button id=”conbtn” label=”连接” width=”57″ height=”34″ fontSize=”14″ click=”startConn()”/>
    </mx:HBox>
    <mx:TextArea id=”chatInfoArea” x=”10″ y=”60″ width=”411″ height=”135″ fontSize=”12″/>
    <mx:Button id=”sendbtn” x=”357″ y=”203″ label=”发送” click=”sendMessage(myInput.text)” height=”33″ enabled=”false”/>
    <mx:TextInput id=”myInput” x=”10″ y=”203″ width=”339″ height=”33″ fontSize=”12″/>
</mx:Application>

python中的__init__和__new__

在python中常用的构造方式是使用__init__,除此之外还有一个__new__。
当一个类中定义了__new__,python会优先调用__new__。
如果定义__new__你需要显式生成一个对象并返回,如果定义new,python会隐式生成一个对象。

以下是例子:

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/env python
#coding=utf-8
 
class A(object):
    def __new__(self, *args, **kwds):
        print "one"
        print "A.__new__", args, kwds
        return object.__new__(B, *args, **kwds)
    def __init__(self, *args, **kwds):
        print "two"
        print "A.__init__", args, kwds
 
class B(object):
    def __new__(self, *args, **kwds):
        print "three"
        print self
        print B
        print "B.__new__", args, kwds
        return object.__new__(self, *args, **kwds)
    def __init__(self, *args, **kwds):
        print "four"
        print "B.__init__", args, kwds
 
class C(object):
    def __init__(self, *args, **kwds):
        print "five"
        print "C.__init__", args, kwds
 
 
print "#######################"
print C()
print "====================="
print A()
print "====================="
print B()

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pydev debugger: starting
#######################
five
C.__init__ () {}
sss
 
=====================
one
A.__new__ () {}
 
=====================
three
 
 
B.__new__ () {}
four
B.__init__ () {}

在类C,只定义了__init__。
在类A,两者都有定义,但是优先调用__new__。
在类B,优先调用__new__,但是显式回调了__init__。

什么时候使用 __new__呢?

1.当构造时需要返回一个已定义的不同类型的对象
When you want you constructor to return an object of a different type to the class in which it is defined. For example, you have a class “animal” with subclasses “farmanimal” and “pet” and you want the animal cosntructor to be able to examine the data passed in to it and return an animal … or a farmanimal or a pet depending on that data.

2.基类程序员重载__new__来阻碍子类重写父类的构造器
It has been suggested that experienced base class programmers override __new__ to discourage programmers who subclass them from overwriting their constructors. Personally, I would suggest that a good training course is better than this form of security through obscurity.

【不知道翻译的对不对,请多多指出】

原文出处

asyncore.dispatcher的通信例子

服务器端

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
83
84
import socket
import asyncore
import threading
 
MAX_RECV = 4096
 
#負責接受client socket連線
class AgentServer(asyncore.dispatcher):   
   def __init__(self, port):
       #asyncore.dispatcher的constructor
       asyncore.dispatcher.__init__(self)
       #client socket
       self.clientSocket = None
       #server port
       self.port = port
       #建立等待的socket
       self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
       self.bind(('', self.port))
       self.listen(5)
 
   def handle_accept(self):
       #接受client socket的連線
     self.clientSocket, address = self.accept()
       print 'New client from : ' + address[0]
       #將和client連線的socket加上一些功能 (自訂socket)
       self.clientSocket = ClientAgent(self.clientSocket)
 
#自訂的client連線socket
class ClientAgent(asyncore.dispatcher):
   def __init__(self, socket):
       asyncore.dispatcher.__init__(self, socket)
       #要送出的data
       self.SendData = ""
 
   #從client收到的data
   def handle_read(self):
       self.RecvData = self.recv(MAX_RECV)
       if len(self.RecvData) > 0:
           print "recv : " + self.RecvData
   #送出data到client
   def handle_write(self):
       send_byte = self.send(self.SendData)
       #一次可能不會全部送完(一次最多送出512 bytes ?)
       if send_byte > 0:
           send_out = self.SendData[:send_byte]
           self.SendData = self.SendData[send_byte:]
           self.handle_write()
       else:
           print "send all!!"
           self.SendData = ""
 
   #不自動執行handle_write
   def writable(self):
       return False
 
   def handle_close(self):
       print "close connection : " + self.getpeername()[0]
       self.close()
#產生等待client連線的thread
class listen_client_thread(threading.Thread):
   def __init__(self,port):
       self.agentServer = AgentServer(port)
       threading.Thread.__init__ ( self )
 
   def run(self):
       print "Listen Client ..."
       asyncore.loop()
#產生處理輸入的thread       
class input_thread(threading.Thread):
   def __init__(self,listen_thread):
       self.listen_thread = listen_thread
       threading.Thread.__init__ ( self )
   def run(self):
       while 1:
           send_data = raw_input()
           self.listen_thread.agentServer.clientSocket.SendData = send_data
           self.listen_thread.agentServer.clientSocket.handle_write()
 
if __name__ == "__main__":
   #產生等待client連線的thread
   listen_thread = listen_client_thread(111)
   listen_thread.start()
   #產生處理輸入的thread
   input_thread(listen_thread).start()

客户端

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
import asyncore, socket
import threading
 
#收到data最大長度
MAX_RECV = 4096
 
#連線server的socket
class client(asyncore.dispatcher):
 
   def __init__(self, host, port):
       asyncore.dispatcher.__init__(self)
       self.SendData = ""
       self.RecvData = ""
       #和server建立連線
       self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
       self.connect( (host, port) )
 
   def handle_connect(self):
       print 'connect!!'
 
   def handle_close(self):
       print "disconnection : " + self.getpeername()[0]
       self.close()
 
   #收到的data
   def handle_read(self):
       self.RecvData = self.recv(MAX_RECV)
       if len(self.RecvData) > 0:
           print "recv : " + self.RecvData
 
   #送出data
   def handle_write(self):
       send_byte = self.send(self.SendData)
       if send_byte > 0:
           send_out = self.SendData[:send_byte]
           self.SendData = self.SendData[send_byte:]
           print "send : " + send_out
           self.handle_write()
       else:
           print "send all!!"
           self.SendData = ""
   #自動偵測送出永遠失敗
   def writable(self):
       return False
 
#等待server傳送訊息的thread
class send_server_thread(threading.Thread):
   def __init__(self,host,port):
       self.client = client(host, port)
       threading.Thread.__init__ ( self )
   def run(self):
       try:
           asyncore.loop()
       except:
           pass
#等待user input的thread       
class input_thread(threading.Thread):
   def __init__(self,client_thread):
       self.client_thread = client_thread
       threading.Thread.__init__ ( self )
   def run(self):
       while 1:
           send_data = raw_input()
           self.client_thread.client.SendData = send_data
           self.client_thread.client.handle_write()
 
#主程式
if __name__ == "__main__":
   #建立和server連線, 並且等待溝通
   client_thread = send_server_thread("localhost",111)
   client_thread.start()
   #等待user input
   input_thread(client_thread).start()

出处

linux的easy_install

今天总算是真的用了linux,以后以centos作为主要的操作系统

在安装了python 2.5之后,还需要安装mysqlDB和autumn
其中在安装autumn的时候用到了easy_install autumn
一开始还以为easy_install是linux的命令
在网上小搜了一下发现原来是python的组件,叫python-setuptools

当使用easy_install安装的时,提示找不到命令,只要装个python-setuptools就可以了
呵呵,在centos我用yum install python-setuptools安装好了
就直接easy_install autumn

嘿,搞定!

python的struct模块

“python是一门非常简洁的语言”
好吧,这种简洁也有缺点,至少有些错误在编译时总是默不吭声。
不过我们有try和except……

 

在数据格式转换方面,struct模块可以满足大部分需求
主要有下面4个方法:
struct.pack
struct.unpack
struct.pack_into
struct.unpack_from

 

还有一个:
struct.calcsize

 

简单记录下!

 

 

struct.pack(fmt, v1, v2, …)
用于将Python的值根据格式符,转换为字符串
参数fmt是格式字符串
v1, v2, …表示要转换的python值
程序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import struct 
 
a = 20   
b = 400   
 
str = struct.pack("ii", a, b) 
print 'length:', len(str)   
print str   
print repr(str)   
 
#---- result   
#length: 8   
#      ----这里是乱码   
#'\x14\x00\x00\x00\x90\x01\x00\x00'

 

 

struct.unpack(fmt, string)
刚好与struct.pack相反,用于将字节流转换成python数据类型
注意!返回一个远组
程序代码

1
2
3
4
5
6
7
str = struct.pack("ii", 20, 400)
a1, a2 = struct.unpack("ii", str)
print 'a1:', a1
print 'a2:', a2
#---- result:
#a1: 20
#a2: 400

 

 

struct.pack_into, struct.unpack_from
其实是实现了独个单位的转换
程序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import struct   
from ctypes import create_string_buffer   
 
buf = create_string_buffer(12)   
print repr(buf.raw)   
 
struct.pack_into("iii", buf, 0, 1, 2, -1)   
print repr(buf.raw)   
 
print struct.unpack_from('iii', buf, 0)
#我试过用   print struct.unpack_from('iii', buf.raw, 0)   
#居然也能正常使用,很奇怪
#可能就是raw和buf其实都是继承了列表
 
#---- result   
#'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'   
#'\x01\x00\x00\x00\x02\x00\x00\x00\xff\xff\xff\xff'   
#(1, 2, -1)

 

而struct.calcsize用于计算格式字符串所对应的结果的长度
如:struct.calcsize(‘ii’),返回8。
因为两个int类型所占用的长度是8个字节。

 

附录:
格式符 C语言类型 Python类型
x pad byte no value
c char string of length 1
b signed char integer
B unsigned char integer
? _Bool bool
h short integer
H unsigned short integer
i int integer
I unsigned int integer or long
l long integer
L unsigned long long
q long long long
Q unsigned long long long
f float float
d double float
s char[] string
p char[] string
P void * long