热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

go实现聊天室功能

初学golang,发现语言层面真的和php不太一样,不过学习上没有特别大的困难,这个小案例也算一个学习心得,以下代码并不是自己去实现的,我只是做了相关的整理,去理解总体的实现流程逻






初学golang,发现语言层面真的和php不太一样,不过学习上没有特别大的困难,这个小案例也算一个学习心得,以下代码并不是自己去实现的,我只是做了相关的整理,去理解总体的实现流程逻辑

实现效果如下图所示:

go实现聊天室功能

整体的逻辑如下,hub结构体中存储着聊天室所有的connection用户连接信息,当用户登录或登出时,通过channel传递信息,将hub结构体中的用户连接信息添加或删除。每当有用户连接时,服务端会新增一个协程,保持对应连接,并分别创建数据读取器和数据写入器,针对该websocket进行读取和写入。

go实现聊天室功能

在通信协议上,使用了websocket协议,它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。websocket协议在实现上基于http协议,通过Upgrade将协议升级。

服务端代码实现(已添加注释可放心阅读):

server.go

package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
//开启协程启动connection服务管理中心
go h.run()
//创建ws服务
router.HandleFunc("/ws", myws)
//启动http服务
if err := http.ListenAndServe("127.0.0.1:8080", router); err != nil {
fmt.Println("err:", err)
}
}
//用户中心,维护多个用户的connection
var h = hub{
c: make(map[*connection]bool),
u: make(chan *connection),
b: make(chan []byte),
r: make(chan *connection),
}
type hub struct {
//当前在线connection信息
c map[*connection]bool
//删除connection
u chan *connection
//传递数据
b chan []byte
//加入connection
r chan *connection
}
func (h *hub) run() {
for {
select {
//用户连接,添加connection信息
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:
//发送不成功则删除connection信息
default:
delete(h.c, c)
close(c.sc)
}
}
}
}
}

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 user_list = []string{}
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"`
}
var wu = &websocket.Upgrader{ReadBufferSize: 512,
WriteBufferSize: 512, CheckOrigin: func(r *http.Request) bool { return true }}
//websocket服务
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{}}
//connection加入hub管理
h.r <- c
go c.writer()
c.reader()
//退出登录
defer logout(c)
}
//数据写入器
func (c *connection) writer() {
//取出发送信息并写入
for message := range c.sc {
fmt.Println(message,"\n")
c.ws.WriteMessage(websocket.TextMessage, message)
}
c.ws.Close()
}
//数据读取器
func (c *connection) reader() {
for {
//接收ws信息
_, 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
}
}
return n_slice
}
//退出
func logout(c *connection){
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
}

客户端代码实现:

<!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">
<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>



go


推荐阅读
author-avatar
杨扬洒傻
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有