博主前几周学习了go语言的一些基础语法。于本周写出了这个小案列。在参考了 中文文档上的实例的同时,加入了与mysql数据库的结合。如果不想实现入库这部分功 能,可以自行略过。一、分析:
想要实现聊天室,客户端和服务端必不可少。
1.客户端:这里大家可以自己动手去写一个自己的客户端界面。也就是用户界面。博主这里写的比较随意。
而这部分重点在于与后端的交互:即发送请求与接收后端返回的数据。
后面我们根据代码再来详细介绍这部分。
2.服务端:我这里是用go来实现的。大家也可以选择其他语言来实现。
这部分重点在于处理用户发来的数据。
2.效果展示: 3.代码实现: 大家一定要看注释,里面是各部分代码功能的具体描述!!。 1.客户端: 1.1 首先使用webscoket建立连接:// 注意后面的chating,后端路由在调用的时候,和这个名字一致
var ws = new WebSocket("ws://127.0.0.1:8081/chating");
1.2 设置连接成功和连接失败出现的提示效果:
// 建立失败:
ws.onerror = function(){
var data = "系统消息:建立连接失败!!"
var p1 = document.createElement("p");
p1.innerText = data;
p1.style.marginTop = '10px';
p1.style.color = 'red';
chat.appendChild(p1);
}
// 建立成功
ws.onopen = function(){
var data = "系统消息:建立连接成功!!"
var p1 = document.createElement("p");
p1.style.marginTop = '10px';
p1.style.color = 'green';
p1.innerText = data;
chat.appendChild(p1);
}
1.3 设置回调函数的内容,也就是处理服务端向客户端发来的数据:
ws.onmessage = function(e){
// 首先对与后端传来的数据进行解析
var msg = JSON.parse(e.data);
var sender,user_name,name_list,change_type;
switch(msg.type){
// 当服务器表示连接成功时,前端向后端发送用户信息
case 'handshake':
// 注意发送的数据要是json格式
var user_info = {'type':'login','content':uname}
// 这个sendMsg是自定义的函数,用来讲用户传来的数据转换为json格式,然后调用websocket的send()方法,发送给服务端。
sendMsg(user_info)
return;
// 如果当前用户的状态是刚登录或者离线,那么对于传来的数据进行处理,使得在前端界面上出现一定的提示(xxx下线、在线人数)
case 'login':
case 'logout':
user_name = msg.content
name_list = msg.user_list
change_type = msg.type
// dealUser也是自定义的函数,用来对传入的数据进行处理,在界面上显示出当前在线人数,用户上线或下线信息。
dealUser(user_name,change_type,name_list)
return;
// 如果用户是在线状态,则获取用户昵称(用于在界面上显示,xxx发言)
case 'user':
sender = msg.from +":";
break;
case 'system':
sender = "系统消息";
break;
// 如果用户重复登录,则结束整个程序(这部分写的不好,没有找到如何关闭当前用户窗口的办法,只能结束整个程序了。)
case 'exist':
window.open("about:blank","_self").close()
}
var data = sender + msg.content;
// 将消息显示在聊天界面上。
var p1 = document.createElement("p");
p1.style.marginTop = '10px';
p1.innerText = data;
chat.appendChild(p1);
}
下面看一下上面所使用的函数:
1.sendMsg()
function sendMsg(msg){
// 编码
var data = JSON.stringify(msg);
ws.send(data);
}
2.dealUser
function dealUser(uname,type,ulist){
// 用户列表
var uls = document.querySelector('.user_list');
// 用户数量
var ctn = document.querySelector('.ctn');
// 每次操作移除所有用户
while(uls.hasChildNodes()){
uls.removeChild(uls.firstChild)
}
// 遍历后端返回的列表,显示所有在线用户
for(var index in ulist){
var user = document.createElement("li");
user.innerHTML = ulist[index];
uls.append(user);
}
// 在线人数
console.log(ulist.length)
ctn.innerHTML = ulist.length;
var change = type == 'login' ? '上线':'下线';
var data = '系统消息:'+ uname +'已'+change;
// 创建页面提示标签
var p1 = document.createElement("p");
p1.style.marginTop = '10px';
p1.style.color = 'gray';
p1.innerText = data;
chat.appendChild(p1);
}
1.4 由于用户发言方式在通过输入框input获取的,所以,我们可以给input添加上监听事件,当按下回车键时,发送数据:
// 给input输入框添加监听事件
var speak = document.querySelector('#speak');
speak.onkeypress = function(e){
if(e.which === 13){
// 这里是处理用户发送数据的自定义的函数
send();
}else{
console.log("发送失败");
}
}
// 输入框发送信息
function send(){
var reg = new RegExp("rh","g");
var data = speak.value;
data = data.replace(reg,"");
var msg = {
'content':data.trim(),
'type':'user'
}
sendMsg(msg)
// 发送完毕,清空输入框
speak.value = "";
}
2.服务端:
1.首先我们先建立data结构体,用于对应前端传送数据的内容
// 数据信息
type Data struct {
User string `json:"user"`
From string `json:"from"`
Type string `json:"type"`
Content string `json:"content"`
UserList [] string `json:"user_list"`
}
2.启动路由,监听接口
// 这里是博主自己添加的数据库初始化函数,考虑到有些读者可能不需要连接数据库,就不放代码了。
initDB()
router := mux.NewRouter()
// 注意:这里要和前端ws里面的名称相同。chat是要执行的回调函数。也是最重要的部分
router.HandleFunc("/chating",chat)
if err := http.ListenAndServe("127.0.0.1:8081",router);
err != nil{
fmt.Println("err1",err)
}
3.建立客户端和管理者结构体
用以上名字有点不太合适哈。因为是聊天室,一般是多人在线。所以,我们建立一个管理者,用来管理所有在线的用户。相对而言比较方便。
// 用户结构
type Connection struct {
// 连接
con *websocket.Conn
// 通道:用来传送数据
sc chan[]byte
// 数据
data *Data
}
// 管理者结构体
type Manager struct {
// 所有用户
allUser map[*Connection]bool
// 广播通道
broast chan [] byte
// 注册
zc chan *Connection
// 销毁
xh chan *Connection
}
4.获取数据
这里我们可以考虑获取数据了,对于服务端来说,接收到用户的请求后,要读取用户请求的内容,然后给予用户反馈,而这个给予过程也就是写入数据到前端的过程。因此,我们需要写2个方法,一个读,一个写。
处理请求:读取:
// 该方法的接收者,是当前用户
func (u *Connection) reader() {
for{
_,message,err := u.con.ReadMessage()
// 报错,则说明读取失败。改用户可能不存在。销毁这个用户
if err != nil{
// 将这个用户存入管理者的销毁区中,告诫管理者,该用户不存在
m.zc <- u
break
}
// 解析接收到的数据
err = json.Unmarshal(message,&u.data)
if err != nil{
fmt.Println("解析失败")
return
}
switch u.data.Type {
case "login":
// 如果是登录状态,则数据内容里存的就是用户名。
u.data.User = u.data.Content
u.data.From = u.data.User
//首先查询数据库中是否已经存在该用户
//不存在则添加
fmt.Println(!findUser(u.data.User))
if !findUser(u.data.User){
addUser(u.data.User)
//添加到当前列表
online_list = append(online_list,u.data.User)
fmt.Println(len(online_list))
//更新在线用户列表
u.data.UserList = online_list
}else{
// 存在判断用户是否在线
if isOnline(u.data.User){
// 在线修改状态,返回给前端
u.data.Type = "exist"
}else{
// 修改状态
modify(u.data.User,1)
//添加到当前列表
online_list = append(online_list,u.data.User)
fmt.Println(len(online_list))
u.data.UserList = online_list
}
}
// 将数据编码,放入到管理者的广播区域,用于广播给其他用户,当前用户的状态以及行为等。(上线、离线、发送数据等)
dataNew,_ := json.Marshal(u.data)
m.broast <- dataNew
// 用户处理在线聊天状态
case "user":
u.data.Type = "user"
dataNew,_ := json.Marshal(u.data)
m.broast <- dataNew
case "logout":
u.data.Type = "logout"
//修改用户状态
modify(u.data.User,0)
// 用户离线,需要从当前在线列表中删除,删除函数是自己写的哈。
online_list = del(online_list,u.data.User)
dataNew,_ := json.Marshal(u.data)
m.broast <- dataNew
// 将离线用户放入销毁区域
m.xh <- u
}
}
}
返回给客户端数据:写
// 向前端写入数据
func (u *Connection)write() {
for message := range u.sc{
u.con.WriteMessage(websocket.TextMessage,message)
}
u.con.Close()
}
5.回调函数chat
还记得路由连接成功后的回调函数嘛,这个才是重中之重,前面的功能我们已经完成。下一步就是写这个回调函数
// 在线用户列表
var online_list = [] string{}
// 这个博主也不太理解,大家可以查询一下
var wsNew = &websocket.Upgrader{ReadBufferSize: 512,WriteBufferSize: 512,CheckOrigin:func(r *http.Request)bool{return true}}
func chat(w http.ResponseWriter,r *http.Request) {
// 升级协议
ws,err := wsNew.Upgrade(w,r,nil)
if err != nil{
return
}
// 初始化一个用户
u := &Connection{con: ws,sc:make(chan[]byte),data:&Data{}}
// 把该用户传入管理者的注册区域(在管理区域中已经修改用户的状态为handshake了)
m.zc <- u
// 向前端写入数据(当前端判断用户的状态为handshake,则发送用户登录的信息,修改了状态为login)
go u.write()
u.reader()
// 这里博主也不是很理解哈,有知道了,可以在评论区告诉一下博主哈。
defer func() {
u.data.Type = "logout"
online_list = del(online_list,u.data.User)
u.data.UserList = online_list
u.data.Content = u.data.User
//修改用户状态
modify(u.data.Content,0)
dataNew,_ := json.Marshal(u.data)
m.broast <- dataNew
m.xh <- u
}()
}
6.管理者:
上面部分完成后,就差管理者了。主要是负责用户消息的广播、用户的销毁。
// 管理方法
func (m *Manager)run() {
for{
select {
// 获取注册的用户,存入用户列表。并修改状态为连接成功.讲修改后的状态写入数据通道,传递给前端
case u := <- m.zc:
m.allUser[u] = true
u.data.Type = "handshake"
u.data.UserList = online_list
dataNew,_ := json.Marshal(u.data)
u.sc <- dataNew
case u := <-m.xh:
// 判断退出的用户是否存在于用户列表,在,则删除改用户,关闭通道
if _,ok := m.allUser[u];ok{
delete(m.allUser,u)
close(u.sc)
}
case data := <- m.broast:
// 遍历所有用户列表。讲广播信息写入每个用用户的通道。供前端读取
for u := range m.allUser{
select {
case u.sc <- data:
default:
delete(m.allUser,u)
close(u.sc)
}
}
}
}
}
7.启动管理者方法
最后,我们不要忘记启动这个管理者的协程哈。回到路由连接那里:
func main() {
// 连接数据库
initDB()
go m.run()
router := mux.NewRouter()
router.HandleFunc("/chating",chat)
if err := http.ListenAndServe("127.0.0.1:8081",router);
err != nil{
fmt.Println("err1",err)
}
}
这样我们就实现了一个简单的聊天室了哈。
总结:以上内容算是对于go基础语法的巩固,如有错误,欢迎大家提出



