Go入门之简单聊天实现

本文介绍了使用Go语言实现一个简单的聊天应用的过程。客户端通过Dial连接服务器,服务端作为中转站,采用广播方式将接收到的消息发送给所有客户端。服务端包括监听线程、工作线程和中转站,其中中转站使用管道和map结构保存客户端连接并进行消息广播。工作线程负责处理客户端的连接、登录、退出以及消息收发。整个系统利用Go的并发特性实现了高效的通信。
摘要由CSDN通过智能技术生成

初学Go,初步了解了管道、IO等用法之后,尝试实现了一个简单的聊天功能,在此记录一下思路(主要是参考的是Go语言圣经

总体框架

  1. 客户端之间负责接收消息和发送消息,服务端只是一个中转消息的中转站。
  2. 因为实现较为简单,因此当服务端收到消息后,采用广播的方式向所有客户端进行广播

客户端实现

客户端需要实现的主要是一个向服务器发送消息并且接收消息的功能,因此实现较为简单:

  1. Dial连接服务器
  2. 创建一个协程对conn进行监听,将消息打印到标准输出
  3. 主协程则负责消息的输入

实现较为简单,因此略,主要讲服务端的实现

func main() {
	conn, err := net.Dial("tcp", "localhost:8888")
	if err != nil {
		log.Fatal(err)
		return
	}

	// 接收conn的消息
	go func() {
		mustCopy(os.Stdout, conn)
	}()

	defer conn.Close()
	mustCopy(conn, os.Stdin)
}

func mustCopy(dst io.Writer, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

服务端实现

首先要思考清楚服务端的设计:

  1. 监听线程,也就是主线程,该线程主要负责监听服务器的端口并且把客户端的连接交给工作线程
  2. 工作线程(handleConn)主要负责处理客户端的登录、退出(实际上在本场景中就是链接的断开以及将登录退出的消息广播给其他客户端),以及客户端消息的接收、发送。
    当然,客户端消息的接收和发送也很难在一个线程中完成,因此工作线程中还需要再创建一个线程专门负责将客户端收到的广播信息发送给对应的客户端
  3. 中转站(broadcaster),当某个工作线程收到来自客户端的消息之后,需要将消息发给中转站,由中转站进行广播;此外还需要负责保存当前所有连接的客户端

数据结构

因此数据结构的设计如下:

// client被定义为一个只能发送的管道,主要目的就是广播到各个工作线程
type client chan<- string

// leaving和entering处理客户端的注册、注销
// message负责接受客户端消息并且广播
// 都是全局变量
var (
	message  = make(chan string)
	leaving  = make(chan client)
	entering = make(chan client)
)

此外中转站用map结构来保存客户端的连接

clients := make(map[client]bool)

监听线程

func main() {
	listener, err := net.Listen("tcp", "localhost:8888")
	if err != nil {
		log.Fatal(err)
		return
	}
	go broadcaster()
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Fatal(err)
			continue
		}

		go handlerConn(conn)
	}
}

中转站

主要使用select来实现注册、注销、广播功能,思路简单

func broadcaster() {
	clients := make(map[client]bool)
	for {
		select {
		case msg := <-message:
			for cli := range clients {
				cli <- msg
			}
		case cli := <-leaving:
			delete(clients, cli)
		case cli := <-entering:
			clients[cli] = true
		}
	}
}

工作线程

// 客户端处理
func handlerConn(conn net.Conn) {
	ch := make(chan string)
	// 将广播的消息写回客户端
	go clientWriter(conn, ch)

	name := conn.RemoteAddr().String()
	
	// 登录
	ch <- "You are " + name + "!"
	message <- name + " has arrived!"
	entering <- ch
	
	// 从客户端接收消息,发送到中转站
	input := bufio.NewScanner(conn)
	for input.Scan() {
		msg := input.Text()
		message <- name + ": " + msg
	}
	
	// 注销
	message <- name + " has exited!"
	leaving <- ch
	conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg)
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值