简单单线程serverdemo
通过下面代码简单搭建一个服务端,并通过telnet模拟客户端,演示多客户端同时请求访问单线程服务器时的阻塞现象。
package main
import (
"fmt"
"net"
"os"
)
func main() {
service := ":2001"
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
mylistener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := mylistener.Accept()
if err != nil {
continue
}
handleRequest(conn)
conn.Close()
}
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error :", err.Error())
os.Exit(1)
}
}
func handleRequest(conn net.Conn) {
var mybuff [512]byte
for {
n, err := conn.Read(mybuff[0:])
if err != nil {
return
}
fmt.Println(string(mybuff[0:]))
fmt.Println("localaddr is:", conn.LocalAddr())
fmt.Println("remoteaddr is:", conn.RemoteAddr())
fmt.Println("##########")
_, err2 := conn.Write(mybuff[0:n])
if err2 != nil {
return
}
}
}
func checckError(err error) {
if err != nil {
fmt.Println("Fatal err:", err.Error())
os.Exit(1)
}
}
打开两个终端、通过telnet测试。第一个先连接的 可以正常通信,第二telnet则发生阻塞
第一个telent,输入后有信息返回:
Connection closed by foreign host.
c$telnet localhost 2001
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
11
11
111
111
第二个telnet,发生阻塞,如下
c2$telnet localhost 2001
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
22
222
此时服务端的屏幕打印如下:
$./l_EchoServer
11
localaddr is: 127.0.0.1:2001
remoteaddr is: 127.0.0.1:39889
##########
111
localaddr is: 127.0.0.1:2001
remoteaddr is: 127.0.0.1:39889
##########
退出第一个telnet 后,服务端开始处理第二个telnet的请求,服务端屏幕输出如下。
单telnet连接的情况下,输出一个信息后打印 localaddr和remoteaddr,阻塞后则 合并处理了 请求,只打印了一次。
$./l_EchoServer
11
localaddr is: 127.0.0.1:2001
remoteaddr is: 127.0.0.1:39889
##########
111
localaddr is: 127.0.0.1:2001
remoteaddr is: 127.0.0.1:39889
##########
22
222
localaddr is: 127.0.0.1:2001
remoteaddr is: 127.0.0.1:39896
##########
第一个telnet退出后,第二个telnet的请求得到处理,打印如下
c2$telnet localhost 2001
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
22
222
22
222
【简单多线程】
上面出现单线程的主要问题是 main函数的 监听部分,
func main() {
//略
for {
conn, err := mylistener.Accept()
if err != nil {
continue
}
handleRequest(conn)
conn.Close()
}
}
即Accept处理一个请求后,会调用handleRequest 来处理,这个时候 main函数把cpu控制权交给handleRequest,等待其返回。
我们是用telnet模拟客户端建立连接的, handleRequest的实现代码如下
func handleRequest(conn net.Conn) {
var mybuff [5]byte
for {
n, err := conn.Read(mybuff[0:])
if err != nil {
return
}
fmt.Println(string(mybuff[0:]))
fmt.Println("localaddr is:", conn.LocalAddr())
fmt.Println("remoteaddr is:", conn.RemoteAddr())
fmt.Println("##########")
_, err2 := conn.Write(mybuff[0:n])
if err2 != nil {
return
}
}
}
通过一个for循环来持续从conn中读取数据,打印到屏幕 并把读取到的数据 重新写入conn,返回给telnet。
直到收到终止信号(telnet 中输入 ctr+] 然后close),跳出循环,结束handleRequest,并把控制权返回给main。
使用for循环的主要目的是只有客户端(telnet 发送的close命令)主动结束后,服务端才处理下个命令。
可以在服务端的 位置增加新的 调试信息
handleRequest(conn)
tmPrintln(“handleRequest return”)
conn.Close()
通过 go的 go命令 并把connClose() 放到handleRequest 内部 即可。
并通过defer 来在handleRequest即将执行完 后关闭conn。
完整代码如下
通过计数器 i 打印处理的线程数,
package main
import (
"fmt"
"net"
"os"
)
func main() {
service := ":2002"
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
mylistener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
i := 0
for {
conn, err1 := mylistener.Accept()
if err1 != nil {
continue
}
go handleReq(conn)
i++
fmt.Println("new request :", i)
}
}
func handleReq(conn net.Conn) {
defer conn.Close()
remoteAddr := conn.RemoteAddr()
fmt.Println("new req from ", remoteAddr)
//var mybuf [512]byte
var mybuf [4000]byte
for {
n, err := conn.Read(mybuf[0:])
if err != nil {
return
}
_, err1 := conn.Write(mybuf[0:n])
if err1 != nil {
return
}
}
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
通过多个终端 用telnet 连接后,打印信息如下
new request : 1
new req from 127.0.0.1:43063
data from: 127.0.0.1:43063 --> 111 from clent 1
new request : 2
new req from 127.0.0.1:43078
data from: 127.0.0.1:43078 --> 222 form clent 2
data from: 127.0.0.1:43063 --> newa;
clent 1
data from: 127.0.0.1:43078 --> sdflj sd
lent 2