Golang tcp socket编程案例

简单的介绍

  1. 项目代码基于 Go mod 方式
  2. 纯Golang编写
  3. 基于 golang net 包
  4. 代码请以最终整理的代码为主
  5. 本人菜鸡一枚,代码写得不好,望各位大佬口下留情~

步骤代码实现

1、定义一个 server(服务端) 结构体以及获取它的函数(服务端)
package server

type server struct {
	// 网络协议类型
	networkType string
	// ip地址 + 端口
	ipAddress string
}

// NewServer 通过此函数可以得到一个 server 结构体
func NewServer(ipAddress string) server {
	if ipAddress == "" {
		ipAddress = "127.0.0.1:8088"
	}
	return server{
		// 默认值 -- 方便开发
		networkType: "tcp",
		ipAddress: ipAddress,
	}
}

2、编写服务端的启动方法(服务端)
// StartServer 启动服务端的方法
func (s server) StartServer() {

	// 1. 表示使用的网络协议:tcp
	// 2. 对本机进行监听 ip为 xxx.xxx.xxx.xxx + 端口为 xxx
	listener, err := net.Listen(s.networkType, s.ipAddress)
	if err != nil {
		panic(fmt.Sprintf("服务器启动失败, err = %v \n", err))
	}

	fmt.Printf("服务器端启动成功, %v \n", listener.Addr().String())

	// 服务器不断的接收客户端的信息
	for {
		conn, err := listener.Accept()
		// 客户端的信息
		remoteAddr := conn.RemoteAddr()
		if err != nil {
			fmt.Printf("客户端: %v 链接服务器端出现异常, err = %v \n", remoteAddr.String(), err)
			// 跳过此客户端链接不在往下执行
			continue
		}
		fmt.Printf("客户端: %v 链接服务器端成功 \n", remoteAddr.String())
		// 处理客户端的消息..
	}
}
3、定义一个 client(客户端) 结构体以及获取它的函数(客户端)
package client

import "net"

type client struct {
	// 网络协议类型
	networkType string
	// ip地址 + 端口
	ipAddress string
}

// NewClient 通过此函数可以得到一个 client 结构体
func NewClient(ipAddress string) client {
	return client{
		networkType: "tcp",
		ipAddress:   ipAddress,
	}
}
4、编写客户端启动方法(客户端)
// StartClient 客户端启动方法
func (c client) StartClient() {
	conn, err := net.Dial(c.networkType, c.ipAddress)
	if err != nil {
		fmt.Printf("链接服务器端: %v , 出现异常 err = %v\n", c.ipAddress, err)
		return
	}
	fmt.Println("链接服务器端成功", conn)
}
5、服务端启动,客户端是否能连接上?(小测试)
5.1、服务器端启动
package main

import "tcp_socket_demo/server"

func main() {

	// 得到一个服务器端的结构体
	server := server.NewServer("")
	// 调用启动的方法
	server.StartServer()
	
}
5.2、服务器端启动的效果

在这里插入图片描述

5.3、客户端启动
package main

import (
	"tcp_socket_demo/client"
)

func main() {
	client := client.NewClient("127.0.0.1:8088")
	client.StartClient()
}
5.4、客户端启动的效果

在这里插入图片描述

5.5、测试客户端链接服务器端的效果

在这里插入图片描述

