使用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)
服务端代码,基于某些原因,这里贴上部分代码,其余请大家根据框架自己很容易实现:
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开发简易聊天室的更多相关文章
- 基于Node.js + WebSocket 的简易聊天室
代码地址如下:http://www.demodashi.com/demo/13282.html Node.js聊天室运行说明 Node.js的本质就是运行在服务端的JavaScript.Node.js ...
- Java和WebSocket开发网页聊天室
小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...
- Android基于XMPP Smack openfire 开发的聊天室
Android基于XMPP Smack openfire 开发的聊天室(一)[会议服务.聊天室列表.加入] http://blog.csdn.net/lnb333666/article/details ...
- php+websocket搭建简易聊天室实践
1.前言 公司游戏里面有个简单的聊天室,了解了之后才知道是node+websocket做的,想想php也来做个简单的聊天室.于是搜集各种资料看文档.找实例自己也写了个简单的聊天室. http连接分为短 ...
- node.js+websocket实现简易聊天室
(文章是从我的个人主页上粘贴过来的,大家也可以访问我的主页 www.iwangzheng.com) websocket提供了一种全双工客户端服务器的异步通信方法,这种通信方法使用ws或者wss协议,可 ...
- node+websocket创建简易聊天室
关于websocket的介绍太多,在这就不一一介绍了,本文主要实现通过websocket创建一个简易聊天室,就是90年代那种聊天室 服务端 1.安装ws模块,uuid模块,ws是websocket模块 ...
- 基于WebSocket的简易聊天室
用的是Flash + WebSocket 哦~ Flask 之 WebSocket 一.项目结构: 二.导入模块 pip3 install gevent-websocket 三.先来看一个一对一聊天的 ...
- 用Martini、websocket实现单机版聊天室
ChatRoom A stand-alone ChatRoom in Martini Please Star https://github.com/renleimlj/ChatRoom Interfa ...
- 使用Html5下WebSocket搭建简易聊天室
一.Html5WebSocket介绍 WebSocket protocol 是HTML5一种新的协议(protocol).它是实现了浏览器与服务器全双工通信(full-duplex). 现在,很多网站 ...
随机推荐
- hdu3926(判断两个图是否相似,模版)
题意:给你2个图,最大度为2.问两个图是否相似. 思路:图中有环.有链,判断环的个数以及每个环组成的人数,还有链的个数以及每个链组成的人数 是否相等即可. 如果形成了环,那么每形成一个环,结点数就会多 ...
- Ubuntu 14.04快速搭建SVN服务器及日常使用
1.介绍 Subversion是一个自由,开源的版本控制系统,这个版本库就像一个普通的文件服务器,不同的是,它可以记录每一次文件和目录的修改情况.这样就可以很方面恢复到以前的版本,并可以查看数据更改 ...
- Oracle 报错:PLS-00201: 必须声明标识符
原因:调用其他用户的包或存储过程. 解决方法:在被调用的包或存储过程的用户下授权执行权限给调用用户: grant execute on 包名 to 用户名;
- 【Unity】使用Resources类管理资源
最近参考了各位大神的资源,初步学习了Unity的资源管理模式,包括在编辑器管理(使用AssetDatabase)和在运行时管理(使用Resources和AssetBundle).在此简单总结运行时用R ...
- [4G]4G模块的热重启
最近在调试4G模块,发现在开机启动时执行的AT指令会概率性的出现返回杂乱字符串的问题.想尽了各种办法还是行不通,在系统中使用minicom敲AT指令就不会有问题,开始怀疑是串口初始化的问题,修改了很多 ...
- postgresql with递归
在PostgreSQL里,with子句提供了一种方法写一个大的查询中使用的辅助报表与查询.它有助于打破复杂和大型查询简单易读的形式. 1. 建表 postgres=# create table tb9 ...
- Android——继续深造——从安装Android Studio 2.0开始(详)
一.下载JDK,JRE,SDK http://jingyan.baidu.com/article/eb9f7b6d884ea7869364e8eb.html 二.配置环境变量: 我的电脑->属性 ...
- es 剩余磁盘空间达到es最小值,添加数据被block
剩余磁盘空间达到es最小值,添加数据被block PUT _all/_settings {"index.blocks.read_only_allow_delete": null} ...
- Spring Cloud 获取注册中心所有服务以及服务下的所有实例
注册中心现有服务与实例数: 在任意客户端填写如下代码: /** * import org.springframework.cloud.client.ServiceInstance; * import ...
- WebRTC 源码分析(三):安卓视频硬编码
数据怎么送进编码器? 怎么从编码器取数据? 如何做流控? 在开始之前,我们先了解一下 MediaCodec 的基本知识. MediaCodec 基础 Developer 官网 上的描述已经很清楚了,下 ...