单机轻松支持百万并发的go协程的简单tcpsocket服务端客户端通信小程序示例源码

单机轻松支持百万并发的go协程的简单tcpsocket服务端客户端通信小程序示例源码

服务端 server.go

package main

import (
	"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() //这个处理进程退出后确保关闭链接conn

	fmt.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 := <-chExit
		if 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 <- 1
			break //退出
		} 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.Stdin
	reader := 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 main

import (
	"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 := <-chExit
		if 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.Stdin
	reader := 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直播

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值