下载地址:olahol/melody: :notes: Go 的极简 websocket 框架
展示用宝塔搭建websocket 我第一次用go部署东西 有很多也不熟悉 走个流程即可
开始搭建
#先搭建好宝塔 我已经搭建好了宝塔 进入下载地址下载zip格式源码
#找到网站 GO项目 SDK管理 常用版本 安装1.24.5 (稳定版)
#下载melody-master.zip并上传到宝塔目录www/wwwroot/websocket并解压
#没有websocket文件夹就新建一个 解压出来的文件夹为melody-master
#进入目录
cd /www/wwwroot/websocket
#初始化模块(如果没做过)
go mod init melody-master
#临时使用国内代理 这会设置 Go 使用阿里云的 goproxy 国内代理,速度更快且更稳定。
go env -w GOPROXY=https://goproxy.cn,direct
#假设入口文件是 main.go,运行: websocket_server 是你编译成go的名字 随便改
#在www/wwwroot创建main.go 复制下面代码并保存 这个地址#https://github.com/olahol/melody/tree/master/examples
#里面有好多扩展main.go 自己去复制或者用我这个现成的
复制以下代码放进main.go并保存
package main
import (
"net/http"
"github.com/olahol/melody"
)
func main() {
m := melody.New()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
})
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
m.HandleRequest(w, r)
})
m.HandleMessage(func(s *melody.Session, msg []byte) {
m.Broadcast(msg)
})
http.ListenAndServe(":5000", nil)
}
自用app引用代码
package main
import (
"encoding/json"
"fmt"
"net/http"
"sync"
"github.com/olahol/melody"
)
// 定义一个结构来存储每个用户的信息
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
}
// 用一个线程安全的Map来存储所有在线用户
var (
users = make(map[*melody.Session]*User)
usersMutex = &sync.RWMutex{}
idCounter int64
)
// App端期望收到的用户列表消息结构
type UserListMessage struct {
Type string `json:"type"`
Users []string `json:"users"`
}
func main() {
m := melody.New()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
})
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
m.HandleRequest(w, r)
})
// 当一个新用户连接时
m.HandleConnect(func(s *melody.Session) {
idCounter++
newUser := &User{ID: idCounter, Name: fmt.Sprintf("User-%d", idCounter)}
usersMutex.Lock()
users[s] = newUser
usersMutex.Unlock()
fmt.Printf("User %s (ID: %d) connected.\n", newUser.Name, newUser.ID)
})
// 当一个用户断开连接时
m.HandleDisconnect(func(s *melody.Session) {
usersMutex.Lock()
if user, ok := users[s]; ok {
delete(users, s)
fmt.Printf("User %s (ID: %d) disconnected.\n", user.Name, user.ID)
}
usersMutex.Unlock()
})
// 当收到任何消息时
m.HandleMessage(func(s *melody.Session, msg []byte) {
var data map[string]interface{}
if err := json.Unmarshal(msg, &data); err != nil {
fmt.Println("Error decoding message:", err)
return
}
// 根据消息类型做不同处理
msgType, _ := data["type"].(string)
switch msgType {
case "set_username":
// 这是App连接成功后发的“自我介绍”
usersMutex.Lock()
if user, ok := users[s]; ok {
if newName, ok := data["username"].(string); ok {
fmt.Printf("User %s (ID: %d) is now known as %s.\n", user.Name, user.ID, newName)
user.Name = newName // 更新用户名
}
}
usersMutex.Unlock()
case "get_user_list":
// 这是我们的App定时发来的请求
usersMutex.RLock()
var userNames []string
for _, user := range users {
userNames = append(userNames, user.Name)
}
usersMutex.RUnlock()
// 构造符合App期望的响应
response := UserListMessage{
Type: "user_list",
Users: userNames,
}
// 只把响应发回给请求者
jsonResponse, _ := json.Marshal(response)
s.Write(jsonResponse)
case "chat_message", "sticker_message":
// 对于聊天和表情,我们依然需要更新用户名,以防用户中途改名
usersMutex.Lock()
if user, ok := users[s]; ok {
if newName, ok := data["username"].(string); ok {
if user.Name != newName {
fmt.Printf("User %s (ID: %d) has changed name to %s.\n", user.Name, user.ID, newName)
user.Name = newName
}
}
}
usersMutex.Unlock()
// 将聊天/表情消息广播给所有人
m.Broadcast(msg)
default:
// 如果是未知类型,就直接广播
fmt.Printf("Broadcasting unknown message type: %s\n", msgType)
m.Broadcast(msg)
}
})
fmt.Println("WebSocket server started at :5000")
http.ListenAndServe(":5000", nil)
}
#下载依赖
go mod tidy
#编译 websocket_server 是要编译出来的名字 编译方法在main.go
go build -o websocket_server main.go
测试脚本 index.html
随便找的 作者也有我没用我放在下面 把index.html 放到/www/wwwroot/
这个地址里面有很多index.html 你可以用我的也可以用他的反正是测试
https://github.com/olahol/melody/tree/master/examples
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Melody Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<input id="msg" placeholder="输入消息" />
<button onclick="send()">发送</button>
<ul id="chat"></ul>
<script>
const ws = new WebSocket(`ws://${location.host}/ws`);
ws.onmessage = e => {
const li = document.createElement("li");
li.textContent = e.data;
document.getElementById("chat").appendChild(li);
};
function send() {
const input = document.getElementById("msg");
ws.send(input.value);
input.value = "";
}
</script>
</body>
</html>
APP专用测试index.html
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test Client</title>
<style>
body { font-family: sans-serif; display: flex; flex-direction: column; height: 100vh; margin: 0; }
#header { padding: 10px; border-bottom: 1px solid #ccc; background-color: #f8f8f8; display: flex; align-items: center; }
#header input { margin-right: 10px; }
#content { display: flex; flex: 1; overflow: hidden; }
#chat-container { flex: 3; display: flex; flex-direction: column; border-right: 1px solid #ccc; }
#messages { flex: 1; list-style-type: none; margin: 0; padding: 10px; overflow-y: auto; }
#messages li { padding: 8px; border-bottom: 1px solid #eee; }
#messages li.sticker img { width: 48px; height: 48px; }
#form { display: flex; padding: 10px; border-top: 1px solid #ccc; }
#form input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 3px; }
#form button { margin-left: 10px; }
#user-list-container { flex: 1; padding: 10px; overflow-y: auto; }
#user-list-container h3 { margin-top: 0; }
#users { list-style-type: none; padding: 0; }
</style>
</head>
<body>
<div id="header">
<input id="username" value="WebAppUser" />
<button id="connectBtn">Connect</button>
</div>
<div id="content">
<div id="chat-container">
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
</div>
<div id="user-list-container">
<h3>Online Users (<span id="user-count">0</span>)</h3>
<ul id="users"></ul>
</div>
</div>
<script>
const usernameInput = document.getElementById('username');
const connectBtn = document.getElementById('connectBtn');
const form = document.getElementById('form');
const input = document.getElementById('input');
const messages = document.getElementById('messages');
const usersList = document.getElementById('users');
const userCount = document.getElementById('user-count');
let ws;
let pollingInterval;
const stickerMap = {
"ic_sticker_smile": `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="#FFC107" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/><path fill="#000000" d="M12 17.5c-2.49 0-4.5-2.01-4.5-4.5h9c0 2.49-2.01 4.5-4.5 4.5z"/><path fill="#000000" d="M8.5 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/><path fill="#000000" d="M15.5 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/></svg>`,
"ic_sticker_laugh": `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="#FFC107" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/><path fill="#000000" d="M12 17.5c-3.04 0-5.5-2.46-5.5-5.5h11c0 3.04-2.46 5.5-5.5 5.5z"/><path fill="#000000" d="M8.5 9C7.67 9 7 8.33 7 7.5S7.67 6 8.5 6s1.5.67 1.5 1.5S9.33 9 8.5 9z"/><path fill="#000000" d="M15.5 9c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5S16.33 9 15.5 9z"/></svg>`
};
function connect() {
const host = window.location.host;
ws = new WebSocket(`ws://${host}/ws`);
ws.onopen = function() {
console.log("Connected to server");
connectBtn.textContent = 'Disconnect';
connectBtn.onclick = disconnect;
// Introduce ourselves
const username = usernameInput.value || "WebAppUser";
ws.send(JSON.stringify({ type: 'set_username', username: username }));
// Start polling for user list
requestUserList();
pollingInterval = setInterval(requestUserList, 5000);
};
ws.onmessage = function (e) {
const data = JSON.parse(e.data);
switch(data.type) {
case "user_list":
updateUserList(data.users);
break;
case "chat_message":
addMessage(`${data.username}: ${data.message}`);
break;
case "sticker_message":
addSticker(data.username, data.sticker_name);
break;
}
};
ws.onclose = function() {
console.log("Disconnected from server");
clearInterval(pollingInterval);
connectBtn.textContent = 'Connect';
connectBtn.onclick = connect;
updateUserList([]);
};
}
function disconnect() {
if (ws) {
ws.close();
}
}
function requestUserList() {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'get_user_list' }));
}
}
function updateUserList(users) {
userCount.textContent = users.length;
usersList.innerHTML = '';
users.forEach(user => {
const item = document.createElement('li');
item.textContent = user;
usersList.appendChild(item);
});
}
function addMessage(text) {
const item = document.createElement('li');
item.textContent = text;
messages.appendChild(item);
messages.scrollTop = messages.scrollHeight;
}
function addSticker(username, stickerName) {
const item = document.createElement('li');
item.classList.add('sticker');
const stickerSVG = stickerMap[stickerName] || 'Sticker';
item.innerHTML = `<strong>${username}:</strong> ${stickerSVG}`;
messages.appendChild(item);
messages.scrollTop = messages.scrollHeight;
}
connectBtn.onclick = connect;
form.addEventListener('submit', function(e) {
e.preventDefault();
if (ws && ws.readyState === WebSocket.OPEN && input.value) {
const username = usernameInput.value || "WebAppUser";
const message = {
type: "chat_message",
username: username,
message: input.value
};
ws.send(JSON.stringify(message));
input.value = '';
}
});
</script>
</body>
</html>
启动你的 Go 服务
#启动你的 Go 服务
cd /www/wwwroot/websocket
./websocket_server
#我用宝塔的就不从这里启动了
点击网站 GO项目 添加GO项目
项目执行文件
/www/wwwroot/websocket/websocket_server
项目名称
websocket_server
项目端口
5000
执行命令
/www/wwwroot/websocket/websocket_server
应用变量 无
运行用户
www
开机启动 √
在添加一个域名和外网映射 就可以测试了
http://你的ip:5000
Comments NOTHING