即时通信Demo

V0.1 根底Server构建

实现新用户连贯胜利后,在server端揭示的性能

server.go

package mainimport (    "fmt"    "net")type Server struct {    Ip string    Port int}//创立一个server的接口func NewServer(ip string,port int) *Server {    server := &Server{        Ip: ip,        Port: port,    }    return server}//启动服务器的接口// 定义对象(类)的办法:   func (对象类型参数)办法名(参数列表)(返回值列表){ }// 如果想通过办法批改对象,那么倡议传递对象的地址 (构造体是值传递,通过构造体的指针批改构造体(地址传递))  (也能够通过返回值批改对象)//func (obj *MyInt) add() { }  // 对象调用时,会主动将对象的地址传给objfunc (this *Server) Start() {    //socket listen    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d",this.Ip,this.Port))    if err != nil {        fmt.Println("net.listen tcp err:", err)    }    //close listen socket    defer listener.Close()    for {        //accept        conn, err := listener.Accept()        if err != nil {            fmt.Println("listen accept err:", err)            continue        }        //do handler        go this.Handler(conn)        }}//链接胜利,执行业务func (this *Server) Handler(conn net.Conn) {    fmt.Println("Connection Success...")}

main.go

package mainfunc main()  {    server := NewServer("127.0.0.1", 8888)    server.Start()}

编译

go build -o server main.go server.go

开启server

./server

测试连贯

nc 127.0.0.1 8888

V0.2 用户上线及播送

实现新用户连贯胜利后,向整体在线用户播送的性能

server.go

