一、首先,需要了解一下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 =
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, )
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 ,\nstack 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网站上的话,应该也不需要单独登录功能。

使用go,基于martini,和websocket开发简易聊天室的更多相关文章

  1. 基于Node.js + WebSocket 的简易聊天室

    代码地址如下:http://www.demodashi.com/demo/13282.html Node.js聊天室运行说明 Node.js的本质就是运行在服务端的JavaScript.Node.js ...

  2. Java和WebSocket开发网页聊天室

    小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...

  3. Android基于XMPP Smack openfire 开发的聊天室

    Android基于XMPP Smack openfire 开发的聊天室(一)[会议服务.聊天室列表.加入] http://blog.csdn.net/lnb333666/article/details ...

  4. php+websocket搭建简易聊天室实践

    1.前言 公司游戏里面有个简单的聊天室,了解了之后才知道是node+websocket做的,想想php也来做个简单的聊天室.于是搜集各种资料看文档.找实例自己也写了个简单的聊天室. http连接分为短 ...

  5. node.js+websocket实现简易聊天室

    (文章是从我的个人主页上粘贴过来的,大家也可以访问我的主页 www.iwangzheng.com) websocket提供了一种全双工客户端服务器的异步通信方法,这种通信方法使用ws或者wss协议,可 ...

  6. node+websocket创建简易聊天室

    关于websocket的介绍太多,在这就不一一介绍了,本文主要实现通过websocket创建一个简易聊天室,就是90年代那种聊天室 服务端 1.安装ws模块,uuid模块,ws是websocket模块 ...

  7. 基于WebSocket的简易聊天室

    用的是Flash + WebSocket 哦~ Flask 之 WebSocket 一.项目结构: 二.导入模块 pip3 install gevent-websocket 三.先来看一个一对一聊天的 ...

  8. 用Martini、websocket实现单机版聊天室

    ChatRoom A stand-alone ChatRoom in Martini Please Star https://github.com/renleimlj/ChatRoom Interfa ...

  9. 使用Html5下WebSocket搭建简易聊天室

    一.Html5WebSocket介绍 WebSocket protocol 是HTML5一种新的协议(protocol).它是实现了浏览器与服务器全双工通信(full-duplex). 现在,很多网站 ...

随机推荐

  1. iOS友盟推送测试模式添加手机设备报红解决如下

    设备描述红色一般是没有往友盟发日志,或者appkey漏掉了.先检查是否正确的填写了推送的appkey,统计的方法为MobClick startWithAppkey:推送的方法为UMessage sta ...

  2. java基础篇---新I/O技术(NIO)

    在JDK1.4以前,I/O输入输出处理,我们把它称为旧I/O处理,在JDK1.4开始,java提供了一系列改进的输入/输出新特性,这些功能被称为新I/O(NEW I/O),新添了许多用于处理输入/输出 ...

  3. java中的元数据

    java中的Annotation和C#中的Attribute相似. 写法上差别较大 @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLA ...

  4. AutoLayout——何为intrinsic content size

    上一篇说到了约束就是等式和不等式.仅仅知道其原理还是没法拉出符合需求的线.所以这一篇主要看来什么是intrinsic content size,以及它有什么用. 在Xcode中,差点儿全部控件或视图, ...

  5. PowerDesigner导出表到HTML或word(实测有效)

    推荐生成HTML,因为看起来更加简洁 一.模版修改 在导出表时,powerdesigner默认为我们提供了很多的模版,在工具栏中选择[Report--->Report Template]即可看到 ...

  6. Map 与 JavaBean 的相互装换

    目的 为了方便使用 DbUtils,在插入数据时需要传入含有占位符的 SQL 语句和对应占位符的值(数组),封装代码如下: /** * 插入实体 */ public static <T> ...

  7. C#反序列化:xml转化为实体

    public static T DeserialXmlToModel<T>(string xmlDocument) { T cmdObj = default(T); XmlSerializ ...

  8. [uboot]uboot中am437对应的GPIO配置

    修改文件:board/ti/am43xx/mux.c 修改内容:void enable_board_pin_mux(configure_module_pin_mux(d3d4_pin_mux)

  9. mysql lower_case_table_names 区分表名大小写设置

    Command-Line Format --lower-case-table-names[=#] System Variable Name lower_case_table_names Variabl ...

  10. 【oneday_onepage】——美国主食吃什么

    Cocktail 鸡尾酒 It is quite usual to drink cocktails before lunch and dinner in America and somewhat le ...