websocekt 的导入
WebSocket是一种在单个TCP连接上进行全双工通信的协议 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据 在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输 需要安装第三方包: cmd中:go get -u -v github.com/gorilla/websocket
websocket 聊天室数据结构分析
需要有一个客户端 client 的 manager ,manager 里应该保存所有的client 信息 在我们的程序里定义了 ClientManager 这个结构体 用 clients 这个 map 结构来保存所有的连接信息 遍历 clients 通过使用 broadcast 这个 channel 把 web 端传送来的消息分发给所有的客户端client 其次每个成功建立长连接 的 client 开一个 read 协程和 wrtie 协程 read 协程不断读取 web 端输入的 meaasge,并把 message 传递给 boradcast ,让 manager 遍历 clients 把 message 通过 broadcast channel ,传递给各个客户端 client 的 send channelwrite 协程不断的将 send channel 里的消息发送给 web 端
data.go
package main
type Data struct {
Ip string `json:"ip"`
User string `json:"user"`
From string `json:"from"`
Type string `json:"type"`
Content string `json:"content"`
UserList [ ] string `json:"user_list"`
}
connection.go
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/websocket"
)
type connection struct {
ws * websocket. Conn
sc chan [ ] byte
data * Data
}
var wu = & websocket. Upgrader{ ReadBufferSize: 512 ,
WriteBufferSize: 512 , CheckOrigin: func ( r * http. Request) bool { return true } }
func myws ( w http. ResponseWriter, r * http. Request) {
ws, err := wu. Upgrade ( w, r, nil )
if err != nil {
return
}
c := & connection{ sc: make ( chan [ ] byte , 256 ) , ws: ws, data: & Data{ } }
h. r <- c
go c. writer ( )
c. reader ( )
defer func ( ) {
c. data. Type = "logout"
user_list = del ( user_list, c. data. User)
c. data. UserList = user_list
c. data. Content = c. data. User
data_b, _ := json. Marshal ( c. data)
h. b <- data_b
h. r <- c
} ( )
}
func ( c * connection) writer ( ) {
for message := range c. sc {
c. ws. WriteMessage ( websocket. TextMessage, message)
}
c. ws. Close ( )
}
var user_list = [ ] string { }
func ( c * connection) reader ( ) {
for {
_ , message, err := c. ws. ReadMessage ( )
if err != nil {
h. r <- c
break
}
json. Unmarshal ( message, & c. data)
switch c. data. Type {
case "login" :
c. data. User = c. data. Content
c. data. From = c. data. User
user_list = append ( user_list, c. data. User)
c. data. UserList = user_list
data_b, _ := json. Marshal ( c. data)
h. b <- data_b
case "user" :
c. data. Type = "user"
data_b, _ := json. Marshal ( c. data)
h. b <- data_b
case "logout" :
c. data. Type = "logout"
user_list = del ( user_list, c. data. User)
data_b, _ := json. Marshal ( c. data)
h. b <- data_b
h. r <- c
default :
fmt. Print ( "========default================" )
}
}
}
func del ( slice [ ] string , user string ) [ ] string {
count := len ( slice)
if count == 0 {
return slice
}
if count == 1 && slice[ 0 ] == user {
return [ ] string { }
}
var n_slice = [ ] string { }
for i := range slice {
if slice[ i] == user && i == count {
return slice[ : count]
} else if slice[ i] == user {
n_slice = append ( slice[ : i] , slice[ i+ 1 : ] ... )
break
}
}
fmt. Println ( n_slice)
return n_slice
}
hub.go
package main
import "encoding/json"
var h = hub{
c: make ( map [ * connection] bool ) ,
u: make ( chan * connection) ,
b: make ( chan [ ] byte ) ,
r: make ( chan * connection) ,
}
type hub struct {
c map [ * connection] bool
b chan [ ] byte
r chan * connection
u chan * connection
}
func ( h * hub) run ( ) {
for {
select {
case c := <- h. r:
h. c[ c] = true
c. data. Ip = c. ws. RemoteAddr ( ) . String ( )
c. data. Type = "handshake"
c. data. UserList = user_list
data_b, _ := json. Marshal ( c. data)
c. sc <- data_b
case c := <- h. u:
if _ , ok := h. c[ c] ; ok {
delete ( h. c, c)
close ( c. sc)
}
case data := <- h. b:
for c := range h. c {
select {
case c. sc <- data:
default :
delete ( h. c, c)
close ( c. sc)
}
}
}
}
}
server.go
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main ( ) {
router := mux. NewRouter ( )
go h. run ( )
router. HandleFunc ( "/ws" , myws)
if err := http. ListenAndServe ( "127.0.0.1:8080" , router) ; err != nil {
fmt. Println ( "err:" , err)
}
}
local.html
<! DOCTYPE html >
< html>
< head>
< title> </ title>
< meta http-equiv = " content-type" content = " text/html;charset=utf-8" >
< style>
p {
text-align : left;
padding-left : 20px;
}
</ style>
</ head>
< body>
< div style = " width : 800px; height : 600px; margin : 30px auto; text-align : center" >
< h1> www.5lmh.comy演示聊天室</ h1>
< div style = " width : 800px; border : 1px solid gray; height : 300px; " >
< div style = " width : 200px; height : 300px; float : left; text-align : left; " >
< p> < span> 当前在线:</ span> < span id = " user_num" > 0</ span> </ p>
< div id = " user_list" style = " overflow : auto; " >
</ div>
</ div>
< div id = " msg_list" style = " width : 598px; border : 1px solid gray; height : 300px; overflow : scroll; float : left; " >
</ div>
</ div>
< br>
< textarea id = " msg_box" rows = " 6" cols = " 50" onkeydown = " confirm ( event) " > </ textarea> < br>
< input type = " button" value = " 发送" onclick = " send ( ) " >
</ div>
</ body>
</ html>
< script type = " text/javascript" >
var uname = prompt ( '请输入用户名' , 'user' + uuid ( 8 , 16 ) ) ;
var ws = new WebSocket ( "ws://127.0.0.1:8080/ws" ) ;
ws. onopen = function ( ) {
var data = "系统消息:建立连接成功" ;
listMsg ( data) ;
} ;
ws. onmessage = function ( e ) {
var msg = JSON . parse ( e. data) ;
var sender, user_name, name_list, change_type;
switch ( msg. type) {
case 'system' :
sender = '系统消息: ' ;
break ;
case 'user' :
sender = msg. from + ': ' ;
break ;
case 'handshake' :
var user_info = { 'type' : 'login' , 'content' : uname} ;
sendMsg ( user_info) ;
return ;
case 'login' :
case 'logout' :
user_name = msg. content;
name_list = msg. user_list;
change_type = msg. type;
dealUser ( user_name, change_type, name_list) ;
return ;
}
var data = sender + msg. content;
listMsg ( data) ;
} ;
ws. onerror = function ( ) {
var data = "系统消息 : 出错了,请退出重试." ;
listMsg ( data) ;
} ;
function confirm ( event ) {
var key_num = event. keyCode;
if ( 13 == key_num) {
send ( ) ;
} else {
return false ;
}
}
function send ( ) {
var msg_box = document. getElementById ( "msg_box" ) ;
var content = msg_box. value;
var reg = new RegExp ( "\r\n" , "g" ) ;
content = content. replace ( reg, "" ) ;
var msg = { 'content' : content. trim ( ) , 'type' : 'user' } ;
sendMsg ( msg) ;
msg_box. value = '' ;
}
function listMsg ( data ) {
var msg_list = document. getElementById ( "msg_list" ) ;
var msg = document. createElement ( "p" ) ;
msg. innerHTML = data;
msg_list. appendChild ( msg) ;
msg_list. scrollTop = msg_list. scrollHeight;
}
function dealUser ( user_name, type, name_list ) {
var user_list = document. getElementById ( "user_list" ) ;
var user_num = document. getElementById ( "user_num" ) ;
while ( user_list. hasChildNodes ( ) ) {
user_list. removeChild ( user_list. firstChild) ;
}
for ( var index in name_list) {
var user = document. createElement ( "p" ) ;
user. innerHTML = name_list[ index] ;
user_list. appendChild ( user) ;
}
user_num. innerHTML = name_list. length;
user_list. scrollTop = user_list. scrollHeight;
var change = type == 'login' ? '上线' : '下线' ;
var data = '系统消息: ' + user_name + ' 已' + change;
listMsg ( data) ;
}
function sendMsg ( msg ) {
var data = JSON . stringify ( msg) ;
ws. send ( data) ;
}
function uuid ( len, radix ) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . split ( '' ) ;
var uuid = [ ] , i;
radix = radix || chars. length;
if ( len) {
for ( i = 0 ; i < len; i++ ) uuid[ i] = chars[ 0 | Math. random ( ) * radix] ;
} else {
var r;
uuid[ 8 ] = uuid[ 13 ] = uuid[ 18 ] = uuid[ 23 ] = '-' ;
uuid[ 14 ] = '4' ;
for ( i = 0 ; i < 36 ; i++ ) {
if ( ! uuid[ i] ) {
r = 0 | Math. random ( ) * 16 ;
uuid[ i] = chars[ ( i == 19 ) ? ( r & 0x3 ) | 0x8 : r] ;
}
}
}
return uuid. join ( '' ) ;
}
</ script>
运行与测试