如何在Golang中复用net.Conn实现Socket多消息收发?
问题分析与解决方案
核心问题定位
- 连接存储错误:服务端用端口号作为连接map的key,新连接会直接覆盖旧连接,导致仅能保存最后一个客户端连接。
- 单次读取即关闭:服务端
connRead函数仅执行一次读取就关闭连接,完全违背长连接复用的需求。 - 客户端读取逻辑bug:连续调用两次
readLine(),会跳过一行消息;且未处理流结束的异常场景。 - 消息边界不清晰:虽然手动添加了换行符,但两端的消息发送/接收逻辑未对齐,导致读取时无法稳定识别完整消息。
修改后的代码实现
Golang服务端代码
修正连接管理、循环读取逻辑,保证长连接复用:
import ( "bufio" "flag" "fmt" "log" "net" "os" "sync" ) const TYPE = "tcp" var ( SOCKET_PORT = flag.Int("socket_server_port", 9001, "Socket server port") socketConnArr = make(map[string]net.Conn) connMutex sync.Mutex // 并发操作map的安全锁 ) func StartSocket() { listen, err := net.Listen(TYPE, fmt.Sprintf(":%d", *SOCKET_PORT)) if err != nil { log.Fatalf("listen err: %s", err) os.Exit(1) } defer listen.Close() fmt.Printf("Socket server running at :%d...\n", *SOCKET_PORT) for { conn, err := listen.Accept() if err != nil { log.Printf("accept err: %s", err) continue } remoteAddr := conn.RemoteAddr().String() connMutex.Lock() socketConnArr[remoteAddr] = conn connMutex.Unlock() fmt.Printf("client connected: %s\n", remoteAddr) // 为每个客户端启动独立的读写goroutine go handleClient(conn) } } // 处理单个客户端的持续读写逻辑 func handleClient(conn net.Conn) { remoteAddr := conn.RemoteAddr().String() // 连接断开时清理map并关闭连接 defer func() { connMutex.Lock() delete(socketConnArr, remoteAddr) connMutex.Unlock() conn.Close() fmt.Printf("client disconnected: %s\n", remoteAddr) }() // 发送初始确认消息 connWrite(conn, "socket server ack") // 循环读取客户端消息,直到连接断开 scanner := bufio.NewScanner(conn) for scanner.Scan() { msg := scanner.Text() fmt.Printf("Received from %s: %s\n", remoteAddr, msg) // 可在此添加业务逻辑,比如回复客户端 connWrite(conn, "Received: "+msg) } if err := scanner.Err(); err != nil { log.Printf("read from %s err: %s", remoteAddr, err) } } func SockWrite(message string) { connMutex.Lock() defer connMutex.Unlock() for _, conn := range socketConnArr { go connWrite(conn, message) } } func connWrite(conn net.Conn, message string) { // 统一添加换行符作为消息分隔符 _, err := fmt.Fprintf(conn, "%s\n", message) if err != nil { log.Printf("write err: %s", err) } } func GetConnList() map[string]net.Conn { connMutex.Lock() defer connMutex.Unlock() // 返回map副本,避免外部修改原数据 copyMap := make(map[string]net.Conn, len(socketConnArr)) for k, v := range socketConnArr { copyMap[k] = v } return copyMap }
Android Kotlin客户端代码
修正读取逻辑,优化资源管理:
lifecycleScope.launch(Dispatchers.IO) { var response: String? var reader: BufferedReader? = null var writer: PrintWriter? = null var socket: Socket? = null try { socket = Socket(SERVER_IP, SERVER_PORT) writer = PrintWriter(socket.getOutputStream(), true) // 自动flush reader = BufferedReader(InputStreamReader(socket.getInputStream())) // 发送初始化消息 writer.println(socketInitMsg) Log.i(SocketHelper.TAG, "socket connected: ${socket.isConnected}") // 循环读取服务端消息,直到流关闭 while (reader.readLine().also { response = it } != null) { Log.i(SocketHelper.TAG, "response: $response") // 在此添加消息处理逻辑 } Log.i(SocketHelper.TAG, "socket disconnected") } catch (e: IOException) { Log.e(SocketHelper.TAG, "IO error: ${e.localizedMessage}") } catch (e: Exception) { Log.e(SocketHelper.TAG, "error: ${e.localizedMessage}") } finally { // 确保所有资源被关闭 reader?.close() writer?.close() socket?.close() } }
关键改进点说明
- 连接管理优化:用客户端
RemoteAddr()作为map的key,添加互斥锁保证并发安全,连接断开时自动从map中清理。 - 长连接维持:服务端通过
bufio.Scanner循环读取客户端消息,直到连接断开;客户端修正读取逻辑,避免跳过消息。 - 消息边界统一:两端均用换行符
\n作为消息分隔符,确保发送和接收的消息能被正确识别。 - 错误处理增强:服务端不再因单个连接错误终止程序,客户端添加
finally块保证资源正常释放。 - 并发安全:对连接map的操作添加互斥锁,避免多goroutine操作导致的panic。
内容的提问来源于stack exchange,提问作者rminaj
相关产品推荐
相关产品推荐

