Golang tcp socket编程案例
简单的介绍
- 项目代码基于 Go mod 方式
- 纯Golang编写
- 基于 golang net 包
- 代码请以最终整理的代码为主
- 本人菜鸡一枚,代码写得不好,望各位大佬口下留情~
步骤代码实现
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)
}
}
效果如下
两个客户端
服务端
两个客户端进行交互
客户端均下线,服务端的效果