前言
在学习完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什么都没有返回,这是因为在循环执行到1conn.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 37package 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:开启一个协程