Go TCP Socket-Server-Client

[toc]

Go语言实现TCP通信

TCP协议

TCP/IP (Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议,数据像水流一样传输,会存在黏包问题。

tcp通信是服务端先发起的,我们先看一下服务端

A simple communication rule

展开查看基础的代码

服务端代码:

code

客户端代码:

code1

🚀 编译结果如下:

D:\文档\最近的\awesome-golang\docs\code\go-super\socket>go run 57-main.go
连接服务器成功 &{{0xc000004c80}}
        sadf
给服务器发送数据成功 4
读取服务器发送的数据成功 SADF

D:\文档\最近的\awesome-golang\docs\code\go-super\socket>go run 56-main.go
监听中...
监听成功 127.0.0.1:8080
接受客户端连接成功 &{{0xc000004f00}}
读取客户端发送的数据成功 sadf
转化为大写成功 SADF
转化为小写成功 sadf

多次请求和连接

上面的问题?

我们只能接收或者发送一串数据,是不是可以用 for 这样可以发送更多的数据。

  • 主 Go 程负责监听,子 Go 程序负责数据处理。

如果服务端:客户端是 1 : n 那么可不可以用 goroutine 实现高效处理

一个 TCP 服务端可以同时连接很多个客户端,例如世界各地的用户使用自己电脑上的浏览器访问淘宝网。因为Go语言中创建多个 goroutine 实现并发非常方便和高效,所以我们可以每建立一次链接就创建一个 goroutine 去处理。

改进代码

客户端💡简单的一个案例如下:

/*
Listen on an address and port for incoming connections.

:param ip: IP address to listen on
:param port: Port to listen on
:returns: A listener object
*/
package main

import (
	"fmt"
	"net"
	"strings"
	"time"
)

func main() {
	// 创建监听
	ip := "127.0.0.1"
	port := 8080
	address := fmt.Sprintf("%s:%d", ip, port) //Sprintf格式化并返回一个字符串而不打印它

	listener, err := net.Listen("tcp", address)

	if err != nil {
		fmt.Println("监听失败", err)
		return
	}
	defer listener.Close()
	fmt.Println("监听成功", address)

	// 清除终端
	fmt.Println("\033[2J")

	for {
		//listerner表示监听器,用于接收客户端的连接请求
		conn, err := listener.Accept() // 表示接受客户端的连接请求
		if err != nil {
			fmt.Println("接受客户端连接失败", err)
			return
		}
		fmt.Println("接受客户端连接成功", conn)

		go func() {
			for {
				// 读取客户端发送的数据
				buf := make([]byte, 1024) // 创建一个切片,用于存储客户端发送的数据
				n, err := conn.Read(buf)  // 读取客户端发送的数据
				if err != nil {
					fmt.Println("读取客户端发送的数据失败", err)
					return
				}
				fmt.Println("读取客户端发送的数据成功", string(buf[:n]))

				// 数据转化为大写
				upper := strings.ToUpper(string(buf[:n]))
				fmt.Println("转化为大写成功", upper)

				// 将数据转化为小写
				lower := strings.ToLower(string(buf[:n]))
				fmt.Println("转化为小写成功", lower)

				// 将数据写回给客户端
				conn.Write([]byte(upper))
				conn.Write([]byte(lower))

				// 给客户端发送数据
				conn.Write([]byte("hello, client"))

				//等待一分钟没有客户端连接,就关闭服务
				time.Sleep(time.Minute)
				listener.Close()
			}
			// 关闭连接
			conn.Close()
		}()
	}
}

服务端💡简单的一个案例如下:

package main

import (
	"fmt"
	"net"
	"time"
)

/*
go

	Here's what the selected code is doing:
	1.Import the socket module.
	2. Create a socket object.
	3. Get the local hostname.
	4. Print the hostname to the console.
	5. Set the default port number.
	6. Bind the socket to the port.
	7. Listen for incoming connections.
	8. Accept an incoming connection.

	客户端

	:param conn: 连接服务器
	:param err: 错误
	:return:
*/
func main() {
	// 客户端
	conn, err := net.Dial("tcp", ":8080")
	if err != nil {
		fmt.Println("连接服务器失败", err)
		return
	}
	fmt.Println("连接服务器成功", conn)

	for {
		// 输入数据切片类型
		fmt.Println("请发送数据 -> 客户端")
		var input []byte
		fmt.Scanln(&input)

		// 给服务器发送数据
		cnt, err := conn.Write([]byte(input))
		if err != nil {
			fmt.Println("给服务器发送数据失败", err)
			return
		}

		fmt.Println("给服务器发送数据成功", cnt)

		// 读取服务器发送的数据
		buf := make([]byte, 1024)
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println("读取服务器发送的数据失败", err)
			return
		}

		fmt.Println("读取服务器发送的数据成功", string(buf[:n]))

		time.Sleep(time.Second * 2)
	}
	// 关闭连接
	defer conn.Close()
}

