【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
 

猜你喜欢

转载自blog.csdn.net/natpan/article/details/82895958