zoukankan      html  css  js  c++  java
  • 使用go,基于martini,和websocket开发简易聊天室



    一、首先,需要了解一下websocket基本原理:here

    二、go语言的websocket实现:

    基于go语言的websocket也有不少,比如github.com/gorilla/websocket。这里选用的应该算是官方的实现code.google.com/p/go.net/websocket

    使用go get安装下载即可。(不过,由于周知的原因:(,我是通过golangtc.com的第三方包下载功能才下载来的)

    三、server端

    第一个遇到的问题,websocket如何和martini集成?

    安装websocket的文档,和http服务集成,应该使用如下方式

    func ChatService (ws *websocket.Conn) { for{
        io.Copy(ws,ws); } }

    http.Handle("/echo", websocket.Handler(EchoServer))

    但是,如果注册到martini的route,运行时会报错

    m.Any("/chat", websocket.Handler(ChatService))

    经阅读Server.go代码,发现,需要使用websocket.Handler的方法ServeHTTP来注册(ps:因为websoket.Handler是个函数签名的自定义类型,所以,我们把ChatService转为websocket.Handler之后,就拥有了它的方法)

    m.Any("/chat", websocket.Handler(ChatService).ServeHTTP)

    服务端代码,基于某些原因,这里贴上部分代码,其余请大家根据框架自己很容易实现:

    type chatMsg struct {
        From      string `json:"from"` 
        To   string `json:"to"`  
        At   string `json:"at"`  
        Type string `json:"type"`
        Success bool        `json:"success"`
        Msg     string      `json:"msg"`    
        Data    interface{} `json:"data"`   
    }

    var
    connections map[*websocket.Conn]*chatClient var msgQueue *list.List var locker, lockQueue *sync.RWMutex var activeClient int64 = 0 var chanNewClient chan *websocket.Conn func init() { connections = make(map[*websocket.Conn]*chatClient) msgQueue = list.New() locker = &sync.RWMutex{} lockQueue = &sync.RWMutex{} chanNewClient = make(chan *websocket.Conn, 10) go msgMonitor() } func ChatService(ws *websocket.Conn) { var err error var user string var hasUser bool var session *sessions.Session var srvMsg *chatMsg defer func() { if ex := recover(); ex != nil { glog.Errorf("[ChatService]get session panic: %v , stack trace:%v", err, debug.Stack()) } }() defer deleteClient(ws) for { if session, err = session_store.Get(ws.Request(), SESSION_NAME); err != nil { glog.Errorf("[ChatService]get session error: %v", err) return } hasUser = false if iuser, has := session.Values["user"]; has { user = iuser.(string) hasUser = true } if hasUser { registerClient(ws, user) } if ptMsg, good, err := readClient(ws); err != nil { return } else if good { if !hasUser && ptMsg.Type != "signin" { srvMsg = &chatMsg{From: "server", Success: false, Msg: "signin first please!", Type: "needsignin"} if err := sendClient(ws, srvMsg); err != nil { return } } switch ptMsg.Type { case "": fallthrough case "msg": if ptMsg.Msg != "" { if err := relayMsg(ws, ptMsg); err != nil { return } } case "listuser": users := getUsersList() if jsdata, err := json.Marshal(users); err != nil { srvMsg := &chatMsg{From: "server", Type: "listuser", Success: false, Msg: "get users error:" + err.Error()} if err := sendClient(ws, srvMsg); err != nil { return } } else { srvMsg := &chatMsg{From: "server", Type: "listuser", Success: true, Data: string(jsdata)} if err := sendClient(ws, srvMsg); err != nil { return } } } } } //end for }

    几点说明:

    1如果读写数据遇到错误,甭犹豫,关掉连接;
    2活动连接自己记录;
    3实现多些的功能,必要定个通讯协议;
    4websocket的错误只有一个类型,但是我发现有一个错误内容是”not implemented“的错误(好像来自firefox),可以安全的忽略;

    上面第四点,应该是websocket服务缺少什么协议的实现,但是我没看出来,下面是调试信息,有知者望不吝赐教:

    2015/01/14 13:43:46 glog.go:169 [[info] [[ChatService] *websocket.ProtocolError when read socket. Request:
    Head: [map[Sec-Websocket-Key:[GH09xUWLdTOVB0u3RJdOgA==] User-Agent:[Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)] Cache-Control:[no-cache] Cookie:[my_session=MTQyMTIxNDE2MXxRdXBlSjlVeTR4SG9TVTFSYUJkN1FyMW1GU0c2U05XdjBEc0F0NXZSQnZSbE1BaVdKem51aE44TktZdWNaaUtfaVZBVmVONE9JY1JUcFU0MVliVkFxR3BnUi1LQ2F0RV82b1FxUE9jMXlIV3NGdlhfbWZiLTBSLTQySHFKNmJlVVZJSWlScmpGZXZLWmZuXzc4aXM5UEZQY2ZlRzF8CISa785vyuDilrrpQTg4IKLyiH-U12yGai4ah-ixbV8=] Origin:[http://192.168.5.92:8088] Connection:[Upgrade] Upgrade:[Websocket] Sec-Websocket-Version:[13] Dnt:[1]] 
    Body:&struct { http.eofReaderWithWriteTo; io.Closer }{eofReaderWithWriteTo:http.eofReaderWithWriteTo{}, Closer:ioutil.nopCloser{Reader:io.Reader(nil)}}]]]

    四、基于网页的client端实现:(客户端浏览器当然需要支持websocket协议,当前最新的浏览器基本都支持了,IE需要10以上)

    <!doctype html>
    <html>
        <head>
        <title>websocket</title>
        <style>
            #log{height: 300px;overflow-y: scroll;border: 1px solid #CCC;padding:0;}
            #signinpad{display:none;background-color:#99F}
            .r-msg{color:#111;}
            .s-msg{color:#090;}
            .msg-from{font-family:fangsong;}
            .chat-msg{padding-left:15px;}
        </style>
        <script type="text/javascript" src="/js/easyui-1.4.1/jquery.min.js" ></script>
          <script type="text/javascript">
             var sock = null;
             var wsuri = "ws://192.168.5.92:8088/chat";
             $(document).on("ready",function() {
                console.log("onload");
                jQuery.fn.flash = function( size, duration )
                {
                    try{
                    var current = this.css( 'border-width' );
                    if(current=="")current="0px"
                    current = parseInt(current)
                    this.animate( { 'borderWidth': 5 }, duration / 2 );
                    this.animate( { 'borderWidth': current }, duration / 2 );
                    }catch(ex){
                        console.log(ex)
                    }
                }
                $("#message").on("keydown",function(evt){if(evt.keyCode==13){send();}})
                try{
                 conn();
                }catch(ex){
                    console.log(ex);
                    alert("连接websocket务器报错:"+ex);
                    $("#btnsend").attr("disabled","disabled");
                    $("#message").attr("disabled","disabled");
                    $("#btngetusers").attr("disabled","disabled");
                }
            });
               function conn(){
                    
                        sock = new WebSocket(wsuri);
                          regevt(); 
                }
                function regevt(){
                    sock.onopen = function() {
                       console.log("connected to " + wsuri);
                        loaduser();
                    }                
                    sock.onerror = function(e) {
                       console.log(" error from connect " + e);
                    }    
                    sock.onclose = function(e) {
                       console.log("connection closed (" + e.code + ")");
                    console.log("reconnecting....");
                        setTimeout(   conn,1000);
                    }    
                    sock.onmessage = onMsg; 
                }
                function onMsg(e) {
                   console.log("message received: " + e.data);        
                   var msg = window.JSON.parse(e.data);    
                   if (msg.type=="" ||msg.type=="msg"){
                       appendMsg(msg.from,msg.msg);
                    }else if (msg.type=="needsignin"){
                        alert("signin first,please!");
                        $("#signinpad").css("display","block");
                        $('#btnsignin').removeAttr("disabled")
                        $('#user').focus();
                        $('#btnsignin').on("click",function(){
                            var user = $('#user').val();
                            var pass = $('#password').val();
                            if( user==""){
                                alert("user name cannot be blank!")
                                $('#user').focus();return
                            }
                            if( pass==""){
                                alert("password cannot be blank!")
                                $('#password').focus();return;
                            }
                            appendMsg("self","signining");
                            $('#btnsignin').attr("disabled","disabled")
                  // 这里的url需要替换为你自己web应用的地址,服务需要返回json $.ajax({url:"/api/usr/signin",method:"post",data:{name:user,password:pass},dataType:"json" ,success:function(data){ console.log("receive data's type=",typeof(data)," data:",data); if (data.success) { onSok(); }else{ onSerr(data.msg) } } ,error:function(R,err){ onSerr(err) } }) }); }else if(msg.type=="listuser"){ if(msg.success){ var list = $("#userlist"); var ind=0; var ccc = list[0].length-1; while (ind<ccc){ $("#userlist").get(0).remove(1); ind++; } var users = window.JSON.parse(msg.data) for (ind=0;ind<users.length;ind++){ list.append("<option value=""+users[ind]+"">"+users[ind]+"</option>") } }else{ appendMsg(msg.from,msg.msg); } }else if(msg.type=="signin"){ if(msg.success){ onSok() }else{ onSerr(msg.msg); } } } function onSok(){ $("#signinpad").css("display","none"); appendMsg("self","signin ok,ready to send message"); $("#message").focus(); document.location.href = document.location.href sock.send('{"type":"listuser","msg":""}') return; } function onSerr(err){ appendMsg("self",err) appendMsg("self","signin again,please!") $('#btnsignin').removeAttr("disabled") $('#user').focus(); return; } function loaduser(){ sock.send('{"type":"listuser","msg":""}') } function send() { var msg = $('#message').val(); if (msg.length==0){ $("#error").text("enter somthing first!") $("#error").flash(5,1000) $('#message').on("keydown",function(){$("#error").text("");$('#message').off("keydown");}) return; }else{ $("#error").text("") } $('#message').val(''); var to = $("#userlist").val(); appendMsg("myself",msg) msgObj={"msg":msg,"type":"msg","to":to}; jss=window.JSON.stringify(msgObj); sock.send(jss); console.log("I sent:",jss) } var lastMsg="" function appendMsg(from,msg){ msgPad = $('#log'); if (msg==lastMsg && from=="myself"){ p=msgPad.find("p:last-child") //alert(p[0].outerHTML); p.flash(5,500) return; } lastMsg = msg; str=""; if (from=="myself" || from=="self" || from=="me" ||from=="I"){ from="self" str ='<p class="s-msg"><i class="msg-from"> self '; }else{ str ='<p class="r-msg"><i class="msg-from">'+from+' '; } str = str+ '('+new Date().toLocaleString()+') :</i> <br/><span class="chat-msg">'+msg+'</span></p>' msgPad.append(str); msgPad.get(0).scrollTop = $('#log').get(0).scrollHeight; } function cls(){ msgPad = $('#log'); msgPad.empty(); } </script> </head> <body> <h1> 即时网上聊天WebSocket </h1> <div id="log"> </div> <div> <table > <tr id="signinpad" ><td><label for="user">user:</label></td> <td><input id="user" name="user" type="text""></input></td> <td><label for="password">pass:</label></td> <td><input id="password" name="password" type="password"></input></td> <td><input id="btnsignin" name="btnsignin" type="button" value="登录"></input></td> </tr> <tr><td><label from="userlist">to who:</label></td> <td> <select id="userlist" style="length:200px;"> <option value="all">所有人</option> </select> </td> <td><button id="btngetusers" onclick="loaduser()">rload users</button> <td><button id="btncls" onclick="cls()">clear</button> </tr> </table> <p> Message: <input id="message" type="text" value="Hello, world!" ><button id="btnsend" onclick="send();">Send Message</button> </p> <div id="error" style="color:red;"></div> </div> </body> </html>

    说明:其中,用户登录需要通过form提交到web后台的登录服务,我试着通过websocket自身服务实现登录,没有搞定保存session,不过如果聊天功能绑定在web网站上的话,应该也不需要单独登录功能。

  • 相关阅读:
    更新内核遇到 requested datatype primary not available
    在tmux缓冲区中搜索
    逻辑卷lvm消失问题.
    linux中的常用的proxy 代理方式
    windows下powershell的包管理工具
    一次docker镜像的迁移
    MAC OS 10.15 挂载ntfs文件系统并设置自动挂载RW模式.
    在linux中设置优先使用ipv4,而不是ipv6
    [搬家]新域名 akagi201.org
    基于Three.js的全景---photo-sphere-viewer
  • 原文地址:https://www.cnblogs.com/dajianshi/p/4222211.html
Copyright © 2011-2022 走看看