golang学习笔记(24)-利用net实现简单的tcp连接的并发服务端

本文详细介绍了如何使用Go语言实现一个并发的TCP服务器,包括服务端的监听、连接管理、数据读写以及客户端的连接建立和数据交互。重点展示了服务端如何处理多个并发连接,以及如何通过net包进行字符串转换操作。

利用net实现简单的tcp连接的并发服务器

tcp的c/s框架

在这里插入图片描述

服务端构建

全代码

package main

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

func HandleConn(conn net.Conn) {
   //获取客户端的网络地址信息
   addr := conn.RemoteAddr().String()
   fmt.Println(addr, " connect successful")
   buf := make([]byte, 2048)
   for {

   	len, err := conn.Read(buf)
   	if err != nil {
   		fmt.Println("conn.read=", err)
   		return
   	}
   	len -= 2
   	fmt.Println("buf=", string(buf[:len]))
   	//fmt.Println(len, buf)
   	if string(buf[:len]) == "exit" {
   		exit := []byte(addr)
   		exit = append(exit, "is exit"...)
   		conn.Write(exit)
   		fmt.Println(addr, "exit")
   		return
   	}
   	//处理操作,小写变大写
   	conn.Write([]byte(strings.ToUpper(string(buf[:len]))))
   }
   defer conn.Close()
}

func main() {
   //监听
   listen, err := net.Listen("tcp", "127.0.0.1:8000")
   if err != nil {
   	fmt.Println(err)
   	return
   }
   defer listen.Close()
   //阻塞等待用户连接
   for {
   	conn, err := listen.Accept()
   	if err != nil {
   		fmt.Println(err)
   		continue
   	}
   	//接收用户请求
   	go HandleConn(conn)
   }

}

服务端与客户端建立连接

首先使用net.Listen()方法创建监听器
其中Listen函数

func Listen(network string, address string) (Listener, error)

其中第一个参数为选的tcp类型,第二个参数为地址。
在本地网络地址上监听宣布。
该网络必须是 “tcp”、“tcp4”、“tcp6”、"unix "或 “unixpacket”。
对于TCP网络,如果地址参数中的主机是空的,或者是一个未指定的字面IP地址,监听本地系统所有可用的单播和任播IP地址。要只使用IPv4,使用网络 “tcp4”。地址可以使用主机名,但不推荐这样做,因为它最多只能为主机的一个IP地址创建一个监听器。如果地址参数中的端口为空或 “0”,如 “127.0.0.1: “或”[:1]:0”,会自动选择一个端口号。Listener的Addr方法可以用来发现选择的端口。
Accept方法用于Accept等待并返回下一个连接给监听器。
当掌握这两个操作后就可以实现客户端与服务器的连接了。
例如

func main() {
	listen, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer listen.Close()
	conn, err := listen.Accept()
	if err != nil {
		fmt.Println(err)
		//continue
		return
	}
	addr := conn.RemoteAddr().String()
	fmt.Println(addr, " connect successful")

}

在这里插入图片描述
如果conn不关闭,连接就不会断开。
所有在添加处理操作时,记得defer conn.close()

并发连接

因为Accept()方法在没有连接时会阻塞等待,所有直接给连接操作开for循环,让他持续等待即可。

func main() {
	//监听
	listen, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer listen.Close()
	//阻塞等待用户连接
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println(err)
			continue
		}
		addr := conn.RemoteAddr().String()
		fmt.Println(addr, " connect successful")
		//接收用户请求
		//go HandleConn(conn)
	}

}

在这里插入图片描述
这样就实验成功发现有两个客户端连接服务器。

服务端处理数据

每个不同的conn会根据不同的请求处理不同的数据,所有在等待连接循环中,要开不同的协程


func main() {
	//监听
	listen, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer listen.Close()
	//阻塞等待用户连接
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println(err)
			continue
		}
		//接收用户请求
		go HandleConn(conn)
	}

}

现在来实现HandleConn。
实验想实现,将客户端传来的字符串转化为大写再返回给客户端
所有首先服务端需要读取连接中的参数,这点需要掌握使用Read()函数

Read()
func (Conn) Read(b []byte) (n int, err error)

从连接中读取数据。可以让Read在一个固定的时间限制后超时并返回一个错误;
返回值为读取到的切片长度。
读取到的数据会赋给参数b
所以简单的读取操作则为

buf := make([]byte, 2048)
len, err := conn.Read(buf)
   	if err != nil {
   		fmt.Println("conn.read=", err)
   		return
}
fmt.Println("buf=", string(buf[:len]))

在连接未关闭的情况下Read()会一直等待连接传入信息,
但在连接关闭后Read会直接执行,并且返回err。
所以想要创建持续等待写入操作的话,需要对err经行处理。
例如