🚀 编译结果如下:

PS D:\文档\最近的\awesome-golang\docs\code\go-super\socket> go run .\56-main.go
接受客户端连接成功 &{{0xc000004f00}}
读取客户端发送的数据成功 12
转化为大写成功 12
转化为小写成功 12
读取客户端发送的数据成功 312
转化为大写成功 312
转化为小写成功 312
读取客户端发送的数据成功 4213412
转化为大写成功 4213412
转化为小写成功 4213412
读取客户端发送的数据成功 AFSSDFAS
转化为大写成功 AFSSDFAS
转化为小写成功 afssdfas
读取客户端发送的数据成功 aFASSaffdF
转化为大写成功 AFASSAFFDF

D:\文档\最近的\awesome-golang\docs\code\go-super\socket>go run 57-main.go
连接服务器成功 &{{0xc0000cca00}}
请发送数据 -> 客户端
12
给服务器发送数据成功 2
读取服务器发送的数据成功 12
请发送数据 -> 客户端
312
给服务器发送数据成功 3
读取服务器发送的数据成功 12hello, client
请发送数据 -> 客户端
4213412
给服务器发送数据成功 7
读取服务器发送的数据成功 312312hello, client
请发送数据 -> 客户端
AFSSDFAS
给服务器发送数据成功 8
读取服务器发送的数据成功 42134124213412hello, client
请发送数据 -> 客户端
aFASSaffdF
给服务器发送数据成功 10
读取服务器发送的数据成功 AFSSDFASafssdfashello, client
请发送数据 -> 客户端

TCP 服务端

TCP服务端程序的处理流程:

  1. 监听端口

  2. 接收客户端请求建立链接

  3. 创建goroutine处理链接。

我们使用Go语言的net包实现的TCP服务端代码如下:

// tcp/server/main.go

// TCP server端

// 处理函数
func process(conn net.Conn) {
    defer conn.Close()  // 关闭连接
    for {
        reader := bufio.NewReader(conn)
        var buf [128]byte
        n, err := reader.Read(buf[:]) // 读取数据
        if err != nil {
            fmt.Println("read from client failed, err:", err)
            break
        }
        recvStr := string(buf[:n])
        fmt.Println("收到client端发来的数据:", recvStr)
        conn.Write([]byte(recvStr))  // 发送数据
    }
}

func main() {
    listen, err := net.Listen("tcp", "127.0.0.1:20000")
    if err != nil {
        fmt.Println("listen failed, err:", err)
        return
    }
    for {
        conn, err := listen.Accept() // 建立连接
        if err != nil {
            fmt.Println("accept failed, err:", err)
            continue
        }
        go process(conn) // 启动一个goroutine处理连接
    }
}

将上面的代码保存之后编译成serverserver.exe可执行文件。

TCP 客户端

一个TCP客户端进行TCP通信的流程如下:

  1. 建立与服务端的链接
  2. 进行数据收发
  3. 关闭链接

使用Go语言的 net 包实现的 TCP 客户端代码如下:

// tcp/client/main.go

// 客户端
func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:20000")
    if err != nil {
        fmt.Println("err :", err)
        return
    }
    defer conn.Close() // 关闭连接
    inputReader := bufio.NewReader(os.Stdin)
    for {
        input, _ := inputReader.ReadString('\n') // 读取用户输入
        inputInfo := strings.Trim(input, "\r\n")
        if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
            return
        }
        _, err = conn.Write([]byte(inputInfo)) // 发送数据
        if err != nil {
            return
        }
        buf := [512]byte{}
        n, err := conn.Read(buf[:])
        if err != nil {
            fmt.Println("recv failed, err:", err)
            return
        }
        fmt.Println(string(buf[:n]))
    }
}

⚠️ 将上面的代码编译成 clientclient.exe 可执行文件,先启动 server 端再启动client端,在client端输入任意内容回车之后就能够在server端看到client端发送的数据,从而实现TCP通信。

将上面的代码编译成clientclient.exe可执行文件,先启动server端再启动client端,在client端输入任意内容回车之后就能够在server端看到client端发送的数据,从而实现TCP通信。

END 链接