运行方式
修改配置
[Server]
监听端口
port = 8888
[Auth]
# 用户名和密码对
mike = 123456
leo = password
运行服务端
./server
客户端
./client -u leo -p password
效果
设计
使用grpc双向流收发消息,proto文件如下
syntax = "proto3";
package chat_server;
option go_package="./chat_server";
service ChatServer {
rpc Chat (stream ChatRequest) returns (stream ChatResponse) {}
}
message ChatRequest {
string msg = 1;
}
message ChatResponse {
string msg = 1;
string user = 2;
}
grpc header中传递用户名和密码用来验证
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
client := chat_server.NewChatServerClient(conn)
md := metadata.New(map[string]string{"user": user, "pwd": pwd})
stream, err := client.Chat(metadata.NewOutgoingContext(context.Background(), md))
客户端逻辑
// 创建Goroutine 接受服务端消息
go func() {
for {
in, err := stream.Recv()
//....
response <- in
}
}()
// 创建Goroutine 接受用户输入发送到服务端
go func() {
for {
stream.Send(&chat_server.ChatRequest{Msg: <-request})
}
}()
// 创建Goroutine 运行用户界面,接受消息到request channel
go func() {
t, err := tcell.New()
if err != nil {
panic(err)
}
// ...
}
服务端逻辑
func (s *Server) Chat(stream chat_server.ChatServer_ChatServer) error {
// 存放连接 map[用户名】连接
s.mu.Lock()
md, _ := metadata.FromIncomingContext(stream.Context())
user, pwd := md.Get("user")[0], md.Get("pwd")[0]
s.clients[user] = stream
s.mu.Unlock()
if v, ok := Users[user]; !(ok && v == pwd) {
return fmt.Errorf("login failed")
}
log.Printf("user 【%s】login\n", user)
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
log.Printf("received msg 【%s】from user 【%s】\n", in.Msg, user)
// 收到消息,依次投递给每个用户
for u, client := range s.clients {
log.Printf("send msg from user 【%s】to user【%s】\n", user, u)
err := client.Send(&chat_server.ChatResponse{Msg: in.Msg, User: user})
if err != nil {
log.Printf("send msg to user %s failed: %v", u, err)
log.Printf("delete user %s session\n", u)
s.mu.Lock()
delete(s.clients, u)
s.mu.Unlock()
}
}
}
}