手记

golang用TCP协议实现简单的聊天室


通常聊天室的架构分为服务器端和客户端:

服务器端:

接受来自于客户端的连接请求并建立连接;

所有客户端的连接会放进连接池中,用于广播消息;

客户端:

连接服务器;

向服务器发送消息;

接收服务器的广播消息;

注意事项:

某一个客户端断开连接后需要从连接池中摘除,并不再接收广播消息;

某一个客户端断开连接后不能影响服务器端或别的客户端的连接;

详细的代码如下,文档看注释就好了,不再细说:

服务器:

server.go

package main

import (

    "net"

    "log"

    "fmt"

)

func main() {

    port := "9090"

    Start(port)

}

// 启动服务器

func Start(port string) {

    host := ":" + port

    // 获取tcp地址

    tcpAddr, err := net.ResolveTCPAddr("tcp4", host)

    if err != nil {

        log.Printf("resolve tcp addr failed: %v\n", err)

        return

    }

    // 监听

    listener, err := net.ListenTCP("tcp", tcpAddr)

    if err != nil {

        log.Printf("listen tcp port failed: %v\n", err)

        return

    }

    // 建立连接池,用于广播消息

    conns := make(map[string]net.Conn)

    // 消息通道

    messageChan := make(chan string, 10)

    // 广播消息

    go BroadMessages(&conns, messageChan)

    // 启动

    for {

        fmt.Printf("listening port %s ...\n", port)

        conn, err := listener.AcceptTCP()

        if err != nil {

            log.Printf("Accept failed:%v\n", err)

            continue

        }

        // 把每个客户端连接扔进连接池

        conns[conn.RemoteAddr().String()] = conn

        fmt.Println(conns)

        // 处理消息

        go Handler(conn, &conns, messageChan)

    }

}

// 向所有连接上的乡亲们发广播

func BroadMessages(conns *map[string]net.Conn, messages chan string) {

    for {

        // 不断从通道里读取消息

        msg := <-messages

        fmt.Println(msg)

        // 向所有的乡亲们发消息

        for key, conn := range *conns {

            fmt.Println("connection is connected from ", key)

            _, err := conn.Write([]byte(msg))

            if err != nil {

                log.Printf("broad message to %s failed: %v\n", key, err)

                delete(*conns, key)

            }

        }

    }

}

// 处理客户端发到服务端的消息,将其扔到通道中

func Handler(conn net.Conn, conns *map[string]net.Conn, messages chan string) {

    fmt.Println("connect from client ", conn.RemoteAddr().String())

    buf := make([]byte, 1024)

    for {

        length, err := conn.Read(buf)

        if err != nil {

            log.Printf("read client message failed:%v\n", err)

            delete(*conns, conn.RemoteAddr().String())

            conn.Close()

            break

        }

        // 把收到的消息写到通道中

        recvStr := string(buf[0:length])

        messages <- recvStr

    }

}

客户端:

client.go

package main

import (

    "net"

    "log"

    "fmt"

    "os"

)

func main() {

    Start(os.Args[1])

}

func Start(tcpAddrStr string) {

    tcpAddr, err := net.ResolveTCPAddr("tcp4", tcpAddrStr)

    if err != nil {

        log.Printf("Resolve tcp addr failed: %v\n", err)

        return

    }

    // 向服务器拨号

    conn, err := net.DialTCP("tcp", nil, tcpAddr)

    if err != nil {

        log.Printf("Dial to server failed: %v\n", err)

        return

    }

    // 向服务器发消息

    go SendMsg(conn)

    // 接收来自服务器端的广播消息

    buf := make([]byte, 1024)

    for {

        length, err := conn.Read(buf)

        if err != nil {

            log.Printf("recv server msg failed: %v\n", err)

            conn.Close()

            os.Exit(0)

            break

        }

        fmt.Println(string(buf[0:length]))

    }

}

// 向服务器端发消息

func SendMsg(conn net.Conn) {

    username := conn.LocalAddr().String()

    for {

        var input string

        // 接收输入消息,放到input变量中

        fmt.Scanln(&input)

        if input == "/q" || input == "/quit" {

            fmt.Println("Byebye ...")

            conn.Close()

            os.Exit(0)

        }

        // 只处理有内容的消息

        if len(input) > 0 {

            msg := username + " say:" + input

            _, err := conn.Write([]byte(msg))

            if err != nil {

                conn.Close()

                break

            }

        }

    }

}

测试方法:

编译server.go和client.go;

打开终端,启动server,默认会监听9090端口;

再打开多个终端,启动client,client启动命令:client 服务器IP:9090;

在client中输入字符并回车,可以看到别的终端都会收到消息;

©著作权归作者所有:来自51CTO博客作者ustb80的原创作品,如需转载,请注明出处,否则将追究法律责任


0人推荐
随时随地看视频
慕课网APP