首页 > golang > golang 聊天室学习笔记
2019
03-30

golang 聊天室学习笔记

1聊天室服务器端

package main

import (
    "fmt"
    "net"
    "strings"
    "time"
)

//定义的此结构体为全局map的value值,包括每一个用户的姓名,ip地址和私人管道
type client struct {
    name string
    addr string
    C    chan string
}

/*这个函数是将私人管道中的内容发送给用户,配合全局管道Message使用可以实现广播的功能,
单独使用可以实现私聊的功能*/
func writemsg2client(clinet client, conn net.Conn) {
    for m := range clinet.C {
        conn.Write([]byte(m + "n"))
    }
}

//这只是一个封装好用来统一(发送信息格式)的小函数,不用在意
func makemsg(name string, addr string, s string) string {
    return "[" + addr + "]" + name + ":" + s
}

//每一个进入聊天室的用户都将启动一个handleconn的go程来处理事件
func handleconn(conn net.Conn) {
    defer conn.Close()
    /*用户连接进来以后要初始化全局map,把自己的信息加入到字典里,相当于进到聊天室里之前要登
      记一下个人信息,注意姓名初始为ip地址。*/
    addr := conn.RemoteAddr().String()
    fmt.Printf("用户%s进入了房间n", addr)
    client := client{addr, addr, make(chan string)}
    //在这里启动子go程,功能上面已经提及,具体就是会写信息给自己连接的客户端
    go writemsg2client(client, conn)
    onlinemap[addr] = client
    //登录进来一切准备就绪后就给所有人广播上线信息啦
    Message <- makemsg(client.name, addr, "login")
    //下面这三个变量服务于下面一些小功能
    var haschat = make(chan bool)
    var ifquit = make(chan bool)
    var flag bool
    //从这单独开启一个go程来读取用户输入的信息
    go func() {
        buf := make([]byte, 4096)
        for {
            n, _ := conn.Read(buf)
            if n == 0 {
                fmt.Printf("%s离开了房间n", client.name)
                ifquit <- true
                return
            }
            //改名功能的实现
            if string(buf[:7]) == "Rename|" {
                client.name = strings.Split(string(buf[:n]), "|")[1]
                onlinemap[addr] = client
                conn.Write([]byte("rename successn"))
            } else if string(buf[:n-1]) == "/who" {
                //查询在线用户信息的功能
                for _, s := range onlinemap {
                    conn.Write([]byte(s.name + "onlinen"))
                }
            } else if string(buf[:2]) == "m|" && strings.Count(string(buf[:n]), "|") == 2 {
                /*私聊功能的实现,其实私聊功能就是跳过了往全局Message里传输信息,
                  改为直接向私人管道里传输信息*/
                flag = true
                slice := strings.Split(string(buf[:n]), "|")
                fmt.Println(slice[1])
                for _, a := range onlinemap {
                    //遍历所有在线用户,向指定的用户管道中发送信息
                    if a.name == slice[1] {
                        flag = false
                        a.C <- makemsg(client.name, addr, slice[2])
                        conn.Write([]byte("send success"))

                    }
                }
                if flag {
                    conn.Write([]byte("no such man or not online"))
                }
            } else {
                Message <- makemsg(client.name, addr, string(buf[:n]))
            }
            haschat <- true
        }
    }()
    for {
        select {
        case <-haschat:
        //超时强踢
        case <-time.After(time.Minute * 100):
            delete(onlinemap, addr)
            Message <- makemsg(client.name, addr, "out time to leave")
            close(client.C)
            return
        case <-ifquit:
            //退出处理
            delete(onlinemap, addr)
            Message <- makemsg(client.name, addr, "out time to leave")
            close(client.C)
            return
        }
    }
}

//这个函数用来将全局Message中的内容全部塞到私人管道C里,实现上下线广播和群聊的功能
func Manager() {
    for {
        msg := <-Message
        for _, s := range onlinemap {
            s.C <- msg
        }
    }
}

var Message = make(chan string)
var onlinemap map[string]client = make(map[string]client)

//主函数
func main() {
    listener, _ := net.Listen("tcp", "127.0.0.1:9876")
    defer listener.Close()
    //提前开启全局Message的go程,防止被阻塞
    go Manager()
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("accept err", err)
            continue
        }
        //每一个连接进来的用户都会被分配进入一个子go程,用来处理上面我们提到的各种功能
        go handleconn(conn)
    }
}

/*备注
1、  listener, _ := net.Listen("tcp", "127.0.0.1:9876")
监听启动

2、  go Manager()开启全局Message的go程,防止被阻塞,没有消息便被阻塞,有消息便会被唤起,
消息发送完毕后重新等待消息,有消息变发送没消息便阻塞等待(Message 是一个字符串channel )。

接收到消息后,遍历所有在线人员,并把消息发送给client的私人通道。

func Manager() {
    for {
        msg := <-Message
        for _, s := range onlinemap {
            s.C <- msg
        }
    }
}

3、私人通道消息处理
这个函数是将私人管道中的内容发送给用户,配合全局管道Message使用可以实现广播的功能。
单独使用可以实现私聊的功能(m|客户端连接ip加端口|发送消息)(m|127.0.0.1:59700|hello)。
这个函数也是等待消息,收到消息后被唤醒执行,消息执行完毕后等待新消息,没有阻塞,有就处理
func writemsg2client(clinet client, conn net.Conn) {
    for m := range clinet.C {
        conn.Write([]byte(m + "n"))
    }
}


*/

2、聊天室客户端

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func readFromServer(conn net.Conn) {
    buf := make([]byte, 4096)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            fmt.Println(err)
            os.Exit(1)

        }
        defer conn.Close()
        fmt.Println("接收到消息:", string(buf[:n]))
        fmt.Println("请输入要发送的消息:")
    }
}

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:9876")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer conn.Close()
    go readFromServer(conn)
    //fmt.Println("请输入要发送的消息:")

    for {

        //strs :=""
        // fmt.Scanln(&strs) 空格有问题
        //strs := make([]byte, 4096)

        //n, err := os.Stdin.Read(strs)
        str, err := bufio.NewReader(os.Stdin).ReadString('n')
        if err != nil {
            fmt.Println(err)
        }
        str = strings.TrimSpace(str)
        //fmt.Println("发送前", , "展示")
        //fmt.Println("a", str, "b")
        if str == "Q" {
            fmt.Println("接收到退出命令,退出客户端")
            break
        }
        conn.Write([]byte(str))

    }

}


作者:golang中国
golang中国

本文》有 9044 条评论

留下一个回复