【go語言 socket程式設計系列】從單執行緒到簡單多執行緒的服務端搭建
簡單單執行緒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