使用Tenlet回音器示例展示TCP服务器的基本结构,回音器服务器代码主要分为四个部分分别是接受连接、会话处理、
Tenlet命令处理和程序入口。
接受连接
回音服务器能同时服务于多个连接,要接受连接就要创建侦听器,侦听器需要一个侦听地址和协议类型。一个服务器可以开启多个侦听器。
telnet服务器处理代码
package main
import(
"fmt"
"net"
)
//根据逻辑传入地址和退出通道
func server(address string, exitChan chan int) {
//根据给定地址进行侦听
l,err := net.Listen("tcp",address)
//如果侦听发生错误,打印错误报告并退出
if err != nil {
fmt.Println(err.Error())
exitChan <- 1
}
//打印侦听地址,表示侦听成功
fmt.Println("listen"+address)
//defer延迟关闭侦听器
defer l.Close()
//循环侦听
for {
//新连接没进来时, Accept是阻塞
conn,err := l.Accept()
//发现任何侦听错误都进行报告打印
if err != nil {
fmt.Println(err.Error())
continue
}
//根据连接开启会话,这个过程需要并发执行
go handleSession(conn,exitChan)
}
}
第七行接受连接的入口,传入地址信息参数和退出服务用的通道参数
第九行使用net包的Listen()函数对传入的地址进行侦听。
第十一到十三行如果侦听发生错误打印错误报告,并使用通道写入非零值退出服务器。
第十六行表示侦听成功,打印侦听地址
第十八行使用defer关键字将服务器延迟退出(知道函数结束退出)
第二十行循环侦听
第二十二行服务器接受一个连接,在没有连接时,Accept函数会一直阻塞,直到连接到来。
第二十四到第二十六行发生任何侦听错误都打印并跳过这次循环。
第二十九行每个连接都会产生一个会话,会话的处理与接受逻辑需要并发执行。
会话处理
每个连接的会话就是一个接收数据的循环。当没有数据时,调用reader.ReadString会发生阻塞,等待数据的到来。一旦数据到来,就可以进行各种逻辑处理。
Telnet会话处理代码
package main
import(
"bufio"
"fmt"
"net"
"strings"
)
//连接的会话逻辑
func handleSession(conn net.Conn, exitChan chan int) {
fmt.Println("Session start:")
//创建一个网络连接数据的读取器
reader := bufio.NewReader(conn)
//接收数据的循环
for {
//读取字符串直到碰到回车返回
str,err := reader.ReadString('/n')
//数据读取正确
if err == nil {
//去掉字符串尾部的回车
str = strings.TrimSpace(str)
//处理Telnet命令
if !processTelnetCommand(str,exitChan) {
conn.Close()
break
}
//Echo 逻辑,返回接收的数据
conn.Write([]byte(str+"\r\n"))
} else {
//发生错误
fmt.Println("Session closed")
conn.Close()
break
}
}
}
第九行会话入口,传入连接和退出用的通道
第十二行使用bufio包的NewReader()方法创建一个网络数据读取器,这个Reader将输入数据的读取过程进行封装,方便我们快速获取到需要的数据。
第十四行会话处理开始,从Socket连接,通过reader读取器读取封包,处理封包后需要继续读取从网络发送过来的下一个封包,因此需要一个会话处理循环。
第十六行使用reader.ReadString()方法进行封包读取。内部会自动处理粘包过程,直到下一个回车符到达后返回数据。这里认为封包来自Telnet,每个指令以回车换行符(“\r\n”)结尾。
第十八行数据读取正常时返回err为nil,如果发生连接断开或者接收错误等网络错误时err就会返回信息。
第二十行使用strings包下的TrimSpace()函数处理数据尾部的回车和空白符。
第二十二行将str字符串传入Telnet指令处理函数processTelnetCommand()中,同时传入退出控制通道exitChan。当这个函数返回false时,表示需要关闭当前连接。
第二十七行将有效数据通过conn的Write()方法写入,同时在字符串尾部添加回车符(“\r\n”),数据将会被Socket发送给连接方。
第二十八到第三十四行,当reader.ReadString()发生错误时,打印错误并关闭连接。
Telnet命令处理
Telnet是一种协议,在操作系统上可以在命令行使用Telnet命令发起TCP连接。
Telnet命令处理代码
package main
import(
"fmt"
"strings"
)
func processTelnetCommand(str string, exitChan chan int) bool {
//@close指令表示终止本次对话
if strings.HasPrefix(str, "@close") {
fmt.Println("Session closed")
//告诉外部需要断开连接
return false
}
//@shutdown指令表示终止服务进程
else if strings.HasPrefix(str,"@shutdown") {
fmt.Println("Session shutdown")
//往通道中写入0阻塞等待接收方处理
exitChan <- 0
return false
}
//打印输出字符串
fmt.Println(str)
return true
}
第六行,处理Telnet命令的函数入口,传入有效字符串并退出通道。
第八到十一行,当输入字符串包含“@close“前缀时,在第十一行返回false,表示需要关闭当前会话。
第十四到第十八行当输入字符串包含”@shutdown“前缀时,第十七行向通道中输入0表示结束服务器。
第二十一行,没有特殊的控制字符时,打印输入的字符串。
程序入口
package main
import "os"
func main(){
//创建无缓冲阻塞通道
exitChan := make(chan int)
//将服务器并发运行
go server("127.0.0.1:7001",exitChan)
//通道阻塞等待接收返回值
code := <- exitChan
//标记程序返回值并退出
os.Exit(code)
}