说明
该demo来源于B站教程视频【8小时转职Golang工程师(如果你想低成本学习Go语言)】 https://www.bilibili.com/video/BV1gf4y1r79E/?p=52&share_source=copy_web&vd_source=727d145bfa2bc1ec2bdd67cf624091fc
我跟随教程视频写的一个简单的demo, 用于巩固基础
其中根据自身习惯修改了部分代码
源码仓库: https://github.com/hidewnd/imDemo.git
其中可能包含一些错误逻辑和,欢迎指正
window环境 客户端构建:go build -o client.exe .\client.go
服务端构建:go build - go build -o server.exe .\main.go .\server.go .\User.go
定义服务端
Server具体实现
结构体
type Server struct {
//服务端的IP地址
Ip string
//服务端的IP端口
Port int
// 在线用户
OnlineUserMap map[string]*User
// 在线用户map的同步锁
mapLock sync.RWMutex
// 消息广播channel
Message chan string
}
创建Server接口
func NewServer(ip string, port int) *Server {
server := &Server{
Ip: ip,
Port: port,
OnlineUserMap: make(map[string]*User),
Message: make(chan string),
}
return server
}
监听用户上线
// BroadCast 服务端发送广播消息
func (s *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
s.Message <- sendMsg
}
// ListenMessage 在线用户监听器
func (s *Server) ListenMessage() {
for {
msg := <-s.Message
s.mapLock.Lock()
for _, cli := range s.OnlineUserMap {
cli.C <- msg
}
s.mapLock.Unlock()
}
}
服务端对用户消息的处理器
// Handler handler处理
func (s *Server) Handler(conn net.Conn) {
fmt.Print("链接建立成功")
// 加入在线用户集
user := NewUser(conn, s)
user.Online()
fmt.Println("[" + user.Addr + "]" + user.Name + ": online...")
isLive := make(chan bool)
// 广播用户发送信息
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 {
user.Offline()
fmt.Println("[" + user.Addr + "]" + user.Name + ": offline...")
return
}
if err != nil && err != io.EOF {
fmt.Println("conn Read err:", err)
return
}
fmt.Println("[" + user.Addr + "]" + user.Name + ":" + string(buf))
// 提取用户信息 去除 '\n' msg := string(buf[:n-1])
user.DoMessage(msg)
}
}()
// 超时强制退出监听检测处理
for {
select {
case <-isLive:
case <-time.After(time.Second * 60):
user.DoMessage("You have been forced offline\n")
fmt.Println("[" + user.Addr + "]" + user.Name + ": have been forced offline")
close(user.C)
err := conn.Close()
if err != nil {
return
}
}
}
}
服务端启动器
func (s *Server) Start() {
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
if err != nil {
fmt.Println("net.listener err:", err)
return
}
defer func(listener net.Listener) {
err := listener.Close()
if err != nil {
fmt.Println("net.listener err:", err)
}
}(listener)
// 启动在线用户监听
go s.ListenMessage()
fmt.Println("start success....")
for {
// accept
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener Accept err:", err)
continue
}
// handler
go s.Handler(conn)
}
}
主函数启动
package main
func main() {
server := NewServer("127.0.0.1", 8888)
server.Start()
}
完整代码
server.go
package main
import (
"fmt"
"io" "net" "sync" "time")
type Server struct {
Ip string
Port int
// 在线用户
OnlineUserMap map[string]*User
mapLock sync.RWMutex
// 消息广播channel
Message chan string
}
// NewServer 创建Server接口
func NewServer(ip string, port int) *Server {
server := &Server{
Ip: ip,
Port: port,
OnlineUserMap: make(map[string]*User),
Message: make(chan string),
}
return server
}
func (s *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
s.Message <- sendMsg
}
func (s *Server) ListenMessage() {
for {
msg := <-s.Message
s.mapLock.Lock()
for _, cli := range s.OnlineUserMap {
cli.C <- msg
}
s.mapLock.Unlock()
}
}
// Handler handler处理
func (s *Server) Handler(conn net.Conn) {
fmt.Print("链接建立成功")
// 加入在线用户集
user := NewUser(conn, s)
user.Online()
fmt.Println("[" + user.Addr + "]" + user.Name + ": online...")
isLive := make(chan bool)
// 广播用户发送信息
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 {
user.Offline()
fmt.Println("[" + user.Addr + "]" + user.Name + ": offline...")
return
}
if err != nil && err != io.EOF {
fmt.Println("conn Read err:", err)
return
}
fmt.Println("[" + user.Addr + "]" + user.Name + ":" + string(buf))
// 提取用户信息 去除 '\n' msg := string(buf[:n-1])
user.DoMessage(msg)
}
}()
// 超时强制退出监听检测处理
for {
select {
case <-isLive:
case <-time.After(time.Second * 60):
user.DoMessage("You have been forced offline\n")
fmt.Println("[" + user.Addr + "]" + user.Name + ": have been forced offline")
close(user.C)
err := conn.Close()
if err != nil {
return
}
}
}
}
// Start 启动服务器接口
func (s *Server) Start() {
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
if err != nil {
fmt.Println("net.listener err:", err)
return
}
defer func(listener net.Listener) {
err := listener.Close()
if err != nil {
fmt.Println("net.listener err:", err)
}
}(listener)
// 启动在线用户监听
go s.ListenMessage()
fmt.Println("start success....")
for {
// accept
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener Accept err:", err)
continue
}
// handler
go s.Handler(conn)
}
}
User具体实现
用户结构体
type User struct {
// 用户名
Name string
// 用户IP地址
Addr string
// chan
C chan string
// 链接
conn net.Conn
// 所属的服务
server *Server
}
创建一个User
func NewUser(c net.Conn, server *Server) *User {
userAddr := c.RemoteAddr().String()
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: c,
server: server,
}
go user.ListenMessage()
return user
}
监听用户状态
func (u *User) ListenMessage() {
for {
msg := <-u.C
_, err := u.conn.Write([]byte(msg + "\n"))
if err != nil {
fmt.Println("write err:", err)
return
}
}
}
// SendMsg 给客户端发送消息
func (u *User) SendMsg(msg string) {
_, err := u.conn.Write([]byte(msg))
if err != nil {
fmt.Println("send Msg err:", err)
}
}
具体实现用户的上下线
// Online 广播用户上线通知
func (u *User) Online() {
u.server.mapLock.Lock()
u.server.OnlineUserMap[u.Name] = u
u.server.mapLock.Unlock()
u.server.BroadCast(u, "online...")
}
// Offline 广播用户下线通知
func (u *User) Offline() {
u.server.mapLock.Lock()
delete(u.server.OnlineUserMap, u.Name)
u.server.mapLock.Unlock()
u.server.BroadCast(u, "offline...")
}
解析用户的消息
func (u *User) DoMessage(message string) {
if len(message) > 0 {
msg := strings.Split(message, "|")
if len(msg) > 1 {
switch msg[0] {
case "rename":
newName := msg[1]
_, ok := u.server.OnlineUserMap[newName]
if ok {
u.SendMsg("this name is already used\n")
return
}
u.server.mapLock.Lock()
u.server.OnlineUserMap[newName] = u
delete(u.server.OnlineUserMap, u.Name)
u.server.mapLock.Unlock()
u.Name = newName
u.SendMsg("update newName success\n")
break
case "to":
// to|用户名|消息
if len(msg) < 3 {
u.server.BroadCast(u, "Command format error\n")
return
}
remoteUer, ok := u.server.OnlineUserMap[msg[1]]
if !ok {
u.server.BroadCast(u, msg[1]+" not found\n")
return
}
remoteUer.SendMsg(msg[2])
}
} else {
switch message {
case "who":
u.server.mapLock.Lock()
for _, user := range u.server.OnlineUserMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":online\n"
u.SendMsg(onlineMsg)
}
u.server.mapLock.Unlock()
break
default:
u.server.BroadCast(u, message)
}
}
}
}
完整代码
package main
import (
"fmt"
"net" "strings")
type User struct {
Name string
Addr string
C chan string
conn net.Conn
server *Server
}
func NewUser(c net.Conn, server *Server) *User {
userAddr := c.RemoteAddr().String()
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: c,
server: server,
}
go user.ListenMessage()
return user
}
// Online 广播用户上线通知
func (u *User) Online() {
u.server.mapLock.Lock()
u.server.OnlineUserMap[u.Name] = u
u.server.mapLock.Unlock()
u.server.BroadCast(u, "online...")
}
// Offline 广播用户下线通知
func (u *User) Offline() {
u.server.mapLock.Lock()
delete(u.server.OnlineUserMap, u.Name)
u.server.mapLock.Unlock()
u.server.BroadCast(u, "offline...")
}
func (u *User) DoMessage(message string) {
if len(message) > 0 {
msg := strings.Split(message, "|")
if len(msg) > 1 {
switch msg[0] {
case "rename":
newName := msg[1]
_, ok := u.server.OnlineUserMap[newName]
if ok {
u.SendMsg("this name is already used\n")
return
}
u.server.mapLock.Lock()
u.server.OnlineUserMap[newName] = u
delete(u.server.OnlineUserMap, u.Name)
u.server.mapLock.Unlock()
u.Name = newName
u.SendMsg("update newName success\n")
break
case "to":
// to|用户名|消息
if len(msg) < 3 {
u.server.BroadCast(u, "Command format error\n")
return
}
remoteUer, ok := u.server.OnlineUserMap[msg[1]]
if !ok {
u.server.BroadCast(u, msg[1]+" not found\n")
return
}
remoteUer.SendMsg(msg[2])
}
} else {
switch message {
case "who":
u.server.mapLock.Lock()
for _, user := range u.server.OnlineUserMap {
onlineMsg := "[" + user.Addr + "]" + user.Name + ":online\n"
u.SendMsg(onlineMsg)
}
u.server.mapLock.Unlock()
break
default:
u.server.BroadCast(u, message)
}
}
}
}
func (u *User) ListenMessage() {
for {
msg := <-u.C
_, err := u.conn.Write([]byte(msg + "\n"))
if err != nil {
fmt.Println("write err:", err)
return
}
}
}
// SendMsg 给客户端发送消息
func (u *User) SendMsg(msg string) {
_, err := u.conn.Write([]byte(msg))
if err != nil {
fmt.Println("send Msg err:", err)
}
}
定义客户端
初始化客户端
客户端结构体
type Client struct {
// 服务端ip
ServerIp string
// 服务端端口
ServerPort int
// 客户端名
Name string
// 客户端管道
conn net.Conn
// 客户端模式
flag int
}
// NewClient 创建一个监听
func NewClient(serverIp string, serverPort int) *Client {
client := &Client{
ServerIp: serverIp,
ServerPort: serverPort,
flag: 999,
}
// 建立tcp长链接
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", client.ServerIp, client.ServerPort))
if err != nil {
fmt.Println("dial err:", err)
return nil
}
client.conn = conn
return client
}
通过参数命令来定义并启动客户端
func init() {
// 初始化配置,通过 -ip设置IP地址
flag.StringVar(&serverIp, "i", "127.0.0.1", "设置服务器的IP地址(默认127.0.0.1)")
// 初始化配置,通过 -p设置IP端口
flag.IntVar(&serverPort, "p", 8888, "设置服务器的端口(默认8888)")
}
// DealResponse 监听服务端返回的消息
func (client *Client) DealResponse() {
_, err := io.Copy(os.Stdout, client.conn)
if err != nil {
return
}
}
// 具体业务的实现
func (client *Client) Run() {
}
func main() {
// 命令行解析
flag.Parse()
client := NewClient(serverIp, serverPort)
if client == nil {
fmt.Println(">>> 链接服务器失败")
return
}
go client.DealResponse()
fmt.Println(">>> 链接服务器成功")
client.Run()
}
客户端封装功能
封装修改用户名
// 包装更新用户名请求
func (client *Client) updateUserName() bool {
fmt.Println(">>> 请输入用户名")
_, err := fmt.Scanln(&client.Name)
if err != nil {
return false
}
sendMsg := "rename|" + client.Name + "\n"
_, err = client.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println(">>> conn write err:", err)
return false
}
return true
}
封装查询在线用户
func (client *Client) listUser() bool {
sendMsg := "who\n"
_, err := client.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println(">>> conn write err:", err)
return false
}
return true
}
封装广播聊天
// PublicChat 公聊模式
func (client *Client) publicChat() {
var chatMsg string
fmt.Println(">>> 请输入发送内容,输入exit退出")
_, err := fmt.Scanln(&chatMsg)
if err != nil {
return
}
for chatMsg != "exit" {
if len(chatMsg) != 0 {
sendMsg := chatMsg + "\n"
_, err := client.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println(">>> conn write err:", err)
break
}
}
// 继续监听下一个发送信息,直到exit
chatMsg = ""
fmt.Println(">>> 请输入发送内容,输入exit退出")
_, err := fmt.Scanln(&chatMsg)
if err != nil {
return
}
}
}
封装私聊
// PrivateChat 私聊模式
func (client *Client) privateChat() {
var remote, chatMsg string
client.listUser()
for remote != "exit" {
fmt.Println(">>> 请输入发送对象,输入exit退出")
_, err := fmt.Scanln(&remote)
if err != nil {
return
}
for chatMsg != "exit" {
if len(chatMsg) != 0 {
// 发送消息到服务端
sendMsg := "to|" + remote + "|" + chatMsg + "\n"
_, err := client.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println(">>> conn write err:", err)
break
}
}
// 继续监听下一个发送信息,直到exit
chatMsg = ""
fmt.Println(">>> 请输入发送内容,输入exit退出")
_, err := fmt.Scanln(&chatMsg)
if err != nil {
return
}
}
// 内循环退出,继续监听输入内容选择用户,知道exit
remote = ""
fmt.Println(">>> 请输入发送对象,输入exit退出")
_, err = fmt.Scanln(&remote)
if err != nil {
return
}
}
}
封装客户端菜单命令
// Menu 命令菜单
func (client *Client) Menu() bool {
var code int
fmt.Println("1: 公聊模式")
fmt.Println("2: 私聊模式")
fmt.Println("3: 更新用户名")
fmt.Println("0: 退出")
_, err := fmt.Scanln(&code)
if err != nil {
return false
}
if code >= 0 && code <= 3 {
client.flag = code
return true
}
return false
}
调整业务函数
func (client *Client) Run() {
for client.flag != 0 {
// 过滤非法的菜单code
for !client.Menu() {
}
// 根据菜单code进行功能调度
switch client.flag {
case 1:
client.publicChat()
break
case 2:
client.privateChat()
break
case 3:
client.updateUserName()
break
}
}
}
完整代码
package main
import (
"flag"
"fmt" "io" "net" "os")
var serverIp string
var serverPort int
type Client struct {
ServerIp string
ServerPort int
Name string
conn net.Conn
flag int
}
// NewClient 创建一个监听
func NewClient(serverIp string, serverPort int) *Client {
client := &Client{
ServerIp: serverIp,
ServerPort: serverPort,
flag: 999,
}
// 建立tcp长链接
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", client.ServerIp, client.ServerPort))
if err != nil {
fmt.Println("dial err:", err)
return nil
}
client.conn = conn
return client
}
// Menu 命令菜单
func (client *Client) Menu() bool {
var code int
fmt.Println("1: 公聊模式")
fmt.Println("2: 私聊模式")
fmt.Println("3: 更新用户名")
fmt.Println("0: 退出")
_, err := fmt.Scanln(&code)
if err != nil {
return false
}
if code >= 0 && code <= 3 {
client.flag = code
return true
}
return false
}
// 包装更新用户名请求
func (client *Client) updateUserName() bool {
fmt.Println(">>> 请输入用户名")
_, err := fmt.Scanln(&client.Name)
if err != nil {
return false
}
sendMsg := "rename|" + client.Name + "\n"
_, err = client.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println(">>> conn write err:", err)
return false
}
return true
}
// 查询用户信息
func (client *Client) listUser() bool {
sendMsg := "who\n"
_, err := client.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println(">>> conn write err:", err)
return false
}
return true
}
// PrivateChat 私聊模式
func (client *Client) privateChat() {
var remote, chatMsg string
client.listUser()
for remote != "exit" {
fmt.Println(">>> 请输入发送对象,输入exit退出")
_, err := fmt.Scanln(&remote)
if err != nil {
return
}
for chatMsg != "exit" {
if len(chatMsg) != 0 {
// 发送消息到服务端
sendMsg := "to|" + remote + "|" + chatMsg + "\n"
_, err := client.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println(">>> conn write err:", err)
break
}
}
// 继续监听下一个发送信息,直到exit
chatMsg = ""
fmt.Println(">>> 请输入发送内容,输入exit退出")
_, err := fmt.Scanln(&chatMsg)
if err != nil {
return
}
}
// 内循环退出,继续监听输入内容选择用户,知道exit
remote = ""
fmt.Println(">>> 请输入发送对象,输入exit退出")
_, err = fmt.Scanln(&remote)
if err != nil {
return
}
}
}
// PublicChat 公聊模式
func (client *Client) publicChat() {
var chatMsg string
fmt.Println(">>> 请输入发送内容,输入exit退出")
_, err := fmt.Scanln(&chatMsg)
if err != nil {
return
}
for chatMsg != "exit" {
if len(chatMsg) != 0 {
sendMsg := chatMsg + "\n"
_, err := client.conn.Write([]byte(sendMsg))
if err != nil {
fmt.Println(">>> conn write err:", err)
break
}
}
// 继续监听下一个发送信息,直到exit
chatMsg = ""
fmt.Println(">>> 请输入发送内容,输入exit退出")
_, err := fmt.Scanln(&chatMsg)
if err != nil {
return
}
}
}
func (client *Client) Run() {
for client.flag != 0 {
// 过滤非法的菜单code
for !client.Menu() {
}
// 根据菜单code进行功能调度
switch client.flag {
case 1:
client.publicChat()
break
case 2:
client.privateChat()
break
case 3:
client.updateUserName()
break
}
}
}
func init() {
// 初始化配置,通过 -ip设置IP地址
flag.StringVar(&serverIp, "i", "127.0.0.1", "设置服务器的IP地址(默认127.0.0.1)")
// 初始化配置,通过 -p设置IP端口
flag.IntVar(&serverPort, "p", 8888, "设置服务器的端口(默认8888)")
}
func (client *Client) DealResponse() {
_, err := io.Copy(os.Stdout, client.conn)
if err != nil {
return
}
}
func main() {
// 命令行解析
flag.Parse()
client := NewClient(serverIp, serverPort)
if client == nil {
fmt.Println(">>> 链接服务器失败")
return
}
go client.DealResponse()
fmt.Println(">>> 链接服务器成功")
client.Run()
}