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

服务器端: 接受来自于客户端的连接请求并建立连接; 所有客户端的连接会放进连接池中,用于广播消息;

客户端: 连接服务器; 向服务器发送消息; 接收服务器的广播消息;

注意事项: 某一个客户端断开连接后需要从连接池中摘除,并不再接收广播消息; 某一个客户端断开连接后不能影响服务器端或别的客户端的连接;

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

服务器:

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中输入字符并回车,可以看到别的终端都会收到消息;