网络层
This commit is contained in:
175
Client/a.html
175
Client/a.html
@@ -1,175 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>游戏方块容器</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: #f0f0f0; /* 页面背景色 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
grid-template-rows: repeat(4, 1fr);
|
|
||||||
gap: 15px;
|
|
||||||
width: min(100vw, 100vh); /* 取视口宽高的较小值 */
|
|
||||||
height: min(100vw, 100vh); /* 确保容器是正方形 */
|
|
||||||
padding: 20px;
|
|
||||||
background-color: white; /* 容器背景为白色 */
|
|
||||||
margin: 0 auto; /* 水平居中 */
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%); /* 完全居中 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-item {
|
|
||||||
background-color: white; /* 大方块背景为白色 */
|
|
||||||
border: 2px solid #e0e0e0; /* 柔和的边框 */
|
|
||||||
border-radius: 6px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap; /* 允许内部小方块换行 */
|
|
||||||
position: relative; /* 为内部元素定位做准备 */
|
|
||||||
overflow: hidden; /* 隐藏超出部分 */
|
|
||||||
/* 不再需要aspect-ratio,因为grid布局会自动保持正方形 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 内部小方块样式 */
|
|
||||||
.inner-square {
|
|
||||||
width: 25%; /* 4x4网格,每个占25%宽度 */
|
|
||||||
height: 25%; /* 4x4网格,每个占25%高度 */
|
|
||||||
border: 1px solid #f5f5f5; /* 浅色边框分隔 */
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 状态指示器 */
|
|
||||||
.status-indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #ccc; /* 默认灰色(未连接) */
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-connected {
|
|
||||||
background-color: #2ecc71; /* 连接成功时绿色 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-error {
|
|
||||||
background-color: #e74c3c; /* 错误时红色 */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="grid-container">
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const container = document.querySelector('.grid-container');
|
|
||||||
|
|
||||||
// 创建16个大方块(容器)
|
|
||||||
for (let i = 1; i <= 16; i++) {
|
|
||||||
const gridItem = document.createElement('div');
|
|
||||||
gridItem.className = 'grid-item';
|
|
||||||
gridItem.id = `grid-${i}`; // 为每个方块设置唯一ID
|
|
||||||
|
|
||||||
// 添加状态指示器
|
|
||||||
const statusIndicator = document.createElement('div');
|
|
||||||
statusIndicator.className = 'status-indicator';
|
|
||||||
gridItem.appendChild(statusIndicator);
|
|
||||||
|
|
||||||
// 在方块内部创建4x4小方块
|
|
||||||
for (let j = 0; j < 16; j++) {
|
|
||||||
const innerSquare = document.createElement('div');
|
|
||||||
innerSquare.className = 'inner-square';
|
|
||||||
gridItem.appendChild(innerSquare);
|
|
||||||
}
|
|
||||||
|
|
||||||
container.appendChild(gridItem);
|
|
||||||
|
|
||||||
// 为每个大方块创建独立的WebSocket连接
|
|
||||||
connectWebSocket(gridItem, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket连接函数
|
|
||||||
function connectWebSocket(gridItem, index) {
|
|
||||||
// 替换为实际的WebSocket服务器地址
|
|
||||||
const wsUrl = `ws://localhost:8080/game/${index}`;
|
|
||||||
const ws = new WebSocket(wsUrl);
|
|
||||||
|
|
||||||
// 获取状态指示器
|
|
||||||
const statusIndicator = gridItem.querySelector('.status-indicator');
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
console.log(`方块 ${index} 已连接到服务器`);
|
|
||||||
statusIndicator.classList.add('status-connected');
|
|
||||||
statusIndicator.classList.remove('status-error');
|
|
||||||
|
|
||||||
// 发送初始化消息(示例)
|
|
||||||
ws.send(JSON.stringify({
|
|
||||||
type: 'init',
|
|
||||||
gridId: index
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
|
||||||
console.log(`方块 ${index} 收到消息:`, event.data);
|
|
||||||
// 在此处理服务器消息,更新小方块状态
|
|
||||||
// 示例:根据消息改变小方块颜色
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
if (data.type === 'update') {
|
|
||||||
updateInnerSquares(gridItem, data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
|
||||||
console.error(`方块 ${index} 连接错误:`, error);
|
|
||||||
statusIndicator.classList.remove('status-connected');
|
|
||||||
statusIndicator.classList.add('status-error');
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
console.log(`方块 ${index} 连接关闭`);
|
|
||||||
statusIndicator.classList.remove('status-connected');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 将WebSocket实例附加到DOM元素上
|
|
||||||
gridItem.ws = ws;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新小方块状态的函数(示例)
|
|
||||||
function updateInnerSquares(gridItem, data) {
|
|
||||||
const innerSquares = gridItem.querySelectorAll('.inner-square');
|
|
||||||
innerSquares.forEach((square, idx) => {
|
|
||||||
// 根据服务器数据更新小方块样式
|
|
||||||
// 示例:随机改变背景色
|
|
||||||
if (data.squares && data.squares[idx]) {
|
|
||||||
square.style.backgroundColor = data.squares[idx].color;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 窗口关闭时关闭所有WebSocket连接
|
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
document.querySelectorAll('.grid-item').forEach(item => {
|
|
||||||
if (item.ws && item.ws.readyState === WebSocket.OPEN) {
|
|
||||||
item.ws.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
103
Client/web/game.js
Normal file
103
Client/web/game.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
const { createApp } = Vue;
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
template: `
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="square" v-for="(square, index) in squares" :key="index">
|
||||||
|
<div class="connection-dot" :class="{ connected: square.connected }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
squares: [
|
||||||
|
{ connected: false, ws: null },
|
||||||
|
{ connected: false, ws: null },
|
||||||
|
{ connected: false, ws: null },
|
||||||
|
{ connected: false, ws: null }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 为每个正方形建立独立的WebSocket连接
|
||||||
|
this.squares.forEach((square, index) => {
|
||||||
|
this.connectWebSocket(square, index);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
connectWebSocket(square, index) {
|
||||||
|
// 替换为实际的WebSocket服务器地址
|
||||||
|
const wsUrl = `ws://localhost:8501/?token=${index + 1}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
square.ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
square.ws.onopen = () => {
|
||||||
|
square.connected = true;
|
||||||
|
console.log(`WebSocket ${index + 1} connected`);
|
||||||
|
|
||||||
|
// 连接建立后发送初始消息
|
||||||
|
try {
|
||||||
|
square.ws.send(JSON.stringify({
|
||||||
|
type: "init"
|
||||||
|
}));
|
||||||
|
console.log(`Initial message sent to WebSocket ${index + 1}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to send initial message to WebSocket ${index + 1}:`, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
square.ws.onclose = (e) => {
|
||||||
|
square.connected = false;
|
||||||
|
console.log(`WebSocket ${index + 1} disconnected`);
|
||||||
|
console.log(e.code, e.reason, e.wasClean)
|
||||||
|
// 尝试重新连接
|
||||||
|
// setTimeout(() => this.connectWebSocket(square, index), 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
square.ws.onerror = (error) => {
|
||||||
|
console.error(`WebSocket ${index + 1} error:`, error);
|
||||||
|
};
|
||||||
|
|
||||||
|
square.ws.onmessage = (event) => {
|
||||||
|
console.log(`WebSocket ${index + 1} message:`, event.data);
|
||||||
|
// 处理接收到的消息
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
const arr = JSON.parse(message.data);
|
||||||
|
|
||||||
|
if (Array.isArray(arr) && arr.length === 2) {
|
||||||
|
const [x, y] = arr;
|
||||||
|
console.log(`Creating dot at (${x}, ${y}) for square ${index}`);
|
||||||
|
|
||||||
|
const squareElement = document.querySelectorAll('.square')[index];
|
||||||
|
if (!squareElement) {
|
||||||
|
console.error('Square element not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建圆点元素
|
||||||
|
const dot = document.createElement('div');
|
||||||
|
dot.className = 'game-dot';
|
||||||
|
dot.style.left = `${x}px`;
|
||||||
|
dot.style.top = `${y}px`;
|
||||||
|
dot.style.zIndex = '10';
|
||||||
|
|
||||||
|
// 添加到游戏场景
|
||||||
|
squareElement.appendChild(dot);
|
||||||
|
console.log('Dot added successfully');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing message:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`WebSocket ${index + 1} init error:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
createApp(App).mount('#app');
|
||||||
17
Client/web/index.html
Normal file
17
Client/web/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>游戏方块容器</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="game.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
80
Client/web/style.css
Normal file
80
Client/web/style.css
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/* 重置默认边距 */
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 应用容器填满整个视口并居中 */
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 重置body样式 */
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网格容器 - 固定2x2布局 */
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 400px 400px;
|
||||||
|
grid-template-rows: 400px 400px;
|
||||||
|
gap: 30px;
|
||||||
|
padding: 40px;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0 auto;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 正方形样式 */
|
||||||
|
.square {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(to right, #ddd 1px, transparent 1px),
|
||||||
|
linear-gradient(to bottom, #ddd 1px, transparent 1px);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
border: 5px solid #555;
|
||||||
|
border-radius: 15px;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 连接状态圆点 */
|
||||||
|
.connection-dot {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #ff4444; /* 默认断开状态-红色 */
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-dot.connected {
|
||||||
|
background-color: #44ff44; /* 连接状态-绿色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 游戏动态圆点 */
|
||||||
|
.game-dot {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: red;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"gateway/config"
|
"gateway/config"
|
||||||
"gateway/grpc_server"
|
"gateway/grpc_server"
|
||||||
"gateway/handler/ws_handler"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/judwhite/go-svc"
|
"github.com/judwhite/go-svc"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@@ -52,10 +51,16 @@ func (p *Program) Start() error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
discover.Listen()
|
discover.Listen()
|
||||||
p.server = grpc_server.NewServer(config.Get().Grpc.Registry.TTL)
|
p.server = grpc_server.NewServer(config.Get().Serve.Grpc.TTL)
|
||||||
p.server.Init(config.Get().Grpc.Registry.Address, config.Get().Grpc.Registry.Port)
|
p.server.Init(config.Get().Serve.Grpc.Address, config.Get().Serve.Grpc.Port)
|
||||||
|
go func() {
|
||||||
ws_handler.NewClient(123, nil)
|
cfg := config.Get()
|
||||||
|
_ = p.wsServer.Run(
|
||||||
|
log.GetLogger().Named("gnet"),
|
||||||
|
fmt.Sprintf("tcp4://0.0.0.0:%v", cfg.Serve.Socket.Web.Port),
|
||||||
|
true, true, false, false, true, 8,
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func (p *Program) initWsServer(cfg *config.Config) error {
|
|||||||
//)
|
//)
|
||||||
p.wsServer = websocket.NewWSServer(
|
p.wsServer = websocket.NewWSServer(
|
||||||
&ws_gateway.GatewayWsServer{},
|
&ws_gateway.GatewayWsServer{},
|
||||||
log.GetLogger(),
|
log.GetLogger().Named("ws_server"),
|
||||||
5*time.Second,
|
5*time.Second,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,22 @@ log:
|
|||||||
max_backups: 3
|
max_backups: 3
|
||||||
max_age: 7
|
max_age: 7
|
||||||
|
|
||||||
grpc:
|
db:
|
||||||
registry:
|
etcd:
|
||||||
|
address: [ "10.0.40.9:2379" ]
|
||||||
|
|
||||||
|
serve:
|
||||||
|
grpc:
|
||||||
address: "10.0.40.199"
|
address: "10.0.40.199"
|
||||||
port: 8500
|
port: 8500
|
||||||
ttl: 20
|
ttl: 20
|
||||||
|
socket:
|
||||||
db:
|
web:
|
||||||
etcd:
|
address: "0.0.0.0"
|
||||||
address: [ "10.0.40.9:2379" ]
|
port: 8501
|
||||||
|
raw:
|
||||||
|
address: "0.0.0.0"
|
||||||
|
port: 8502
|
||||||
|
http:
|
||||||
|
address: "0.0.0.0"
|
||||||
|
port: 8503
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
App AppConfig `yaml:"app"`
|
App *AppConfig `yaml:"app"`
|
||||||
Log LogConfig `yaml:"log"`
|
Log *LogConfig `yaml:"log"`
|
||||||
Grpc GrpcConfig `yaml:"grpc"`
|
DB *DBConfig `yaml:"db"`
|
||||||
DB DBConfig `yaml:"db"`
|
Serve *ServeConfig `yaml:"serve"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppConfig struct {
|
type AppConfig struct {
|
||||||
@@ -19,16 +19,25 @@ type LogConfig struct {
|
|||||||
Level string `yaml:"level"`
|
Level string `yaml:"level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GrpcConfig struct {
|
|
||||||
Registry *struct {
|
|
||||||
Address string `yaml:"address"`
|
|
||||||
Port int `yaml:"port"`
|
|
||||||
TTL int64 `yaml:"ttl"`
|
|
||||||
} `yaml:"registry"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DBConfig struct {
|
type DBConfig struct {
|
||||||
Etcd *struct {
|
Etcd *struct {
|
||||||
Address []string `yaml:"address"`
|
Address []string `yaml:"address"`
|
||||||
} `yaml:"etcd"`
|
} `yaml:"etcd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServeConfig struct {
|
||||||
|
Grpc *struct {
|
||||||
|
AddressConfig
|
||||||
|
TTL int64 `yaml:"ttl"`
|
||||||
|
} `yaml:"grpc"`
|
||||||
|
Socket *struct {
|
||||||
|
Web *AddressConfig `yaml:"web"`
|
||||||
|
Raw *AddressConfig `yaml:"raw"`
|
||||||
|
} `yaml:"socket"`
|
||||||
|
Http *AddressConfig `yaml:"http"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddressConfig struct {
|
||||||
|
Address string `yaml:"address"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ require (
|
|||||||
common v0.0.0-00010101000000-000000000000
|
common v0.0.0-00010101000000-000000000000
|
||||||
github.com/gin-contrib/cors v1.7.6
|
github.com/gin-contrib/cors v1.7.6
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/gobwas/ws v1.4.0
|
|
||||||
github.com/golang/protobuf v1.5.4
|
|
||||||
github.com/judwhite/go-svc v1.2.1
|
github.com/judwhite/go-svc v1.2.1
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
go.mongodb.org/mongo-driver v1.17.4
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/text v0.26.0
|
|
||||||
google.golang.org/grpc v1.71.1
|
google.golang.org/grpc v1.71.1
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
)
|
)
|
||||||
@@ -20,11 +17,9 @@ require (
|
|||||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||||
github.com/bytedance/sonic v1.13.3 // indirect
|
github.com/bytedance/sonic v1.13.3 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/getsentry/sentry-go v0.34.0 // indirect
|
github.com/getsentry/sentry-go v0.34.0 // indirect
|
||||||
@@ -35,8 +30,10 @@ require (
|
|||||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
|
github.com/gobwas/ws v1.4.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
@@ -48,7 +45,6 @@ require (
|
|||||||
github.com/panjf2000/ants/v2 v2.11.3 // indirect
|
github.com/panjf2000/ants/v2 v2.11.3 // indirect
|
||||||
github.com/panjf2000/gnet/v2 v2.9.1 // indirect
|
github.com/panjf2000/gnet/v2 v2.9.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.10.0 // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.12.0 // indirect
|
github.com/spf13/afero v1.12.0 // indirect
|
||||||
@@ -62,12 +58,12 @@ require (
|
|||||||
go.etcd.io/etcd/client/pkg/v3 v3.6.1 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.6.1 // indirect
|
||||||
go.etcd.io/etcd/client/v3 v3.6.1 // indirect
|
go.etcd.io/etcd/client/v3 v3.6.1 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
|
||||||
golang.org/x/arch v0.18.0 // indirect
|
golang.org/x/arch v0.18.0 // indirect
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
golang.org/x/sync v0.15.0 // indirect
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
|
golang.org/x/text v0.26.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
|
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
|
||||||
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
|
||||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
|
||||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||||
@@ -11,8 +7,6 @@ github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1
|
|||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
@@ -23,8 +17,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||||
@@ -112,8 +104,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
|
|
||||||
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||||
@@ -154,8 +144,6 @@ go.etcd.io/etcd/client/pkg/v3 v3.6.1 h1:CxDVv8ggphmamrXM4Of8aCC8QHzDM4tGcVr9p2BS
|
|||||||
go.etcd.io/etcd/client/pkg/v3 v3.6.1/go.mod h1:aTkCp+6ixcVTZmrJGa7/Mc5nMNs59PEgBbq+HCmWyMc=
|
go.etcd.io/etcd/client/pkg/v3 v3.6.1/go.mod h1:aTkCp+6ixcVTZmrJGa7/Mc5nMNs59PEgBbq+HCmWyMc=
|
||||||
go.etcd.io/etcd/client/v3 v3.6.1 h1:KelkcizJGsskUXlsxjVrSmINvMMga0VWwFF0tSPGEP0=
|
go.etcd.io/etcd/client/v3 v3.6.1 h1:KelkcizJGsskUXlsxjVrSmINvMMga0VWwFF0tSPGEP0=
|
||||||
go.etcd.io/etcd/client/v3 v3.6.1/go.mod h1:fCbPUdjWNLfx1A6ATo9syUmFVxqHH9bCnPLBZmnLmMY=
|
go.etcd.io/etcd/client/v3 v3.6.1/go.mod h1:fCbPUdjWNLfx1A6ATo9syUmFVxqHH9bCnPLBZmnLmMY=
|
||||||
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
|
||||||
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package ws_handler
|
|||||||
import (
|
import (
|
||||||
"common/log"
|
"common/log"
|
||||||
"common/net/socket"
|
"common/net/socket"
|
||||||
|
"common/utils"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -14,29 +16,90 @@ type Client struct {
|
|||||||
sync.WaitGroup
|
sync.WaitGroup
|
||||||
conn socket.ISocketConn // Socket
|
conn socket.ISocketConn // Socket
|
||||||
mailChan chan Event // 邮箱队列
|
mailChan chan Event // 邮箱队列
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger // 日志
|
||||||
ctx context.Context
|
ctx context.Context // 上下文
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc // 取消上下文
|
||||||
heartBeat time.Time
|
heartBeat time.Time // 最后一次心跳
|
||||||
|
|
||||||
UID int32
|
UID int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(uid int32, conn socket.ISocketConn) *Client {
|
func NewClient(uid int32, conn socket.ISocketConn) *Client {
|
||||||
client := &Client{}
|
client := &Client{
|
||||||
client.UID = uid
|
UID: uid,
|
||||||
client.conn = conn
|
conn: conn,
|
||||||
client.logger = log.GetLogger().Named("uid").With("uid", client.UID)
|
logger: log.GetLogger().Named(fmt.Sprintf("uid:%v", uid)),
|
||||||
client.logger.Errorf("错误日志 %v", 1)
|
heartBeat: time.Now(),
|
||||||
|
mailChan: make(chan Event, 1024),
|
||||||
client.heartBeat = time.Now()
|
}
|
||||||
client.mailChan = make(chan Event, 1024)
|
|
||||||
client.ctx, client.cancel = context.WithCancel(context.Background())
|
client.ctx, client.cancel = context.WithCancel(context.Background())
|
||||||
|
client.Add(1)
|
||||||
go client.Loop()
|
go client.Loop()
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseClient 关闭客户端
|
func (c *Client) Loop() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
c.logger.Errorf("Client Loop err: %v", err)
|
||||||
|
debug.PrintStack()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer c.onClose()
|
||||||
|
t := time.NewTicker(20 * time.Second)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
return
|
||||||
|
case evt, ok := <-c.mailChan:
|
||||||
|
if ok {
|
||||||
|
c.handle(evt)
|
||||||
|
}
|
||||||
|
case <-t.C:
|
||||||
|
_ = c.conn.Ping()
|
||||||
|
if time.Now().Sub(c.heartBeat) > 120*time.Second {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) OnEvent(event Event) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
c.logger.Warnf(fmt.Sprintf("send event chan error: %v", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case c.mailChan <- event:
|
||||||
|
default:
|
||||||
|
c.logger.Warnf("Client mailChan full")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) handle(event Event) {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *ClientEvent:
|
||||||
|
m, err := parseMsg(e.Msg)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Errorf("handle event json.Unmarshal err: %v", err)
|
||||||
|
c.cancel()
|
||||||
|
}
|
||||||
|
c.logger.Infof("收到客户端消息:%+v", *m)
|
||||||
|
switch m.Type {
|
||||||
|
case "init":
|
||||||
|
_ = c.conn.Write(wapMsg(&msg{
|
||||||
|
Type: "init",
|
||||||
|
Data: fmt.Sprintf("[%v,%v]", utils.RandInt(1, 100), utils.RandInt(1, 100)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
case *PongEvent:
|
||||||
|
c.heartBeat = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseClient 关闭客户端(同步,会等待onClose执行完成)
|
||||||
func (c *Client) CloseClient() {
|
func (c *Client) CloseClient() {
|
||||||
if c.cancel != nil {
|
if c.cancel != nil {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
@@ -56,34 +119,3 @@ func (c *Client) onClose() {
|
|||||||
UserMgr.Delete(c.UID)
|
UserMgr.Delete(c.UID)
|
||||||
c.Done()
|
c.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Loop() {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
c.logger.Errorf("Client Loop err: %v", err)
|
|
||||||
debug.PrintStack()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer c.onClose()
|
|
||||||
c.Add(1)
|
|
||||||
//心跳检测
|
|
||||||
hearBeatTicker := time.NewTicker(3000 * time.Millisecond)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-c.ctx.Done():
|
|
||||||
return
|
|
||||||
case _, _ = <-c.mailChan:
|
|
||||||
|
|
||||||
case <-hearBeatTicker.C:
|
|
||||||
// 心跳超时直接关掉连接
|
|
||||||
if c.checkHeartBeatTimeout() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) checkHeartBeatTimeout() bool {
|
|
||||||
sub := time.Now().Sub(c.heartBeat)
|
|
||||||
return sub > 60*time.Second
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
package ws_handler
|
package ws_handler
|
||||||
|
|
||||||
import (
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Event interface {
|
type Event interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientEvent 客户端发过来的Event
|
|
||||||
type ClientEvent struct {
|
type ClientEvent struct {
|
||||||
Event
|
Event
|
||||||
Msg proto.Message
|
Msg []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoveConnectionEvent struct {
|
type PongEvent struct {
|
||||||
Event
|
Event
|
||||||
}
|
}
|
||||||
|
|||||||
21
Server/Gateway/handler/ws_handler/temp.go
Normal file
21
Server/Gateway/handler/ws_handler/temp.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package ws_handler
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type msg struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMsg(data []byte) (*msg, error) {
|
||||||
|
m := &msg{}
|
||||||
|
if err := json.Unmarshal(data, m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wapMsg(m *msg) []byte {
|
||||||
|
data, _ := json.Marshal(m)
|
||||||
|
return data
|
||||||
|
}
|
||||||
@@ -1,27 +1,59 @@
|
|||||||
package ws_gateway
|
package ws_gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"common/log"
|
||||||
"common/net/socket"
|
"common/net/socket"
|
||||||
|
"fmt"
|
||||||
|
"gateway/handler/ws_handler"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GatewayWsServer struct {
|
type GatewayWsServer struct {
|
||||||
|
logger *zap.SugaredLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnOpen(_ socket.ISocketConn) ([]byte, socket.Action) {
|
func (g *GatewayWsServer) OnOpen(conn socket.ISocketConn) ([]byte, socket.Action) {
|
||||||
|
g.logger = log.GetLogger().Named(fmt.Sprintf("addr:%v", conn.RemoteAddr()))
|
||||||
return nil, socket.None
|
return nil, socket.None
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnHandShake(conn socket.ISocketConn) {
|
func (g *GatewayWsServer) OnHandShake(conn socket.ISocketConn) {
|
||||||
//query := conn.GetParam("query")
|
token, ok := conn.GetParam("token").(string)
|
||||||
|
if !ok || len(token) == 0 {
|
||||||
|
g.logger.Warnf("token is not string")
|
||||||
|
_ = conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, err := strconv.Atoi(token)
|
||||||
|
if err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
client := ws_handler.NewClient(int32(t), conn)
|
||||||
|
ws_handler.UserMgr.Add(int32(t), client)
|
||||||
|
conn.SetParam("client", client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnMessage(conn socket.ISocketConn, bytes []byte) socket.Action {
|
func (g *GatewayWsServer) OnMessage(conn socket.ISocketConn, bytes []byte) socket.Action {
|
||||||
|
client, ok := conn.GetParam("client").(*ws_handler.Client)
|
||||||
|
if !ok || client.UID == 0 {
|
||||||
|
return socket.Close
|
||||||
|
}
|
||||||
|
client.OnEvent(&ws_handler.ClientEvent{Msg: bytes})
|
||||||
return socket.None
|
return socket.None
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnClose(conn socket.ISocketConn, _ error) socket.Action {
|
func (g *GatewayWsServer) OnPong(conn socket.ISocketConn) {
|
||||||
return socket.None
|
client, ok := conn.GetParam("client").(*ws_handler.Client)
|
||||||
|
if !ok || client.UID == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.OnEvent(&ws_handler.PongEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GatewayWsServer) OnClose(_ socket.ISocketConn, _ error) socket.Action {
|
||||||
|
return socket.Close
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GatewayWsServer) OnTick() (time.Duration, socket.Action) {
|
func (g *GatewayWsServer) OnTick() (time.Duration, socket.Action) {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func Init(debug bool, maxSize, maxBackups, maxAge int, level string) {
|
|||||||
logger = logger.WithOptions(
|
logger = logger.WithOptions(
|
||||||
zap.AddCaller(),
|
zap.AddCaller(),
|
||||||
zap.AddCallerSkip(1),
|
zap.AddCallerSkip(1),
|
||||||
zap.AddStacktrace(zapcore.WarnLevel),
|
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SetLogger(logger.Sugar())
|
SetLogger(logger.Sugar())
|
||||||
|
|||||||
@@ -15,19 +15,33 @@ const (
|
|||||||
Shutdown
|
Shutdown
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OpCode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpContinuation OpCode = 0x0
|
||||||
|
OpText OpCode = 0x1
|
||||||
|
OpBinary OpCode = 0x2
|
||||||
|
OpClose OpCode = 0x8
|
||||||
|
OpPing OpCode = 0x9
|
||||||
|
OpPong OpCode = 0xa
|
||||||
|
)
|
||||||
|
|
||||||
// ISocketServer 由应用层实现
|
// ISocketServer 由应用层实现
|
||||||
type ISocketServer interface {
|
type ISocketServer interface {
|
||||||
OnOpen(ISocketConn) ([]byte, Action) // 开启连接
|
OnOpen(ISocketConn) ([]byte, Action) // 开启连接
|
||||||
OnHandShake(ISocketConn) // 开始握手
|
OnHandShake(ISocketConn) // 开始握手
|
||||||
OnMessage(ISocketConn, []byte) Action // 收到消息
|
OnMessage(ISocketConn, []byte) Action // 收到消息
|
||||||
OnClose(ISocketConn, error) Action // 关闭连接
|
OnPong(ISocketConn)
|
||||||
|
OnClose(ISocketConn, error) Action // 关闭连接
|
||||||
OnTick() (time.Duration, Action)
|
OnTick() (time.Duration, Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ISocketConn 由网络层实现
|
// ISocketConn 由网络层实现
|
||||||
type ISocketConn interface {
|
type ISocketConn interface {
|
||||||
GetParam(key string) string
|
GetParam(key string) interface{}
|
||||||
SetParam(key string, values string)
|
SetParam(key string, values interface{})
|
||||||
|
RemoteAddr() string
|
||||||
|
Ping() error // 需要隔一段时间调用一下,建议20s
|
||||||
Write(data []byte) error
|
Write(data []byte) error
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ func (s *WSServer) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
|
|||||||
curHeader: nil,
|
curHeader: nil,
|
||||||
cachedBuf: bytes.Buffer{},
|
cachedBuf: bytes.Buffer{},
|
||||||
},
|
},
|
||||||
param: make(map[string]string),
|
param: make(map[string]interface{}),
|
||||||
|
remoteAddr: c.RemoteAddr().String(),
|
||||||
}
|
}
|
||||||
c.SetContext(ws)
|
c.SetContext(ws)
|
||||||
s.unUpgradeConn.Store(c.RemoteAddr().String(), ws)
|
s.unUpgradeConn.Store(c.RemoteAddr().String(), ws)
|
||||||
@@ -78,10 +79,10 @@ func (s *WSServer) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
|
|||||||
// The parameter err is the last known connection error.
|
// The parameter err is the last known connection error.
|
||||||
func (s *WSServer) OnClose(c gnet.Conn, err error) (action gnet.Action) {
|
func (s *WSServer) OnClose(c gnet.Conn, err error) (action gnet.Action) {
|
||||||
s.unUpgradeConn.Delete(c.RemoteAddr().String())
|
s.unUpgradeConn.Delete(c.RemoteAddr().String())
|
||||||
tmp := c.Context()
|
ws, ok := c.Context().(*WSConn)
|
||||||
ws, ok := tmp.(*WSConn)
|
|
||||||
if ok {
|
if ok {
|
||||||
s.logger.Warnf("connection close")
|
ws.isClose = true
|
||||||
|
ws.logger.Warnf("connection close: %v --------------------------------------------------", err)
|
||||||
return gnet.Action(s.i.OnClose(ws, err))
|
return gnet.Action(s.i.OnClose(ws, err))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -91,18 +92,14 @@ func (s *WSServer) OnClose(c gnet.Conn, err error) (action gnet.Action) {
|
|||||||
func (s *WSServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
func (s *WSServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
||||||
tmp := c.Context()
|
tmp := c.Context()
|
||||||
if tmp == nil {
|
if tmp == nil {
|
||||||
if s.logger != nil {
|
s.logger.Errorf("OnTraffic context nil: %v", c)
|
||||||
s.logger.Errorf("OnTraffic context nil: %v", c)
|
|
||||||
}
|
|
||||||
action = gnet.Close
|
action = gnet.Close
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ws, ok := tmp.(*WSConn)
|
ws, ok := tmp.(*WSConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
if s.logger != nil {
|
ws.logger.Errorf("OnTraffic convert ws error: %v", tmp)
|
||||||
s.logger.Errorf("OnTraffic convert ws error: %v", tmp)
|
|
||||||
}
|
|
||||||
action = gnet.Close
|
action = gnet.Close
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -121,9 +118,7 @@ func (s *WSServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
|||||||
if data != nil {
|
if data != nil {
|
||||||
err := ws.Conn.AsyncWrite(data, nil)
|
err := ws.Conn.AsyncWrite(data, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ws.logger != nil {
|
ws.logger.Errorf("update ws write upgrade protocol error", err)
|
||||||
ws.logger.Errorf("update ws write upgrade protocol error", err)
|
|
||||||
}
|
|
||||||
action = gnet.Close
|
action = gnet.Close
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,14 +126,22 @@ func (s *WSServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
|||||||
} else {
|
} else {
|
||||||
msg, err := ws.readWsMessages()
|
msg, err := ws.readWsMessages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ws.logger != nil {
|
ws.logger.Errorf("read ws messages errors", err)
|
||||||
ws.logger.Errorf("read ws messages errors", err)
|
|
||||||
}
|
|
||||||
return gnet.Close
|
return gnet.Close
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
for _, m := range msg {
|
for _, m := range msg {
|
||||||
|
if socket.OpCode(m.OpCode) == socket.OpPong {
|
||||||
|
s.i.OnPong(ws)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if socket.OpCode(m.OpCode) == socket.OpClose {
|
||||||
|
return gnet.Close
|
||||||
|
}
|
||||||
|
if socket.OpCode(m.OpCode) == socket.OpPing {
|
||||||
|
continue
|
||||||
|
}
|
||||||
a := s.i.OnMessage(ws, m.Payload)
|
a := s.i.OnMessage(ws, m.Payload)
|
||||||
if gnet.Action(a) != gnet.None {
|
if gnet.Action(a) != gnet.None {
|
||||||
action = gnet.Action(a)
|
action = gnet.Action(a)
|
||||||
@@ -182,10 +185,7 @@ func (s *WSServer) OnTick() (delay time.Duration, action gnet.Action) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := v.Close(); err != nil {
|
if err := v.Close(); err != nil {
|
||||||
if s.logger != nil {
|
v.logger.Errorf("upgrade ws time out close socket error: %v", err)
|
||||||
s.logger.Errorf("upgrade ws time out close socket error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d, a := s.i.OnTick()
|
d, a := s.i.OnTick()
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ import (
|
|||||||
// WSConn 实现ISocketConn接口
|
// WSConn 实现ISocketConn接口
|
||||||
type WSConn struct {
|
type WSConn struct {
|
||||||
gnet.Conn
|
gnet.Conn
|
||||||
buf bytes.Buffer
|
buf bytes.Buffer
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
isUpgrade bool
|
isUpgrade bool
|
||||||
isClose bool
|
isClose bool
|
||||||
param map[string]string
|
param map[string]interface{}
|
||||||
openTime int64
|
openTime int64
|
||||||
|
remoteAddr string
|
||||||
wsMessageBuf
|
wsMessageBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,19 +170,24 @@ func (w *WSConn) OnRequest(u []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WSConn) GetParam(key string) string {
|
func (w *WSConn) GetParam(key string) interface{} {
|
||||||
return w.param[key]
|
return w.param[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WSConn) SetParam(key string, values string) {
|
func (w *WSConn) SetParam(key string, values interface{}) {
|
||||||
w.param[key] = values
|
w.param[key] = values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WSConn) RemoteAddr() string {
|
||||||
|
return w.remoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WSConn) Write(data []byte) error {
|
func (w *WSConn) Write(data []byte) error {
|
||||||
if w.isClose {
|
return w.write(data, ws.OpText)
|
||||||
return errors.New("connection has close")
|
}
|
||||||
}
|
|
||||||
return w.write(data, ws.OpBinary)
|
func (w *WSConn) Ping() (err error) {
|
||||||
|
return w.write(make([]byte, 0), ws.OpPing)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WSConn) Close() (err error) {
|
func (w *WSConn) Close() (err error) {
|
||||||
@@ -192,74 +198,12 @@ func (w *WSConn) Close() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *WSConn) write(data []byte, opCode ws.OpCode) error {
|
func (w *WSConn) write(data []byte, opCode ws.OpCode) error {
|
||||||
|
if w.isClose {
|
||||||
|
return errors.New("connection has close")
|
||||||
|
}
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
err := wsutil.WriteServerMessage(&buf, opCode, data)
|
if err := wsutil.WriteServerMessage(&buf, opCode, data); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return w.Conn.AsyncWrite(buf.Bytes(), nil)
|
return w.Conn.AsyncWrite(buf.Bytes(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (w *WSConn) Pong(data []byte) (err error) {
|
|
||||||
// buf := bytes.Buffer{}
|
|
||||||
// if data == nil {
|
|
||||||
// err = wsutil.WriteServerMessage(&buf, ws.OpPong, emptyPayload)
|
|
||||||
// } else {
|
|
||||||
// err = wsutil.WriteServerMessage(&buf, ws.OpPong, data)
|
|
||||||
// }
|
|
||||||
// if w.isClose {
|
|
||||||
// return errors.New("connection has close")
|
|
||||||
// }
|
|
||||||
// if err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// return w.Conn.AsyncWrite(buf.Bytes(), nil)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//func (w *WSConn) Ping(data []byte) (err error) {
|
|
||||||
// buf := bytes.Buffer{}
|
|
||||||
// if data == nil {
|
|
||||||
// err = wsutil.WriteServerMessage(&buf, ws.OpPing, emptyPayload)
|
|
||||||
// } else {
|
|
||||||
// err = wsutil.WriteServerMessage(&buf, ws.OpPing, data)
|
|
||||||
// }
|
|
||||||
// if w.isClose {
|
|
||||||
// return errors.New("connection has close")
|
|
||||||
// }
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return w.Conn.AsyncWrite(buf.Bytes(), nil)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//func (w *WSConn) GetProperty(key string) (interface{}, error) {
|
|
||||||
// if w.context[key] == nil {
|
|
||||||
// return nil, errors.New("not found this key")
|
|
||||||
// }
|
|
||||||
// return w.context[key], nil
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (w *WSConn) SetProperty(key string, ctx interface{}) {
|
|
||||||
// w.context[key] = ctx
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (w *WSConn) RemoveProperty(key string) {
|
|
||||||
// delete(w.context, key)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (w *WSConn) GetConnID() uint64 {
|
|
||||||
// return w.connID
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (w *WSConn) Clear() {
|
|
||||||
// w.context = make(map[string]interface{})
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (w *WSConn) GetUrlParam() map[string][]string {
|
|
||||||
// return w.urlParma
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (w *WSConn) SetUrlParam(key string, values []string) {
|
|
||||||
// w.urlParma[key] = values
|
|
||||||
//}
|
|
||||||
|
|||||||
22
Server/common/proto/cs/define.proto
Normal file
22
Server/common/proto/cs/define.proto
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option go_package = "common/proto/gen/cs";
|
||||||
|
import "common.proto";
|
||||||
|
|
||||||
|
enum MessageID {
|
||||||
|
MESSAGE_ID_INVALID = 0;
|
||||||
|
|
||||||
|
// 移动指令
|
||||||
|
MESSAGE_TYPE_PLAYER_MOVE = 3;
|
||||||
|
|
||||||
|
// 聊天消息
|
||||||
|
MESSAGE_TYPE_CHAT_MESSAGE = 4;
|
||||||
|
|
||||||
|
// 心跳包
|
||||||
|
MESSAGE_TYPE_HEARTBEAT = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Message {
|
||||||
|
MessageID id = 1;
|
||||||
|
bytes payload = 2;
|
||||||
|
}
|
||||||
13
Server/common/utils/number.go
Normal file
13
Server/common/utils/number.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandInt 生成 [min, max] 范围内的随机整数
|
||||||
|
func RandInt(min, max int) int {
|
||||||
|
if min > max {
|
||||||
|
min, max = max, min
|
||||||
|
}
|
||||||
|
return rand.Intn(max-min+1) + min
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user