package mainimport (    "fmt"    "net"    "sync")type Server struct {    Ip   string    Port int    //在线用户列表    OnlineMap map[string]*User    mapLock   sync.RWMutex    //音讯播送channel    Message chan string}//创立一个server的接口func NewServer(ip string, port int) *Server {    server := &Server{        Ip:        ip,        Port:      port,        OnlineMap: make(map[string]*User),        Message:   make(chan string),    }    return server}//坚挺Message播送,有音讯则发送给全副在线User (在start办法中加载)func (this *Server) ListenMessager() {    for {        msg := <-this.Message        this.mapLock.Lock()        for _, user := range this.OnlineMap {            user.C <- msg        }        this.mapLock.Unlock()    }}//音讯播送func (this *Server) BroadCast(user *User, msg string) {    sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg    this.Message <- sendMsg}//链接胜利,执行业务func (this *Server) Handler(conn net.Conn) {    user := NewUser(conn)    //用户上线,将用户退出OnlineMap中    this.mapLock.Lock()    this.OnlineMap[user.Name] = user    this.mapLock.Unlock()    //播送用户上线音讯    this.BroadCast(user, "上线了")    //阻塞以后Handler(避免以后及子goroutine死亡)    select {}}//启动服务器的接口func (this *Server) Start() {    //socket listen    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))    if err != nil {        fmt.Println("net.listen tcp err:", err)    }    //close listen socket    defer listener.Close()    //启动监听Message的goroutine    go this.ListenMessager()    for {        //accept        conn, err := listener.Accept()        if err != nil {            fmt.Println("listen accept err:", err)            continue        }        //do handler        go this.Handler(conn)    }}

user.go

package mainimport "net"type User struct {    Name string    Addr string    C    chan string    conn net.Conn}//创立用户func NewUser(conn net.Conn) *User {    userAddr := conn.RemoteAddr().String()    user := &User{        Name: userAddr,        Addr: userAddr,        C:    make(chan string),        conn: conn,    }    //启动监听以后user channel的goroutine    go user.ListenMessage()    return user}//监听以后User channel,有新音讯则发送到客户端func (this *User) ListenMessage() {    for {        msg := <-this.C        this.conn.Write([]byte(msg + "\n"))    }}

main.go

package mainfunc main()  {    server := NewServer("127.0.0.1", 8888)    server.Start()}

编译运行

go build -o server main.go server.go user.go

连贯测试

nc 127.0.0.1 8888

V0.3用户音讯播送

server.go

func (this *Server) Handler(conn net.Conn) {    user := NewUser(conn)    //用户上线,将用户退出OnlineMap中    this.mapLock.Lock()    this.OnlineMap[user.Name] = user    this.mapLock.Unlock()    //播送用户上线音讯    this.BroadCast(user, "上线了")    //接管用户音讯    go func() {        buf := make([]byte, 4096)        for {            //将conn中数据读取到buf中            n, err := conn.Read(buf)            if n == 0 {                this.BroadCast(user, "下线")                return            }            if err != nil && err != io.EOF {                fmt.Println("Conn Read err:", err)                return            }            //提取用户的音讯(去除\n)            msg := string(buf[:n-1])            //将失去的音讯进行播送            this.BroadCast(user, msg)        }    }()    //阻塞以后Handler(避免以后及子goroutine死亡)    select {}}

查看端口占用:

lsof -i:8888

V0.4用户业务封装

Server.go

package mainimport (    "fmt"    "io"    "net"    "sync")type Server struct {    Ip   string    Port int    //在线用户列表    OnlineMap map[string]*User    mapLock   sync.RWMutex    //音讯播送channel    Message chan string}//创立一个server的接口func NewServer(ip string, port int) *Server {    server := &Server{        Ip:        ip,        Port:      port,        OnlineMap: make(map[string]*User),        Message:   make(chan string),    }    return server}//坚挺Message播送,有音讯则发送给全副在线User (在start办法中加载)func (this *Server) ListenMessager() {    for {        msg := <-this.Message        this.mapLock.Lock()        for _, user := range this.OnlineMap {            user.C <- msg        }        this.mapLock.Unlock()    }}//音讯播送func (this *Server) BroadCast(user *User, msg string) {    sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg    this.Message <- sendMsg}//链接胜利,执行业务func (this *Server) Handler(conn net.Conn) {    user := NewUser(conn, this)    //用户上线,将用户退出OnlineMap中    user.Online()    //接管用户音讯    go func() {        buf := make([]byte, 4096)        for {            //将conn中数据读取到buf中            n, err := conn.Read(buf)            if n == 0 {                user.Offline()                return            }            if err != nil && err != io.EOF {                fmt.Println("Conn Read err:", err)                return            }            //提取用户的音讯(去除\n)            msg := string(buf[:n-1])            //将失去的音讯进行播送            user.DoMessage(msg)        }    }()    //阻塞以后Handler(避免以后及子goroutine死亡)    select {}}//启动服务器的接口func (this *Server) Start() {    //socket listen    listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))    if err != nil {        fmt.Println("net.listen tcp err:", err)    }    //close listen socket    defer listener.Close()    //启动监听Message的goroutine    go this.ListenMessager()    for {        //accept        conn, err := listener.Accept()        if err != nil {            fmt.Println("listen accept err:", err)            continue        }        //do handler        go this.Handler(conn)    }}

user.go

package mainimport "net"type User struct {    Name   string    Addr   string    C      chan string    conn   net.Conn    server *Server}//创立用户func NewUser(conn net.Conn, server *Server) *User {    userAddr := conn.RemoteAddr().String()    user := &User{        Name:   userAddr,        Addr:   userAddr,        C:      make(chan string),        conn:   conn,        server: server,    }    //启动监听以后user channel的goroutine    go user.ListenMessage()    return user}//监听以后User channel,有新音讯则发送到客户端func (this *User) ListenMessage() {    for {        msg := <-this.C        this.conn.Write([]byte(msg + "\n"))    }}//用户上线func (this *User) Online() {    this.server.mapLock.Lock()    this.server.OnlineMap[this.Name] = this    this.server.mapLock.Unlock()    this.server.BroadCast(this, "上线了")}//用户下线func (this *User) Offline() {    this.server.mapLock.Lock()    delete(this.server.OnlineMap, this.Name)    this.server.mapLock.Unlock()    this.server.BroadCast(this, "下线了")}//用户音讯func (this *User) DoMessage(msg string) {    this.server.BroadCast(this, msg)}

V0.5在线用户查问

user.go

//用户音讯func (this *User) DoMessage(msg string) {    if msg == "who" {        this.server.OnlineMap.Lock()        for _, user := range this.server.OnlineMap {            olMsg := "[" + user.Addr + "]" + user.Name + ":在线...\n"            this.conn.Write([]byte(olMsg))        }        this.server.OnlineMap.Unlock()    } else {        this.server.BroadCast(this, msg)    }}

V0.6批改用户名

user.go

//向客户端输入音讯func (this *User) sendMsg(msg string) {    this.conn.Write([]byte(msg))}//用户音讯func (this *User) DoMessage(msg string) {    if msg == "who" {        this.server.mapLock.Lock()        for _, user := range this.server.OnlineMap {            olMsg := "[" + user.Addr + "]" + user.Name + ":在线...\n"            this.sendMsg(olMsg)        }        this.server.mapLock.Unlock()    } else if len(msg) > 7 && msg[:7] == "rename|" {        newName := strings.Split(msg, "|")[1]        _, ok := this.server.OnlineMap[newName]        if ok {            this.sendMsg("以后用户名已被应用\n")        } else {            this.server.mapLock.Lock()            delete(this.server.OnlineMap, this.Name)            this.server.OnlineMap[newName] = this            this.server.mapLock.Unlock()            this.Name = newName            this.sendMsg("您曾经更名为:" + newName + "\n")        }    } else {        this.server.BroadCast(this, msg)    }}

V0.7 超时强踢

server.go

//链接胜利,执行业务func (this *Server) Handler(conn net.Conn) {    user := NewUser(conn, this)    //用户上线,将用户退出OnlineMap中    user.Online()    //监听用户是否沉闷的channel    isLive := make(chan bool)    //接管用户音讯    go func() {        buf := make([]byte, 4096)        for {            //将conn中数据读取到buf中            n, err := conn.Read(buf)            if n == 0 {                user.Offline()                return            }            if err != nil && err != io.EOF {                fmt.Println("Conn Read err:", err)                return            }            //提取用户的音讯(去除\n)            msg := string(buf[:n-1])            //将失去的音讯进行播送            user.DoMessage(msg)            //用户任意音讯            isLive <- true        }    }()    //阻塞以后Handler(避免以后及子goroutine死亡)    for {        select {        case <-isLive:            //以后用户是沉闷的,激活select重置定时器        case <-time.After(time.Second * 10):            //倒计时10秒强制退出            user.sendMsg("你被踢了")            close(user.C)            conn.Close()            return        }    }}

V0.8私聊性能

//用户音讯func (this *User) DoMessage(msg string) {    ...else if len(msg) > 4 && msg[:3] == "to|" {        remoteName := strings.Split(msg, "|")[1]        if remoteName == "" {            this.sendMsg("音讯格局不正确\n")            return        }        //获取对方User对象        remoteUser, ok := this.server.OnlineMap[remoteName]        if !ok {            this.sendMsg("用户不存在\n")            return        }        //音讯内容发送给指定User        content := strings.Split(msg, "|")[2]        if content == "" {            this.sendMsg("音讯不能为空\n")            return        }        remoteUser.sendMsg(this.Name + "对您说:" + content + "\n")    }...}

V0.9 客户端实现 client.go

建设连贯
package mainimport (    "fmt"    "net")type Client struct {    ServerIp   string    ServerPort int    Name       string    conn       net.Conn}func NewClient(serverIp string, serverPort int) *Client {    client := &Client{        ServerIp:   serverIp,        ServerPort: serverPort,    }    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))    if err != nil {        fmt.Println("net.Dial err:", err)        return nil    }    client.conn = conn    return client}func main() {    client := NewClient("127.0.0.1", 8888)    if client == nil {        fmt.Println(">>>>>连贯服务器失败")        return    }    fmt.Println(">>>>>连贯服务器胜利")    //阻塞服务     select {}}

测试

go build -o client client.go./client
解析命令行
package mainimport (    "flag"    "fmt"    "net")type Client struct {    ServerIp   string    ServerPort int    Name       string    conn       net.Conn}var serverIp stringvar serverPort int// ./client -ip 127.0.0.1 -port 8888func init() {    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP")    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口")}func NewClient(serverIp string, serverPort int) *Client {    client := &Client{        ServerIp:   serverIp,        ServerPort: serverPort,    }    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))    if err != nil {        fmt.Println("net.Dial err:", err)        return nil    }    client.conn = conn    return client}func main() {    flag.Parse()    client := NewClient(serverIp, serverPort)    if client == nil {        fmt.Println(">>>>>连贯服务器失败")        return    }    fmt.Println(">>>>>连贯服务器胜利")    select {}}

测试

go build -o client client.go./client -ip 127.0.0.1 -port 8888
菜单显示
package mainimport (    "flag"    "fmt"    "net")type Client struct {    ServerIp   string    ServerPort int    Name       string    conn       net.Conn    flag       int //以后client的模式}func (client *Client) Run() {    //除非输出0否则始终循环    for client.flag != 0 {        //除非输出0-3,否则始终循环        for client.menu() != true {        }        //依据flag执行不同业务        switch client.flag {        case 1:            fmt.Println("抉择公聊...")        case 2:            fmt.Println("抉择私聊...")        case 3:            fmt.Println("抉择改名...")        }    }}func (client *Client) menu() bool {    var flag int //接管用户输出    fmt.Println("1.公聊")    fmt.Println("2.私聊")    fmt.Println("3.改名")    fmt.Println("0.退出")    fmt.Scanln(&flag)    if flag >= 0 && flag <= 3 {        client.flag = flag        return true    } else {        fmt.Println("输出不非法")        return false    }}var serverIp stringvar serverPort int// ./client -ip 127.0.0.1 -port 8888func init() {    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP")    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口")}func NewClient(serverIp string, serverPort int) *Client {    client := &Client{        ServerIp:   serverIp,        ServerPort: serverPort,        flag:       999,    }    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))    if err != nil {        fmt.Println("net.Dial err:", err)        return nil    }    client.conn = conn    return client}func main() {    flag.Parse()    client := NewClient(serverIp, serverPort)    if client == nil {        fmt.Println(">>>>>连贯服务器失败")        return    }    fmt.Println(">>>>>连贯服务器胜利")    //启动客户端业务    client.Run()}

测试

go build -o client client.go./client -ip 127.0.0.1 -port 8888
更新用户名
package mainimport (    "flag"    "fmt"    "io"    "net"    "os")type Client struct {    ServerIp   string    ServerPort int    Name       string    conn       net.Conn    flag       int //以后client的模式}func (client *Client) Run() {    //除非输出0否则始终循环    for client.flag != 0 {        //除非输出0-3,否则始终循环        for client.menu() != true {        }        //依据flag执行不同业务        switch client.flag {        case 1:            fmt.Println("抉择公聊...")        case 2:            fmt.Println("抉择私聊...")        case 3:            client.UpdateName()        }    }}func (client *Client) menu() bool {    var flag int //接管用户输出    fmt.Println("1.公聊")    fmt.Println("2.私聊")    fmt.Println("3.改名")    fmt.Println("0.退出")    fmt.Scanln(&flag)    if flag >= 0 && flag <= 3 {        client.flag = flag        return true    } else {        fmt.Println("输出不非法")        return false    }}var serverIp stringvar serverPort int// ./client -ip 127.0.0.1 -port 8888func init() {    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP")    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口")}func NewClient(serverIp string, serverPort int) *Client {    client := &Client{        ServerIp:   serverIp,        ServerPort: serverPort,        flag:       999,    }    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))    if err != nil {        fmt.Println("net.Dial err:", err)        return nil    }    client.conn = conn    return client}func (client *Client) UpdateName() bool {    fmt.Println(">>>>>请输出用户名:")    fmt.Scanln(&client.Name)    sendMsg := "rename|" + client.Name + "\n"    _, err := client.conn.Write([]byte(sendMsg))    if err != nil {        fmt.Println("conn.Write err:", err)        return false    }    return true}//解决server回应的音讯,间接显示到规范输入即可func (client *Client) DealResponse() {    //一旦client有数据,就间接copy到stdout规范输入上,永恒阻塞监听    io.Copy(os.Stdout, client.conn)}func main() {    flag.Parse()    client := NewClient(serverIp, serverPort)    if client == nil {        fmt.Println(">>>>>连贯服务器失败")        return    }    fmt.Println(">>>>>连贯服务器胜利")    //独自开启一个goroutine解决server的回执音讯    go client.DealResponse()    //启动客户端业务    client.Run()}
公聊模式
func (client *Client) PublicChat() {    var chatMsg string    fmt.Println(">>>>>请输出聊天内容,exit退出")    fmt.Scanln(&chatMsg)    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            }            chatMsg = ""            fmt.Println(">>>>>请输出聊天内容,exit退出")            fmt.Scanln(&chatMsg)        }    }}
私聊模式
package mainimport (    "flag"    "fmt"    "io"    "net"    "os")type Client struct {    ServerIp   string    ServerPort int    Name       string    conn       net.Conn    flag       int //以后client的模式}func (client *Client) Run() {    //除非输出0否则始终循环    for client.flag != 0 {        //除非输出0-3,否则始终循环        for client.menu() != true {        }        //依据flag执行不同业务        switch client.flag {        case 1:            client.PublicChat()        case 2:            client.PrivateChat()        case 3:            client.UpdateName()        }    }}func (client *Client) menu() bool {    var flag int //接管用户输出    fmt.Println("1.公聊")    fmt.Println("2.私聊")    fmt.Println("3.改名")    fmt.Println("0.退出")    fmt.Scanln(&flag)    if flag >= 0 && flag <= 3 {        client.flag = flag        return true    } else {        fmt.Println("输出不非法")        return false    }}var serverIp stringvar serverPort int// ./client -ip 127.0.0.1 -port 8888func init() {    flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP")    flag.IntVar(&serverPort, "port", 8888, "设置服务器端口")}func NewClient(serverIp string, serverPort int) *Client {    client := &Client{        ServerIp:   serverIp,        ServerPort: serverPort,        flag:       999,    }    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))    if err != nil {        fmt.Println("net.Dial err:", err)        return nil    }    client.conn = conn    return client}func (client *Client) PublicChat() {    var chatMsg string    fmt.Println(">>>>>请输出聊天内容,exit退出")    fmt.Scanln(&chatMsg)    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            }            chatMsg = ""            fmt.Println(">>>>>请输出聊天内容,exit退出")            fmt.Scanln(&chatMsg)        }    }}func (client *Client) SelectUsers() {    sendMsg := "who\n"    _, err := client.conn.Write([]byte(sendMsg))    if err != nil {        fmt.Println("conn.Write err:", err)        return    }}func (client *Client) PrivateChat() {    var remoteName string    var chatMsg string    client.SelectUsers()    fmt.Println(">>>>>请输出用户名,exit退出")    fmt.Scanln(&remoteName)    for remoteName != "exit" {        fmt.Println(">>>>>请输出聊天内容,exit退出")        fmt.Scanln(&chatMsg)        for chatMsg != "exit" {            if len(chatMsg) != 0 {                sendMsg := "to|" + remoteName + "|" + chatMsg + "\n\n"                _, err := client.conn.Write([]byte(sendMsg))                if err != nil {                    fmt.Println("conn.Write err:", err)                    break                }                chatMsg = ""                fmt.Println(">>>>>请输出聊天内容,exit退出")                fmt.Scanln(&chatMsg)            }        }        client.SelectUsers()        fmt.Println(">>>>>请输出用户名,exit退出")        fmt.Scanln(&remoteName)    }}func (client *Client) UpdateName() bool {    fmt.Println(">>>>>请输出用户名:")    fmt.Scanln(&client.Name)    sendMsg := "rename|" + client.Name + "\n"    _, err := client.conn.Write([]byte(sendMsg))    if err != nil {        fmt.Println("conn.Write err:", err)        return false    }    return true}//解决server回应的音讯,间接显示到规范输入即可func (client *Client) DealResponse() {    //一旦client有数据,就间接copy到stdout规范输入上,永恒阻塞监听    io.Copy(os.Stdout, client.conn)}func main() {    flag.Parse()    client := NewClient(serverIp, serverPort)    if client == nil {        fmt.Println(">>>>>连贯服务器失败")        return    }    fmt.Println(">>>>>连贯服务器胜利")    //独自开启一个goroutine解决server的回执音讯    go client.DealResponse()    //启动客户端业务    client.Run()}

参考资料

8小时转职Golang工程师