单机轻松支持百万并发的go协程的简单tcpsocket服务端客户端通信小程序示例源码
服务端 server.go
package mainimport ("bufio""fmt""io""net""os""strings""time"
)// 简单的客户端服务端通信示例
// 知识点总结:
// 1.listener.Accept() 这个会一直等待客户端链接, 有一个链接就往后面执行代码没有链接就阻塞;
// 2. go process(conn) 会对每个链接开启一个协程进行处理
// 3. 在协程处理函数里面必须要在开头加上 defer conn.Close() 确保协程结束后关闭相应的链接资源
// 4. 从终端获取用户输入数据使用了os.Stdin作为输入源 reader := bufio.NewReader(os.Stdin)
// 5. 输入源在每次读取完毕后可以重置 reader.Reset(os.Stdin) // 每次数据发送后重置reader
// 在从conn.Read(buf)读取数据显示时需要带上这里读取到的n 如:string(buf[:n])
func main() {listener, err := net.Listen("tcp", ":8008")if err != nil {fmt.Println("链接异常:", err)}defer listener.Close() //关闭链接// 循环,等待客户端来链接for {conn, err := listener.Accept() // 等待链接if err != nil {fmt.Println("Listener.Accept:", err)continue // 跳转到下一个链接}go process(conn)}
}// 处理链接函数
func process(conn net.Conn) {defer conn.Close() //这个处理进程退出后确保关闭链接connfmt.Printf("收到来自%v的链接\n", conn.RemoteAddr())//向客户端发送一条消息conn.Write([]byte("Welcome to Go tcp server!"))chExit := make(chan int, 2) // 创建一个退出管道go sendMsFromStdin(conn, chExit)go readMsgFromClient(conn, chExit)//阻塞等待用户输入for {n, ok := <-chExitif n == 1 {fmt.Printf("客户端 %v 链接已关闭\n", conn.RemoteAddr())return} else {fmt.Printf("main管道 n=%v ok=%v \n", n, ok)break}}}
func readMsgFromClient(conn net.Conn, ch chan int) {for {// 每次循环都新创建一个切片用于读取客户端发送的数据buf := make([]byte, 1024)n, err := conn.Read(buf) // 这个Read会使协程一直阻塞在这里,直到客户端发送消息if err == nil && n > 0 {// string(buf[:n]) 这里的n很重要,代表仅显示从conn中读取到的信息fmt.Printf("收到数据:%v", string(buf[:n]))} else if err == io.EOF {//fmt.Printf("错误退出:n=%v, %v", n, err)ch <- 1break //退出} else if _, ok := <-ch; !ok {//fmt.Print("client closed!!")return //管道已经关闭,直接退出} else {fmt.Printf("等待客户端%v发送消息", conn.RemoteAddr())}}close(ch) //关闭ch
}// 从终端获取数据并发送到客户端
func sendMsFromStdin(conn net.Conn, ch chan int) {// 这里使用标准输入作为rd, os.Stdinreader := bufio.NewReader(os.Stdin)maxErrCnt := 3 // 最多重试次数,超过这个次数这个链接就会直接关闭//循环处理for {// 从终端读取用户的输入并发送给服务器line, err := reader.ReadString('\n')if err != nil {fmt.Printf("获取数据失败, %v", err)}if strings.HasPrefix(line, "quit") {fmt.Printf("服务端强制关闭了客户端 %v\n", conn.RemoteAddr())ch <- 1 //写入1 退出循环break}// 读取数据正常,开始发送数据len, err := conn.Write([]byte(line))if err != nil && maxErrCnt > 0 {maxErrCnt--fmt.Printf("发送数据错误 %v 等待3秒后重试,还剩 %d次重试机会\n", err, maxErrCnt)time.Sleep(3 * time.Second)} else if err == nil {fmt.Printf("本次发送了 %v 个字节的数据\n", len)reader.Reset(os.Stdin) // 每次数据发送后重置reader} else if maxErrCnt < 0 {ch <- 1 //写入1 退出循环break} else if _, ok := <-ch; !ok { // 因为这个管道有2个地方可以关闭, 所以加这个判断确保管道关闭后可以正确及时退出本函数return //管道已经关闭,直接退出}}close(ch) //关闭ch}
客户端 client.go
package mainimport ("bufio""fmt""io""net""os""strings""time"
)func main() {conn, err := net.Dial("tcp", "127.0.0.1:8008")if err != nil {fmt.Println("链接失败: ", err)return}defer conn.Close()chExit := make(chan int, 1)// 从终端获取数据并发送到服务端go sendMsFromStdin(conn, chExit)go readMsgFromServer(conn)//阻塞等待用户输入for {n, ok := <-chExitif n == 1 {fmt.Println("用户退出客户端!")break} else {fmt.Printf("main管道 n=%v ok=%v \n", n, ok)}}fmt.Println("client exit")
}
func readMsgFromServer(conn net.Conn) {for {buf := make([]byte, 1024)n, err := conn.Read(buf)if err == io.EOF {break} else if err == nil && len(buf[:n]) > 0 {fmt.Printf("接收到服务端的信息: %v\n", string(buf[:n]))}}
}// 从终端获取数据并发送到服务端
func sendMsFromStdin(conn net.Conn, ch chan<- int) {// 这里使用标准输入作为rd, os.Stdinreader := bufio.NewReader(os.Stdin)maxErrCnt := 3//循环处理for {// 从终端读取用户的输入并发送给服务器line, err := reader.ReadString('\n')if err != nil {fmt.Printf("获取数据失败, %v", err)}if strings.HasPrefix(line, "quit") {ch <- 1 //写入1 退出循环break}// 读取数据正常,开始发送数据len, err := conn.Write([]byte(line))if err != nil && maxErrCnt > 0 {maxErrCnt--fmt.Printf("发送数据错误 %v 等待3秒后重试,还剩 %d次重试机会\n", err, maxErrCnt)time.Sleep(3 * time.Second)} else if err == nil {fmt.Printf("本次发送了 %v 个字节的数据\n", len)reader.Reset(os.Stdin) // 每次数据发送后重置reader} else if maxErrCnt <= 0 {ch <- 1 //写入1 退出循环break} else {fmt.Printf("else len:%v err:%v \n", len, err)}}fmt.Println("即将关闭ch")close(ch) //关闭ch}
tcpSocket客户端服务端通信示例小程序演示视频-CSDN直播