go socket实现灵活发送接收消息
使用socket实现类似微信单聊自由发送或接收消息的功能.
server端:
func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { panic(err) } log.Println("Listening... ...") for { conn, err := listener.Accept() if err != nil { panic(err) } log.Println("connect success") go handFunc(conn) } }
server端主函数监听8080端口,为了实现多用户连接,使用for循环使Accept方法能接收多个客户端发送的连接请求,handFunc函数是处理连接成功后要进行的交互操作,使用goroutine开起一个协程去操作.
func handFunc(conn net.Conn) { recv, send := make(chan string), make(chan string) for { go func(r chan string) { buf := make([]byte, 1024) cnt, err := conn.Read(buf) if err != nil { panic(err) } r <- fmt.Sprintf("recv : %v", string(buf[:cnt])) }(recv) go func(s chan string) { reader := bufio.NewReader(os.Stdin) line, _, _ := reader.ReadLine() cnt, err := conn.Write(line) if err != nil { panic(err) } s <- fmt.Sprintf("send : %v", string(line[:cnt])) }(send) select { case accept := <-recv: log.Println(accept) case to := <-send: log.Println(to) } } }
handFunc中接收主函数连接成功后建立的net.Conn参数,函数中主要实现服务端信息的接收和发送功能.for循环为了实现多次交互,handFunc中又开起了两个goroutine,第一个用于接收消息,第二个用于输入内容发送给客户端,使用channel和select实现自由发送和接收.
先定义两个字符串类型的管道,分别将两个管道传递给两个匿名函数,第一个匿名函数中在conn.Read()时发生阻塞,直到有客户端发来消息,把处理后的消息写入管道中;第二个匿名函数在reader.ReadLine()处发生阻塞,直到控制台有消息输入,通过conn.Write()方法将控制台输入的消息发送给客户端并将提示信息写入对应管道.在select处我们监听这两个管道,两个管道中只要有一个获取到数据就会打印相应管道中的消息并结束select的阻塞.(注意:此时无论是接收了消息还是发送了消息,handFunc中的两个goroutine只是关闭了一个,另一个还在阻塞状态,下边我们还会提起未关闭的goroutine)
client端:
func main() { conn, err := net.Dial("tcp", ":8080") if err != nil { panic(err) } log.Println("connect success") recv, send := make(chan string), make(chan string) for { go func(s chan string) { reader := bufio.NewReader(os.Stdin) line, _, _ := reader.ReadLine() cnt, err := conn.Write(line) if err != nil { panic(err) } s <- fmt.Sprintf("send: %v", string(line[:cnt])) }(send) go func(r chan string) { buf := make([]byte, 1024) cnt, err := conn.Read(buf) if err != nil { panic(err) } r <- fmt.Sprintf("recv: %v", string(buf[:cnt])) }(recv) select { case accept := <-recv: log.Println(accept) case to := <-send: log.Println(to) } } }
客户端的方法和服务端如出一辙,同样是使用for循环使程序可以接收和发送多次,使用管道和select实现控制台输入和服务端消息的监听.
结果展示:
server端: client端:
可以看到,服务启动后显示连接成功,服务端连续发送两条消息,客户端接收两条消息成功;客户端随后发送两条,服务端正常接收.此时类似微信单聊的简单效果已经完成了.
现在我们回头看上文中提到的handFunc中另一个goroutine还处于阻塞状态的问题.如果你只用一个客户端也发现不了什么问题,但如果同时开启了多个客户端会怎么样呢?下边我们测试一下:
我们同时启动两个client端并给server发消息,发现一切都正常,server端能正常接收两个client发送的消息(通过conn.RemoteAddr()方法可以获取到客户端的ip和端口).但当服务端发送请求时,却出现消息轮番发送给client1和client2的情况,这是因为在handFunc中,在服务端接收到消息时,另一个goroutine还在阻塞中,只有当server端在客户端输入内容并发送后,goroutine才会结束.也就是说,服务端的reader.ReadLine()一直处于阻塞状态,如果client1先连接了server端,那么在server端的终端输入内容时,会先回复client1,这样就解除了client1在server端发生的写入阻塞,接着client2发生的写阻塞会在server的终端中等待,直到终端有消息写入,这样就出现client1和client2轮番接收数据的情况,具体解决办法这里就不说了,欢迎大家提供建议.