前言
在学习完Go
语言之后,总是感觉没有合适的上手项目进行练习,最近正好看到一个TCP网络聊天室
的小项目,这个项目只使用基础的包而不使用任何框架,非常适合练手。
需要的工具有
Go
开发环境nc
工具,方便模拟client
进行测试
建立连接
在Go
中,我们可以使用net
包来进行基本的server
的socket
的创建,也就是net.Listen
方法
|
|
下面是这个函数的原型
|
|
可以看到,函数的两个参数都是string
类型的,第一个参数指定的是通信的网络(可以直接指定tcp), 第二个是server
的地址。返回值就是监听对象和err
例如,我们想要启动一个监听,就可以这样写
|
|
就创建好了一个scoket
在得到listener
后,要开启接受功能,可以调用
|
|
同样,这个函数有两个返回值,正常使用是这样的
|
|
其中,返回的是一个net.Conn
类型的参数,可以通过conn
在socket
之间传递数据
|
|
conn.Read
可以从连接中读取数据(server
可以read
来自client
的数据), 同时conn.Write
可以从连接中发送数据(server
向client
发送数据)
这里就实现了通信的基石,即发送和接受数据,其整个过程就是
- 创建
socket
:net.Listen
,返回一个net.Listener
对象 - 开始接收请求:
listener.Accept
,返回一个net.Conn
对象 - 使用
net.Conn
实现接收数据和发送数据
simple demo
这里创建一个简单的demo
程序,在server
接收到来自client
的数据后,把接受到的数据全部转换为大写后发送给client
|
|
然后使用nc
工具
|
|
当我们发送hello
的时候,server
正确的返回了HELLO
思考:当有多个
client
的时候怎么办?
协程处理
只有一个用户创建连接的时候可以正常返回,但此时有多个用户创建了连接请求,由于我们只accept
了一次连接请求,所以当多个用户尝试连接的时候,第二个及之后的那些用户无法与服务器建立连接。
解决办法
-
每次在循环的过程中不断的进行监听,而不是只监听一次。 原始代码是
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// ..... conn, err := listener.Accept() // 把这里添加到循环中 defer conn.Close() for { if err != nil { fmt.Println("connect fail", err) return } // 读取数据 conn.Read(buf) // 写回数据 conn.Write(bytes.ToUpper(buf)) } }
添加到循环后就可以不断的建立连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// ...... for { conn, err := listener.Accept() if err != nil { fmt.Println("connect fail", err) return } // 读取数据 conn.Read(buf) // 写回数据 conn.Write(bytes.ToUpper(buf)) } }
然后再新建
client
的时候就可以处理多用户连接。这样写有什么问题?
可以发现,当在一个
client
发送第二组数据后,server
什么都没有返回,这是因为在循环执行到1
conn.Write(bytes.ToUpper(buf))
server
一直在期待新的链接,而不是去处理之前的client
的数据 -
使用
go
协程在每次
conn
成功后,为了保持后续的链接,可以把后续的read
和write
封装为go
协程1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
package main import ( "bytes" "fmt" "net" ) func Handler(conn net.Conn) { defer conn.Close() buf := make([]byte, 1024) for { cnt, _ := conn.Read(buf) conn.Write(bytes.ToUpper(buf[:cnt])) } } func main() { ip := "127.0.0.1" port := 8080 //CreateServer(ip, port) listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ip, port)) defer listener.Close() if err != nil { fmt.Println("Eror!", err) return } //defer conn.Close() for { conn, err := listener.Accept() if err != nil { fmt.Println("connect fail", err) return } go Handler(conn) } }
也就是说在主函数内,只负责去监听是否有用户链接,而链接后的读写就去创建一个新的协程,在这个协程内根据这个链接不断的去实现
client-server
之间的读写。
总结
net.Listen
:创建tcp socket
, 返回listener
对象listener.Accept
:监听客户端的连接, 返回net.Conn
连接对象net.Conn
:实现read
和write
,读取和发送数据go
:开启一个协程