6、编写服务端处理客户端消息代码(服务端)
6.1、在 StartServer() 方法中 加入 go process(conn),并且加上时间打印的效果
// 获取当前运行的时间
func nowTime() string {
	return time.Now().Format("2006-01-02 15:04:05")
}
6.2、process函数
// 用于处理客户端消息的函数
func process(conn net.Conn) {
	// 客户端断开链接后要及时关闭
	defer conn.Close()
	connRemoteAddr := conn.RemoteAddr().String()
	// 循环读取客户端的消息
	for {
		// 创建一个1024长度的 byte 切片用于存储客户端的消息
		bytes := make([]byte, 1024)
		// 等待客户端通过 conn 发送消息
		// 如果客户端一直没有发送,那么此协程就阻塞在这里
		readLen, err := conn.Read(bytes)
		if err != nil {
			errStr := err.Error()
			contains := strings.Contains(errStr, "An existing connection was forcibly closed by the remote host")
			if contains || errStr == "EOF" {
				fmt.Printf("客户端[%v]: %v 断开链接\n", nowTime(), connRemoteAddr)
				return
			} else {
				fmt.Printf("服务器读取客户端[%v]: %v 消息出现异常, err = %v \n", nowTime(), connRemoteAddr, err)
				return
			}
		}
		// 服务器端显示客户端的消息
		// bytes[:readLen] ==> 只打印有效的数据长度
		fmt.Printf("server[%v]-客户端[%v]说: %v\n", nowTime(), connRemoteAddr, string(bytes[:readLen]))
	}
}
6.3、StartServer方法完整的代码
// StartServer 启动服务端的方法
func (s server) StartServer() {

	// 1. 表示使用的网络协议:tcp
	// 2. 对本机进行监听 ip为 xxx.xxx.xxx.xxx + 端口为 xxx
	listener, err := net.Listen(s.networkType, s.ipAddress)
	if err != nil {
		panic(fmt.Sprintf("服务器启动失败, err = %v \n", err))
	}

	fmt.Printf("服务器端启动成功, %v , time: %v \n", listener.Addr().String(), nowTime())

	// 服务器不断的接收客户端的信息
	for {
		conn, err := listener.Accept()
		// 客户端的信息
		remoteAddr := conn.RemoteAddr()
		if err != nil {
			fmt.Printf("客户端[%v]: %v 链接服务器端出现异常, err = %v \n", nowTime(), remoteAddr.String(), err)
			// 跳过此客户端链接不在往下执行
			continue
		}
		fmt.Printf("客户端[%v]: %v 链接服务器端成功 \n", nowTime(), remoteAddr.String())
		// 处理客户端的消息..
		// 有多个客户端链接,就有多少个协程
		go process(conn)
	}
}
7、编写客户端发送消息代码(客户端)
7.1、注意这里的客户端有了小改动,包括结构体

代码如下

package client

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

type client struct {
	// 网络协议类型
	networkType string
	// ip地址 + 端口
	ipAddress string
	// 当前的链接
	conn net.Conn
}

// NewClient 通过此函数可以得到一个 client 结构体
func NewClient(ipAddress string) client {
	return client{
		networkType: "tcp",
		ipAddress:   ipAddress,
	}
}

// 获取当前运行的时间
func nowTime() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// StartClient 客户端启动方法
func (c *client) StartClient() {
	conn, err := net.Dial(c.networkType, c.ipAddress)
	if err != nil {
		fmt.Printf("链接服务器端: %v ,time = %v ,出现异常 err = %v\n", c.ipAddress, nowTime(), err)
		return
	}
	fmt.Printf("[%v]链接服务器端:[%v]成功\n", nowTime(), conn)
	c.conn = conn
}
7.2 客户端发送消息的方法编写
// SendMessage 发送消息
func (c *client) SendMessage(msg string) {
	writeLen, err := c.conn.Write([]byte(msg))
	if err != nil {
		fmt.Printf("[%v]客户端发送消息至服务器失败, err = %v\n", nowTime(), err)
		return
	}
	fmt.Printf("[%v]客户发送长度为[%d]-[%v]消息成功\n", nowTime(), writeLen, msg)
}
8、客户端链接服务器端(客户端)
package main

import (
	"tcp_socket_demo/client"
)

func main() {
	client := client.NewClient("127.0.0.1:8088")
	client.StartClient()
	client.SendMessage("你好啊,服务器,我是A客户端")
}
9、重新编译使客户端与服务器端进行交互
9.1、服务器端启动效果

在这里插入图片描述

9.2、客户端A向服务器端发送消息

在这里插入图片描述

9.3、服务器端接收到客户端的消息

在这里插入图片描述

以上示例完整的服务器端实现代码

package server

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

type server struct {
	// 网络协议类型
	networkType string
	// ip地址 + 端口
	ipAddress string
}

// NewServer 通过此函数可以得到一个 server 结构体
func NewServer(ipAddress string) server {
	if ipAddress == "" {
		ipAddress = "127.0.0.1:8088"
	}
	return server{
		// 默认值 -- 方便开发
		networkType: "tcp",
		ipAddress:   ipAddress,
	}
}

