使用websocket搭建一个即时通讯工具
使用语言
- go
- html+javascript
前端和后端可以跑的通
后端
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/gorilla/websocket"
"log"
"net/http"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // 解决跨域,否则就会连接失败
},
}
type MsgType struct {
// 和前端固定好的消息类型
Username string `json:"username"`
Value string `json:"value"`
}
var address = flag.String("addr", "localhost:8080", "http service address")
func handler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("aaa", err)
return
}
// 监听客户端传来的信息
go func() {
for {
// 监听从前端传来的消息
messageType, message, err := conn.ReadMessage()
// 把前端传来的数据变成结构体
jsonMsg := MsgType{}
fmt.Println("messageType", messageType)
_ = json.Unmarshal(message, &jsonMsg)
// 接收到前端传来的数据,给前端发送消息
sendMsg := MsgType{Username: "alex xu", Value: "hello world"}
conn.WriteJSON(sendMsg)
if err != nil {
// 客户端发送了关闭连接请求
return
}
}
}()
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(*address, nil))
}
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.info > div {
display: flex;
}
.info > div > div {
margin-right: 20px;
}
</style>
</head>
<body>
<input id="username" placeholder="请输入用户名">
<button id="login_btn">登陆</button>
<!--<button id="send_btn">发送</button>-->
<button id="logout">退出</button>
<div id="info" class="info">
<!-- <div>-->
<!-- <div>Alex:</div>-->
<!-- <div>hello world</div>-->
<!-- </div>-->
</div>
<script>
const conn_websocket = () => {
conn = new WebSocket("ws://localhost:8080/echo")
conn.onerror = (event, err) => {
// 失败了情况
console.log('error', event)
conn = ""
}
conn.onopen = () => {
// 成功的情况下
console.log('连接成功')
send_data({username: username_input.value, value: ""});
}
conn.onmessage = event => {
add_msg_to_html(JSON.parse(event.data))
}
conn.onclose = (event) => {
// 关闭了的情况下
conn = ""
console.log("关闭了", event)
}
}
const close_websocket = () => {
// 关闭websocket连接
conn.close()
conn = ""
alert("关闭成功")
}
const send_data = (data, to_html = false) => {
// 发送服务
if (conn === '') alert("websocket没有连接,请检查websocket是否连接成功");
else {
conn.send(JSON.stringify(data))
// 将发送的内容放在html里面
if (to_html) add_msg_to_html(data)
}
}
const create_input_el = (id, placeholder) => {
let input = document.querySelector(`#${id}`);
if (!input) {
input = document.createElement("input");
input.setAttribute("id", id);
input.setAttribute("placeholder", placeholder)
}
return input
}
const create_btn_el = (id, content, click_call_func) => {
/*
id: 元素的id
content: 按钮的内容
click_call_func: 点击事件的回掉函数
*/
let btn_el = document.querySelector(`#${id}`)
if (!btn_el) {
btn_el = document.createElement("button");
btn_el.innerText = content
btn_el.onclick = click_call_func
}
return btn_el
};
const send_btn_handler = () => {
// 发送消息
const send_input = document.querySelector("#send_data");
send_data({username: username_input.value, value: send_input.value}, true)
}
const add_msg_to_html = data => {
// 将要发送的内容写到html里面
const div = document.createElement("div");
div.innerHTML = `
<div>${data.username}:</div>
<div>${data.value}</div>
`;
info.appendChild(div);
}
</script>
<script>
let conn = "";
const login_btn = document.querySelector("#login_btn"); // 登陆按钮
const logout = document.querySelector("#logout"); // 退出按钮
const send_btn = document.querySelector("#send_btn"); // 发送按钮
const username_input = document.querySelector("#username");
const body = document.querySelector("body");
const info = document.querySelector("#info")
login_btn.onclick = async () => {
await conn_websocket()
body.appendChild(create_input_el("send_data", "请输入要发送的内容"));
body.appendChild(create_btn_el("send_btn", "发送", send_btn_handler));
}
logout.oncclick = close_websocket;
</script>
</body>
</html>
效果图
后端搭建开始搭建聊天室,让用户可以在一个聊天室里面聊天
在上面我们已经实现了发送和消息,这一步我们就要创建一个聊天室了,让他们可以在同一个房间内聊天
后端
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/gorilla/websocket"
"log"
"net/http"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // 解决跨域,否则就会连接失败
},
}
type UserConn struct {
// 每个用户的连接
RoomNo string
userName string
conn *websocket.Conn
}
type MsgType struct {
// 和前端固定好的消息类型
Username string `json:"username"`
Value string `json:"value"`
RoomNo string `json:"room_no"`
}
var address = flag.String("addr", "localhost:8080", "http service address")
var UserRoomDict = make(map[string][]UserConn) // 用户房间字典
func joinRoom(conn *websocket.Conn, msgType MsgType) {
/*
@desc: 把不同的用户加入同一个房间
@auth: alex
@data: 10/27/22
@params conn: 当前用户的连接,
@params msgType 用户传来的消息
*/
userConnSlice := make([]UserConn, 0)
userConnStruct := UserConn{userName: msgType.Username, conn: conn, RoomNo: msgType.RoomNo}
userConnSlice = append(userConnSlice, userConnStruct)
if UserRoomDict[msgType.RoomNo] != nil {
// 把用户和房间关联起来
// 表示当前房间已经和用户绑定在了一起
userConn := UserRoomDict[msgType.RoomNo]
// 判断当前用户是否已经出狱连接状态
exist := true
for index := 0; index < len(userConn); index++ {
if userConn[index].userName == msgType.Username {
exist = false
}
}
if exist {
UserRoomDict[msgType.RoomNo] = append(userConn, userConnStruct)
}
} else {
// 房间和用户没有绑定在一起,这是第一次登陆
roomUserSlice := make([]UserConn, 0)
roomUserSlice = append(roomUserSlice, userConnStruct)
UserRoomDict[msgType.RoomNo] = roomUserSlice
}
}
func sendMsg(msgType MsgType) {
/*
@desc: 给同一房间的用户发送消息
@auth: alex
@data: 10/27/22
@params msgType 用户传来的消息
*/
roomUserSlice := UserRoomDict[msgType.RoomNo]
fmt.Println("roomUserSlice", roomUserSlice)
for index := 0; index < len(roomUserSlice); index++ {
//fmt.Println("room_user_slice", roomUserSlice[index])
if msgType.Value != "" {
fmt.Println("room_user_slice", roomUserSlice[index])
userInfo := roomUserSlice[index]
if userInfo.userName != msgType.Username {
userInfo.conn.WriteJSON(MsgType{Username: msgType.Username, Value: msgType.Value})
}
}
}
}
func handler(w http.ResponseWriter, r *http.Request) {
//RoomUserSlice := make([]RoomUser, 0) // 用户房间切片
//UserConnSlice := make([]UserConn, 0) // 用户连接切片
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("aaa", err)
return
}
messageChan := make(chan MsgType)
// 监听客户端传来的信息
go func(messageChan chan MsgType) {
for {
// 监听从前端传来的消息
_, message, err := conn.ReadMessage()
// 把前端传来的数据变成结构体
jsonMsg := MsgType{}
_ = json.Unmarshal(message, &jsonMsg)
messageChan <- jsonMsg
if err != nil {
// 客户端发送了关闭连接请求
return
}
}
}(messageChan)
go func(messageChan chan MsgType) {
//处理消息
for {
select {
case message := <-messageChan:
fmt.Println("message", message)
if message.Value == "" {
joinRoom(conn, message)
} else {
sendMsg(message)
}
}
}
}(messageChan)
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(*address, nil))
}
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.info > div {
display: flex;
}
.info > div > div {
margin-right: 20px;
}
</style>
</head>
<body>
<label for="username"></label><input id="username" placeholder="请输入用户名">
<label for="room_no"></label> <input id="room_no" placeholder="请输入要加入的房间">
<button id="login_btn">登陆</button>
<button id="logout">退出</button>
<div id="info" class="info">
</div>
<script>
const conn_websocket = () => {
conn = new WebSocket("ws://localhost:8080/echo")
conn.onerror = (event, err) => {
// 失败了情况
console.log('error', event)
conn = ""
}
conn.onopen = () => {
// 成功的情况下
console.log('连接成功')
send_data({username: username_input.value, value: "", room_no: room_no_input.value});
}
conn.onmessage = event => {
add_msg_to_html(JSON.parse(event.data))
}
conn.onclose = (event) => {
// 关闭了的情况下
conn = ""
console.log("关闭了", event)
}
}
const close_websocket = () => {
// 关闭websocket连接
conn.close()
conn = ""
alert("关闭成功")
}
const send_data = (data, to_html = false) => {
// 发送服务
if (conn === '') alert("websocket没有连接,请检查websocket是否连接成功");
else {
conn.send(JSON.stringify(data))
// 将发送的内容放在html里面
if (to_html) add_msg_to_html(data)
}
}
const create_input_el = (id, placeholder) => {
let input = document.querySelector(`#${id}`);
if (!input) {
input = document.createElement("input");
input.setAttribute("id", id);
input.setAttribute("placeholder", placeholder)
}
return input
}
const create_btn_el = (id, content, click_call_func) => {
/*
id: 元素的id
content: 按钮的内容
click_call_func: 点击事件的回掉函数
*/
let btn_el = document.querySelector(`#${id}`)
if (!btn_el) {
btn_el = document.createElement("button");
btn_el.innerText = content
btn_el.onclick = click_call_func
}
return btn_el
};
const send_btn_handler = () => {
// 发送消息
const send_input = document.querySelector("#send_data");
send_data({username: username_input.value, value: send_input.value, room_no: room_no_input.value}, true)
}
const add_msg_to_html = data => {
// 将要发送的内容写到html里面
const div = document.createElement("div");
div.innerHTML = `
<div>${data.username}:</div>
<div>${data.value}</div>
`;
info.appendChild(div);
}
</script>
<script lang="ts">
let conn = "";
const login_btn = document.querySelector("#login_btn"); // 登陆按钮
const logout = document.querySelector("#logout"); // 退出按钮
const send_btn = document.querySelector("#send_btn"); // 发送按钮
const username_input = document.querySelector("#username");
const room_no_input = document.querySelector("#room_no"); // 房间号输入框
const body = document.querySelector("body");
const info = document.querySelector("#info")
login_btn.onclick = async () => {
if (room_no_input.value === "" || username_input.value === "") alert("请先输入用户名和选择房间");
else {
await conn_websocket()
body.appendChild(create_input_el("send_data", "请输入要发送的内容"));
body.appendChild(create_btn_el("send_btn", "发送", send_btn_handler));
}
}
// 退出连接
logout.oncclick = close_websocket;
</script>
</body>
</html>
效果
如果那里写的不好,希望大家可以提出来啊,谢谢了。