9 changed files with 437 additions and 8 deletions
@ -0,0 +1,16 @@
|
||||
# alpine need CGO_ENABLED=0, sqlite need CGO_ENABLED=1 ... |
||||
# FROM alpine:3 |
||||
FROM ubuntu:22.10 |
||||
ARG APP |
||||
ENV LANG C.UTF-8 |
||||
|
||||
WORKDIR /opt |
||||
|
||||
COPY target/${APP}/${APP} /opt/go-app |
||||
COPY target/${APP}/config/ /opt/config/ |
||||
|
||||
RUN chmod +x /opt/go-app \ |
||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ |
||||
&& echo 'Asia/Shanghai' > /etc/timezone |
||||
|
||||
CMD ["./go-app", "-conf=config/config.toml"] |
||||
@ -0,0 +1,334 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/base64" |
||||
"fmt" |
||||
"github.com/bytedance/sonic" |
||||
"github.com/gorilla/websocket" |
||||
"google.golang.org/protobuf/proto" |
||||
"math/rand" |
||||
"net" |
||||
"runtime" |
||||
"sonet/api/gen/auth" |
||||
"sonet/api/gen/chat" |
||||
"sonet/pkg/config" |
||||
"sonet/pkg/protocol" |
||||
"sonet/pkg/utils/logger" |
||||
"sonet/pkg/utils/security" |
||||
"sonet/pkg/utils/shutdown" |
||||
"strconv" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
mockUsers = 100 |
||||
eachUserSend = 100000 |
||||
mockNetUsers []*NetUser |
||||
sendCounter int64 = 0 |
||||
receiverCounter int64 = 0 |
||||
wsUrls = []string{"ws://192.168.110.36:7001", "ws://192.168.110.36:7003"} |
||||
seqId int32 |
||||
callbacks map[int32]func(res any) |
||||
callbackMutex *sync.Mutex |
||||
useMsSum int64 |
||||
useMsAvg int64 |
||||
) |
||||
|
||||
func init() { |
||||
config.InitLogger() |
||||
|
||||
callbacks = make(map[int32]func(res any), 128) |
||||
callbackMutex = &sync.Mutex{} |
||||
} |
||||
|
||||
func records(ctx context.Context) { |
||||
ticker := time.NewTicker(time.Second) |
||||
defer ticker.Stop() |
||||
|
||||
var prevSendCounter, prevReceiverCounter int64 |
||||
|
||||
for { |
||||
select { |
||||
case <-ctx.Done(): |
||||
return |
||||
case <-ticker.C: |
||||
// fmt.Printf("record: %d/%d, total: %d/%d \n", receiverCounter-prevReceiverCounter, sendCounter-prevSendCounter, receiverCounter, sendCounter)
|
||||
logger.Infof("record: %d/%d, total: %d/%d, avg %dms\n", receiverCounter-prevReceiverCounter, sendCounter-prevSendCounter, receiverCounter, sendCounter, useMsAvg) |
||||
prevReceiverCounter = receiverCounter |
||||
prevSendCounter = sendCounter |
||||
} |
||||
} |
||||
} |
||||
|
||||
type NetUser struct { |
||||
Uid string |
||||
Conn *websocket.Conn |
||||
} |
||||
|
||||
func main() { |
||||
runtime.GOMAXPROCS(runtime.NumCPU()) |
||||
// go prof.StartPprof(":8888")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
go records(ctx) |
||||
|
||||
mockNetUsers = make([]*NetUser, mockUsers) |
||||
// initial uids
|
||||
for i := 0; i < mockUsers; i++ { |
||||
uid := strconv.Itoa(110000 + i) |
||||
conn, err := getConn() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
err = handleConn(ctx, uid, conn) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
mockNetUsers[i] = &NetUser{ |
||||
Uid: uid, |
||||
Conn: conn, |
||||
} |
||||
} |
||||
|
||||
for i := 0; i < mockUsers; i++ { |
||||
netUser := mockNetUsers[i] |
||||
go sendBatchChatMessage(ctx, netUser.Uid, netUser.Conn, eachUserSend) |
||||
} |
||||
|
||||
shutdown.Await() |
||||
cancel() |
||||
|
||||
logger.Infof("total receiver:%d, send:%d\n", receiverCounter, sendCounter) |
||||
} |
||||
|
||||
func sendBatchChatMessage(ctx context.Context, uid string, conn *websocket.Conn, count int) { |
||||
r := rand.New(rand.NewSource(time.Now().UnixMilli())) |
||||
for i := 0; i < count; i++ { |
||||
receiverUid := mockNetUsers[r.Intn(mockUsers)].Uid |
||||
args := &chat.ReqSend{ |
||||
Receiver: receiverUid, |
||||
Content: "hello", |
||||
} |
||||
channel, err := send(conn, chat.Chat_ServiceDesc.ServiceName, "send", args) |
||||
if err != nil { |
||||
fmt.Println("send failed:", err) |
||||
return |
||||
} |
||||
|
||||
res := <-channel // wait result
|
||||
err, failed := res.(error) |
||||
if failed { |
||||
fmt.Println("send failed:", err) |
||||
} else { |
||||
// fmt.Println("send ok:", res)
|
||||
} |
||||
} |
||||
} |
||||
|
||||
func getConn() (conn *websocket.Conn, err error) { |
||||
wsUrl := wsUrls[rand.Intn(len(wsUrls))] |
||||
conn, _, err = websocket.DefaultDialer.Dial(wsUrl+"/ws", nil) |
||||
if err != nil { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
func getToken(uid string) (token string, err error) { |
||||
// generate token
|
||||
subject := &auth.Subject{Uid: uid, Username: uid, Time: time.Now().UnixMilli()} |
||||
bytes, err := sonic.Marshal(subject) |
||||
if err != nil { |
||||
return |
||||
} |
||||
keyBytes, err := base64.StdEncoding.DecodeString("9Nz3Y6DES3msAFndz4QsJAECUIFkf+KKaRa+jRnNALk=") |
||||
if err != nil { |
||||
return |
||||
} |
||||
encode, err := security.EncryptAesCBC(bytes, keyBytes) |
||||
if err != nil { |
||||
return |
||||
} |
||||
token = base64.URLEncoding.EncodeToString(encode) |
||||
return |
||||
} |
||||
|
||||
// handleConn listen and auth verify
|
||||
func handleConn(ctx context.Context, uid string, conn *websocket.Conn) (err error) { |
||||
go listen(ctx, conn) |
||||
|
||||
// handshake
|
||||
token, err := getToken(uid) |
||||
if err != nil { |
||||
return |
||||
} |
||||
args := &auth.ReqVerify{Token: token} |
||||
|
||||
channel, err := send(conn, auth.Auth_ServiceDesc.ServiceName, "Verify", args) |
||||
if err != nil { |
||||
return |
||||
} |
||||
res := <-channel |
||||
// fmt.Printf("%v\n", res)
|
||||
if e, failed := res.(error); failed { |
||||
err = e |
||||
} |
||||
return |
||||
} |
||||
|
||||
func listen(ctx context.Context, conn *websocket.Conn) { |
||||
var e error |
||||
defer func() { |
||||
if e != nil { |
||||
fmt.Println("conn error: ", e) |
||||
} |
||||
}() |
||||
|
||||
go func() { |
||||
select { |
||||
case <-ctx.Done(): // finished close connection
|
||||
_ = conn.Close() |
||||
} |
||||
}() |
||||
|
||||
for { |
||||
t, message, err := conn.ReadMessage() |
||||
if err != nil { |
||||
if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" { |
||||
return |
||||
} |
||||
|
||||
e = err |
||||
_ = conn.Close() |
||||
break |
||||
} |
||||
if t != websocket.BinaryMessage { |
||||
e = fmt.Errorf("unknown message type: %d\n", t) |
||||
continue |
||||
} |
||||
|
||||
payload, err := protocol.DecodeSo(message) |
||||
if err != nil { |
||||
logger.Error("decode message error: ", err) |
||||
continue |
||||
} |
||||
header := payload.Header |
||||
|
||||
if header.Type == 4 { |
||||
callbackMutex.Lock() |
||||
callback, ok := callbacks[header.SeqId] |
||||
if ok { |
||||
delete(callbacks, header.SeqId) |
||||
} |
||||
callbackMutex.Unlock() |
||||
if !ok { |
||||
e = fmt.Errorf("callback %d not found", header.SeqId) |
||||
return |
||||
} |
||||
callback(err) |
||||
continue |
||||
} |
||||
|
||||
// notice message
|
||||
if header.Type == protocol.TypeNotice { |
||||
switch header.Target { |
||||
case "ChatMessage": |
||||
msg := &chat.ChatMessage{} |
||||
if err := proto.Unmarshal(payload.Body, msg); err != nil { |
||||
logger.Error("unmarshal proto message error: ", err) |
||||
return |
||||
} |
||||
//atomic.AddInt64(&receiverCounter, 1)
|
||||
// fmt.Printf("reciever: %v\n", msg)
|
||||
} |
||||
continue |
||||
} |
||||
|
||||
// rpc response
|
||||
var msg proto.Message |
||||
switch header.Svc { |
||||
case auth.Auth_ServiceDesc.ServiceName: |
||||
switch header.Target { |
||||
case "Subject": |
||||
// fmt.Println("res verify...")
|
||||
msg = &auth.Subject{} |
||||
} |
||||
case chat.Chat_ServiceDesc.ServiceName: |
||||
switch header.Target { |
||||
case "ResSend": |
||||
// fmt.Println("res send...")
|
||||
msg = &chat.ResSend{} |
||||
} |
||||
} |
||||
if msg == nil { |
||||
logger.Warning("unknown rpc response: ", len(payload.Body), header) |
||||
continue |
||||
} |
||||
err = proto.Unmarshal(payload.Body, msg) |
||||
if e != nil { |
||||
logger.Error("unmarshal rcp res body error: ", payload, err) |
||||
return |
||||
} |
||||
|
||||
callbackMutex.Lock() |
||||
callback, ok := callbacks[header.SeqId] |
||||
if ok { |
||||
delete(callbacks, header.SeqId) |
||||
} |
||||
callbackMutex.Unlock() |
||||
if !ok { |
||||
e = fmt.Errorf("callback %d not found", header.SeqId) |
||||
return |
||||
} |
||||
callback(msg) |
||||
} |
||||
} |
||||
|
||||
func send(conn *websocket.Conn, svc, method string, reqArgs proto.Message) (channel chan any, err error) { |
||||
seq := atomic.AddInt32(&seqId, 1) |
||||
header := &protocol.Header{ |
||||
Magic: protocol.Magic, |
||||
Type: 1, |
||||
UrlType: 1, |
||||
SerializeType: 1, |
||||
SeqId: seq, |
||||
Svc: svc, |
||||
Target: method, |
||||
} |
||||
payload := &protocol.Payload{Header: header} |
||||
payload.Body, err = proto.Marshal(reqArgs) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
bytes, err := protocol.EncodeSo(payload) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
begin := time.Now().UnixMilli() |
||||
// seq, await
|
||||
channel = make(chan any) |
||||
|
||||
// ready callback
|
||||
callbackMutex.Lock() |
||||
callbacks[seq] = func(res any) { |
||||
ms := time.Now().UnixMilli() - begin |
||||
msSum := atomic.AddInt64(&useMsSum, ms) |
||||
counter := atomic.AddInt64(&receiverCounter, 1) |
||||
atomic.StoreInt64(&useMsAvg, msSum/counter) |
||||
|
||||
channel <- res |
||||
} |
||||
callbackMutex.Unlock() |
||||
|
||||
// send message
|
||||
err = conn.WriteMessage(websocket.BinaryMessage, bytes) |
||||
if err != nil { |
||||
return |
||||
} |
||||
atomic.AddInt64(&sendCounter, 1) |
||||
return |
||||
} |
||||
@ -0,0 +1,14 @@
|
||||
package prof |
||||
|
||||
import ( |
||||
"log" |
||||
"net/http" |
||||
_ "net/http/pprof" |
||||
) |
||||
|
||||
func StartPprof(addr string) { |
||||
err := http.ListenAndServe(addr, nil) |
||||
if err != nil { |
||||
log.Fatal("http server start failed: ", err) |
||||
} |
||||
} |
||||
@ -0,0 +1,24 @@
|
||||
@echo off |
||||
@title build so app |
||||
|
||||
@REM build.bat gateway_ws 1.0.0 |
||||
|
||||
set APP=%1 |
||||
set DOCKER_IMAGE_VERSION=%2 |
||||
set GO111MODULE=on |
||||
set CGO_ENABLED=0 |
||||
set GOOS=linux |
||||
set GOARCH=amd64 |
||||
go build -o target/%APP%/%APP% cmd/%APP%/main.go |
||||
if exist cmd\%APP%\config.toml ( |
||||
if not exist target\%APP%\config ( |
||||
md target\%APP%\config |
||||
) |
||||
copy cmd\%APP%\config.toml target\%APP%\config\ |
||||
) |
||||
|
||||
if not "%DOCKER_IMAGE_VERSION%"=="" ( |
||||
docker build -t so-%APP%:%DOCKER_IMAGE_VERSION% --build-arg APP=%APP% . |
||||
) |
||||
|
||||
echo %APP% build finished |
||||
@ -0,0 +1,28 @@
|
||||
version: '3' |
||||
services: |
||||
svr-gateway-ws-1: |
||||
image: so_gateway_ws:1.0.0 |
||||
network_mode: host |
||||
environment: |
||||
- SO_APP.HTTPPORT=7001 |
||||
- SO_APP.ENDPOINTADDRESS=192.168.110.36:7001 |
||||
- SO_GRPC.ADDRESS=:7011 |
||||
svr-gateway-ws-2: |
||||
image: so_gateway_ws:1.0.0 |
||||
network_mode: host |
||||
environment: |
||||
- SO_APP.HTTPPORT=7003 |
||||
- SO_APP.ENDPOINTADDRESS=192.168.110.36:7003 |
||||
- SO_GRPC.ADDRESS=:7012 |
||||
|
||||
svr-chat: |
||||
image: so_chat:1.0.0 |
||||
network_mode: host |
||||
|
||||
svr-auth: |
||||
image: so_auth:1.0.0 |
||||
network_mode: host |
||||
|
||||
svr-gatewat-http: |
||||
image: so_gateway_http:1.0.0 |
||||
network_mode: host |
||||
Loading…
Reference in new issue