// StartServer 启动服务端的方法
func (s server) StartServer() {

	// 1. 表示使用的网络协议:tcp
	// 2. 对本机进行监听 ip为 xxx.xxx.xxx.xxx + 端口为 xxx
	listener, err := net.Listen(s.networkType, s.ipAddress)
	if err != nil {
		panic(fmt.Sprintf("服务器启动失败, err = %v \n", err))
	}

	fmt.Printf("服务器端启动成功, %v , time: %v \n", listener.Addr().String(), nowTime())

	// 服务器不断的接收客户端的信息
	for {
		conn, err := listener.Accept()
		// 客户端的信息
		remoteAddr := conn.RemoteAddr()
		if err != nil {
			fmt.Printf("客户端[%v]: %v 链接服务器端出现异常, err = %v \n", nowTime(), remoteAddr.String(), err)
			// 跳过此客户端链接不在往下执行
			continue
		}
		fmt.Printf("客户端[%v]: %v 链接服务器端成功 \n", nowTime(), remoteAddr.String())
		// 处理客户端的消息..
		// 有多个客户端链接,就有多少个协程
		go process(conn)
	}
}

// 获取当前运行的时间
func nowTime() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// 用于处理客户端消息的函数
func process(conn net.Conn) {
	// 客户端断开链接后要及时关闭
	defer conn.Close()
	connRemoteAddr := conn.RemoteAddr().String()
	// 循环读取客户端的消息
	for {
		// 创建一个1024长度的 byte 切片用于存储客户端的消息
		bytes := make([]byte, 1024)
		// 等待客户端通过 conn 发送消息
		// 如果客户端一直没有发送,那么此协程就阻塞在这里
		readLen, err := conn.Read(bytes)
		if err != nil {
			errStr := err.Error()
			contains := strings.Contains(errStr, "An existing connection was forcibly closed by the remote host")
			if contains || errStr == "EOF" {
				fmt.Printf("客户端[%v]: %v 断开链接\n", nowTime(), connRemoteAddr)
				return
			} else {
				fmt.Printf("服务器读取客户端[%v]: %v 消息出现异常, err = %v \n", nowTime(), connRemoteAddr, err)
				return
			}
		}
		// 服务器端显示客户端的消息
		// bytes[:readLen] ==> 只打印有效的数据长度
		fmt.Printf("server[%v]-客户端[%v]说: %v\n", nowTime(), connRemoteAddr, string(bytes[:readLen]))
	}
}

以上示例完整的客户端实现代码

package client

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

type client struct {
	// 网络协议类型
	networkType string
	// ip地址 + 端口
	ipAddress string
	// 当前的链接
	conn net.Conn
}

// NewClient 通过此函数可以得到一个 client 结构体
func NewClient(ipAddress string) client {
	return client{
		networkType: "tcp",
		ipAddress:   ipAddress,
	}
}

// 获取当前运行的时间
func nowTime() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// StartClient 客户端启动方法
func (c *client) StartClient() {
	conn, err := net.Dial(c.networkType, c.ipAddress)
	if err != nil {
		fmt.Printf("链接服务器端: %v ,time = %v ,出现异常 err = %v\n", c.ipAddress, nowTime(), err)
		return
	}
	fmt.Printf("[%v]链接服务器端:[%v]成功\n", nowTime(), conn.RemoteAddr().String())
	c.conn = conn
}

// SendMessage 发送消息
func (c *client) SendMessage(msg string) {
	writeLen, err := c.conn.Write([]byte(msg))
	if err != nil {
		fmt.Printf("[%v]客户端发送消息至服务器失败, err = %v\n", nowTime(), err)
		return
	}
	fmt.Printf("[%v]客户发送长度为[%d]-[%v]消息成功\n", nowTime(), writeLen, msg)
}

客户端改造

客户端保持链接,可以不断发送消息,直到自已选择退出客户端

func main() {
	client := client.NewClient("127.0.0.1:8088")
	client.StartClient()
	for {
		fmt.Printf("请输入要发送的内容(回车即可发送,退出请输入exit): ")
		// 标准输入
		reader := bufio.NewReader(os.Stdin)
		// 回车代表输入结束
		readString, err := reader.ReadString('\n')
		if err != nil {
			fmt.Printf("终端输入出现错误,请重新输入, err = %v\n", err)
			continue
		}
		if strings.TrimSpace(readString) == "exit" {
			break
		}
		client.SendMessage(readString)
	}
}
效果如下

两个客户端
在这里插入图片描述
服务端
在这里插入图片描述
两个客户端进行交互
在这里插入图片描述
客户端均下线,服务端的效果
在这里插入图片描述

代码地址

https://github.com/oukele/go_tcp_socket_demo

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值