1. 程式人生 > >【go語言 socket程式設計系列】從單執行緒到簡單多執行緒的服務端搭建

【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