Archive

标签为 ‘服务器’的文章

聊天服务器的思考

 

这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>