服务端构建
全代码
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客户端
服务端
客户端断开连接
再输入
客户端
服务端
实验完毕,新人学习,还请大家指出问题。