美文网首页
Go的聊天实现

Go的聊天实现

作者: 编程_书恨少 | 来源:发表于2019-04-28 17:57 被阅读0次

一、主功能描述:
1.服务端监听端口,看是否有客户端发送信息,根据不同的客户端ip进行中转
2.客户端接收服务端发来的信息,并且自己也可以发信息给服务端

二、注意点
1.使用协程进行服务端接收客户端信息
2.string(buf[0:numOfBytes]) ,限制重复字符
3.添加日志

三、代码实现
1.服务端实现

// chatroom server
package main

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

const (
    LOG_DIRECTORY = "./test.log" //记录错误日志的路径
)

var onlineConns = make(map[string]net.Conn)//存储客户端链接映射 key为链接ip:port value为连接对象conn
var messageQueue = make(chan string, 1000)//消息队列 带缓冲的buf
var logFile *os.File
var logger *log.Logger

var quitChan = make(chan bool)

func CheckError(err error) {
    if err != nil {
        panic(err)
    }
}

//消息接收协程
func ProcessInfo(conn net.Conn) {
    buf := make([]byte, 1024)
    //协程退出时 说明客户端断开链接 所以要将当前链接从onlineConns删除掉 
    defer func(conn net.Conn) {
        addr := fmt.Sprintf("%s", conn.RemoteAddr())
        delete(onlineConns, addr)
        conn.Close()

        for i := range onlineConns {
            fmt.Println("now online conns:" + i)
        }
    }(conn) //采用匿名函数的方式 调用defer

    for {
        numOfBytes, err := conn.Read(buf)
        if err != nil {
            break
        }

        if numOfBytes != 0 {
            /*结尾buf[0:numOfBytes]的原因是:numOfBytes是指接收的字节数 如果只用string(buf)
                可能会导致接收字符串中有其他之前接收的字符
            */
            message := string(buf[0:numOfBytes])

            //将消息放入到消息队列
            messageQueue <- message
        }
    }
}

//消费者协程
func ConsumeMessage() {
    for {
        select {
        case message := <-messageQueue:
            //对消息进行解析
            doProcessMessage(message)
        case <-quitChan:
            break
        }
    }
}

//消息解析函数
func doProcessMessage(message string) {
    contents := strings.Split(message, "#")
    if len(contents) > 1 {
        addr := contents[0]
        sendMessage := strings.Join(contents[1:], "#")//这么做是为了防止 消息体也含有"#"

        addr = strings.Trim(addr, " ")

        if conn, ok := onlineConns[addr]; ok {
            _, err := conn.Write([]byte(sendMessage))
            if err != nil {
                fmt.Println("online conns send failure!")
            }
        }
    } else {
        //走到这里 说明客户端调用list命令 查看系统当前链接ip
        contents := strings.Split(message, "*")
        if strings.ToUpper(contents[1]) == "LIST" {
            var ips string = ""
            for i := range onlineConns {
                ips = ips + "|" + i
            }
            if conn, ok := onlineConns[contents[0]]; ok {
                _, err := conn.Write([]byte(ips))
                if err != nil {
                    fmt.Println("online conns send failure!")
                }
            }
        }
    }
}

func main() {
    //打开日志文件
    logFile, err := os.OpenFile(LOG_DIRECTORY, os.O_RDWR|os.O_CREATE, 0)
    if err != nil {
        fmt.Println("log file create failure!")
        os.Exit(-1)
    }

    defer logFile.Close()

    //利用go自带的log 将打开文件对象生成日志文件对象
    logger = log.New(logFile, "\r\n", log.Ldate|log.Ltime|log.Llongfile)

    listen_socket, err := net.Listen("tcp", "127.0.0.1:8080")
    CheckError(err)
    defer listen_socket.Close()

    fmt.Println("Server is waiting....")

    logger.Println("I am writing the logs...")

    //开启消费者协程
    go ConsumeMessage()

    for {
        conn, err := listen_socket.Accept()
        CheckError(err)

        //将conn存储到onlineConns映射表中
        addr := fmt.Sprintf("%s", conn.RemoteAddr())
        //如果有客户端链接 则将对应ip和链接对象以KV形式记录到onlineConns中
        onlineConns[addr] = conn
        for i := range onlineConns {
            fmt.Println(i)
        }

        //开启协程处理
        go ProcessInfo(conn)
    }
}

2.客户端

// chatroom_client
package main

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

func CheckError(err error) {
    if err != nil {
        panic(err)
    }
}

func MessageSend(conn net.Conn) {
    var input string
    for {
        //接收系统标准输入
        reader := bufio.NewReader(os.Stdin)
        data, _, _ := reader.ReadLine()
        input = string(data)

        //如果客户端输入exit 表示要结束连接
        if strings.ToUpper(input) == "EXIT" {
            conn.Close()
            break
        }

        _, err := conn.Write([]byte(input))
        if err != nil {
            conn.Close()
            fmt.Println("client connect failure: " + err.Error())
            break
        }
    }
}

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:8080")
    CheckError(err)
    defer conn.Close()

    //开启消息发送协程
    go MessageSend(conn)

    //主协程负责接收消息
    buf := make([]byte, 1024)
    for {
        numOfBytes, err := conn.Read(buf)

        //如果客户端输入exit结束 不再像之前抛出异常 而是给出提示消息
        if err != nil {
            fmt.Println("您已经退出 欢迎使用聊天通讯!")
            os.Exit(0)
        }

        /*结尾buf[0:numOfBytes]的原因是:numOfBytes是指接收的字节数 如果只用string(buf)
            可能会导致接收字符串中有其他之前接收的字符
        */
        fmt.Println("receive server message content:" + string(buf[0:numOfBytes]))
    }

    fmt.Println("Client program end!")
}

相关文章

网友评论

      本文标题:Go的聊天实现

      本文链接:https://www.haomeiwen.com/subject/rjcdnqtx.html