buf := make([]byte, 2048)
   for {

   	len, err := conn.Read(buf)
   	if err != nil {
   		fmt.Println("conn.read=", err)
   		return
   	}
   	fmt.Println("buf=", string(buf[:len]))
   }
   defer conn.Close()

这样就实现了简单的等待信息传入的功能
连接服务端
在这里插入图片描述
客户端写入
在这里插入图片描述

服务端读取在这里插入图片描述

Write()
func (Conn) Write(b []byte) (n int, err error)

写入将数据写入连接中。可以让Write超时,并在一个固定的时间限制后返回一个错误
现在实现小写转化为大写操作

conn.Write([]byte(strings.ToUpper(string(buf[:len]))))
其他

利用上述方法,就可以实现简单的并发服务器,但在实验中发现,利用os.stdin.Read()方法写入数据传到服务端时,会把回车与换行一起传入,所以在读取后要切掉最后两个元素,
并且可以利用RemoteAddr方法查询到连接来的客户端端口
所以完整客户端代码如下

package main

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

func HandleConn(conn net.Conn) {
	//获取客户端的网络地址信息
	addr := conn.RemoteAddr().String()
	fmt.Println(addr, " connect successful")
	buf := make([]byte, 2048)
	for {

		len, err := conn.Read(buf)
		if err != nil {
			fmt.Println("conn.read=", err)
			return
		}
		len -= 2
		fmt.Println("buf=", string(buf[:len]))
		//fmt.Println(len, buf)
		if string(buf[:len]) == "exit" {
			exit := []byte(addr)
			exit = append(exit, "is exit"...)
			conn.Write(exit)
			fmt.Println(addr, "exit")
			return
		}
		//处理操作,小写变大写
		conn.Write([]byte(strings.ToUpper(string(buf[:len]))))
	}
	defer conn.Close()
}

func main() {
	//监听
	listen, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer listen.Close()
	//阻塞等待用户连接
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println(err)
			continue
		}
		//接收用户请求
		go HandleConn(conn)
	}

}

客户端构建

客户端完整代码

package main

import (
	"fmt"
	"net"
	"os"
)

func main() {
	//主动连接服务器
	conn, err := net.Dial("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()
	//接收服务器信息
	go func() {
		buf := make([]byte, 2045)
		for {
			len, err2 := conn.Read(buf)
			if err2 != nil {
				fmt.Println("conn.Read err=", err)
				break
			}
			fmt.Println(string(buf[:len]))
		}
	}()
	//发送数据
	for {
		buf := make([]byte, 1024)
		n, err := os.Stdin.Read(buf)
		if err != nil {
			fmt.Println("os.Stdin err=", err)
		}
		conn.Write(buf[:n])
	}

}

创立连接

利用net.Dial方法连接客户端

func Dial(network string, address string) (Conn, error)

第一个参数为tcp类型,第二个参数为要访问的服务端地址

Write()

buf := make([]byte, 1024)
   	n, err := os.Stdin.Read(buf)
   	if err != nil {
   		fmt.Println("os.Stdin err=", err)
   	}
   	conn.Write(buf[:n])

其中利用了os.Stdin.Read()方法来读取键盘输入的信息。
== 发现利用fmt中的Scan方法无法读取全部信息,暂时不知道原理,还请大家解答指点 ==

Read()

buf := make([]byte, 2045)
		for {
			len, err2 := conn.Read(buf)
			if err2 != nil {
				fmt.Println("conn.Read err=", err)
				break
			}
			fmt.Println(string(buf[:len]))
		}

其他

因为write与read可以持续并行操作,所以在持续化上,与服务端要注意的操作相同,要对err经行处理,所以整体代码为

package main

import (
	"fmt"
	"net"
)

func main() {
	//主动连接服务器
	conn, err := net.Dial("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()
	//接收服务器信息
	go func() {
		buf := make([]byte, 2045)
		for {
			len, err2 := conn.Read(buf)
			if err2 != nil {
				fmt.Println("conn.Read err=", err)
				break
			}
			fmt.Println(string(buf[:len]))
		}
	}()
	//发送数据
	for {
		buf := make([]byte, 1024)
		fmt.Scan(&buf)
		if err != nil {
			fmt.Println("os.Stdin err=", err)
		}
		conn.Write(buf[0:3])
	}

}

结果测试

实现功能:服务端可以连接多个客户端,服务端将客户端传入字符串转为大写再返回客户端,exit为退出连接字符串。
多客户端连接:
在这里插入图片描述
不同客户端功能暂时
1客户端
在这里插入图片描述
2客户端
在这里插入图片描述
服务端
在这里插入图片描述
客户端断开连接
在这里插入图片描述
再输入
客户端
在这里插入图片描述
服务端
在这里插入图片描述


实验完毕,新人学习,还请大家指出问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值