此问题与go及其网络包装有关。
我写了一个简单的tcp服务器来处理一些RPC。客户端正在使用chan net.Conn
来管理客户端上的所有tcp连接。服务器正在使用TCP侦听器运行。
这是代码: 客户:
package server
import (
"errors"
"log"
"net"
)
var tcpPool chan net.Conn
func NewClient(connections int, address string) {
tcpPool = make(chan net.Conn, connections)
for i := 0; i < connections; i++ {
conn, err := net.Dial("tcp4", address)
if err != nil {
log.Panic(err)
}
tcpPool <- conn
}
}
func SendMessage(msg []byte) ([]byte, error) {
conn := getConn()
log.Println("check conn: ", conn)
log.Println("msg: ", msg)
defer releaseConn(conn)
// send message
n, err := conn.Write(msg)
if err != nil {
log.Panic(err)
} else if n < len(msg) {
log.Panic(errors.New("Message did not send in full"))
}
// receiving a message
inBytes := make([]byte, 0)
for {
// bufsize 1024, read bufsize bytes each time
b := make([]byte, bufSize)
res, err := conn.Read(b)
log.Println("server sends >>>>>>>>>>>>: ", res)
if err != nil {
b[0] = ReError
break
}
inBytes = append(inBytes, b[:res]...)
// message finished.
if res < bufSize {
break
}
}
// check replied message
if len(inBytes) == 0 {
return []byte{}, errors.New("empty buffer error")
}
log.Println("SendMessage gets: ", inBytes)
return inBytes, nil
}
func releaseConn(conn net.Conn) error {
log.Println("return conn to pool")
select {
case tcpPool <- conn:
return nil
}
}
func getConn() (conn net.Conn) {
log.Println("Take one from pool")
select {
case conn := <-tcpPool:
return conn
}
}
服务器
func StartTCPServer(network, addr string) error {
listener, err := net.Listen(network, addr)
if err != nil {
return errors.Wrapf(err, "Unable to listen on address %s\n", addr)
}
log.Println("Listen on", listener.Addr().String())
defer listener.Close()
for {
log.Println("Accept a connection request.")
conn, err := listener.Accept()
if err != nil {
log.Println("Failed accepting a connection request:", err)
continue
}
log.Println("Handle incoming messages.")
go onConn(conn)
}
}
//onConn recieves a tcp connection and waiting for incoming messages
func onConn(conn net.Conn) {
inBytes := make([]byte, 0)
defer func() {
if e := recover(); e != nil {
//later log
if err, ok := e.(error); ok {
println("recover", err.Error())
}
}
conn.Close()
}()
// load msg
for {
buf := make([]byte, bufSize)
res, err := conn.Read(buf)
log.Println("server reading: ", res)
inBytes = append(inBytes, buf[:res]...)
if err != nil || res < bufSize {
break
}
}
var req RPCRequest
err := json.Unmarshal(inBytes, &req)
if err != nil {
log.Panic(err)
}
log.Println("rpc request: ", req)
var query UserRequest
err = json.Unmarshal(req.Query, &query)
if err != nil {
log.Panic(err)
}
log.Println("rpc request query: ", query)
// call method to process request
// good now we can proceed to function call
// some actual function calls gets a output
// outBytes, err := json.Marshal(out)
conn.Write(outBytes)
}
我认为这是非常标准的。但是由于某种原因,我只能在客户端之一发送消息,然后第二和第三开始出现一些不正常情况。
1st --->成功,得到回应
第二个--->客户端可以发送,但是什么也没回来,服务器端的日志在即将到来的消息中没有显示
第三--->如果我再从客户端发送一次,则显示broken pipe
错误。
答案 0 :(得分:2)
有一些不好的处理方式。 首先,确保来自服务器的消息完成的标志取决于io.EOF,而不取决于长度
// message finished.
if res < 512 {
break
}
相反,阅读器返回io.EOF
是显示消息完成的唯一符号。
其次,chan type
具有要阻止的属性,不需要使用select。顺便说一句,您确实需要启动goroutine才能释放。对getConn的要求相同
func releaseConn(conn net.Conn) {
go func(){
tcpPool <- conn
}()
}
func getConn() net.Conn {
con := <-tcpPool
return con
}
第三,监听器不应关闭,下面的代码不正确
defer listener.Close()
最重要的原因是
在客户端,
res, err := conn.Read(b)
会收到服务器的回复。
当没有响应时,它阻止而不是io.EOF,也不会出现其他错误。
这意味着,您不能将持久的通讯部件装到函数send()中。
您可以做一件事来使用sendmsg()发送,但绝不要使用sendmsg()来处理回复。
您可以像这样处理回复
var receive chan string
func init() {
receive = make(chan string, 10)
}
func ReceiveMessage(con net.Conn) {
// receiving a message
inBytes := make([]byte, 0, 1000)
var b = make([]byte, 512)
for {
// bufsize 1024, read bufsize bytes each time
res, err := con.Read(b)
if err != nil {
if err == io.EOF {
break
}
fmt.Println(err.Error())
break
}
inBytes = append(inBytes, b[:res]...)
msg := string(inBytes)
fmt.Println("receive msg from server:" + msg)
receive <- msg
}
}
我在您的代码中发现了几个问题,但我无法确定是哪个导致您的失败。 这是我的代码,根据您编写的内容进行了一些修复。 client.go:
package main
import (
"fmt"
"io"
"log"
"net"
)
var tcpPool chan net.Conn
var receive chan string
func init() {
receive = make(chan string, 10)
}
func NewClient(connections int, address string) {
tcpPool = make(chan net.Conn, connections)
for i := 0; i < connections; i++ {
conn, err := net.Dial("tcp", address)
if err != nil {
log.Panic(err)
}
tcpPool <- conn
}
}
func SendMessage(con net.Conn, msg []byte) error {
// send message
_, err := con.Write(msg)
if err != nil {
log.Panic(err)
}
return nil
}
func ReceiveMessage(con net.Conn) {
// receiving a message
inBytes := make([]byte, 0, 1000)
var b = make([]byte, 512)
for {
// bufsize 1024, read bufsize bytes each time
res, err := con.Read(b)
if err != nil {
if err == io.EOF {
break
}
fmt.Println(err.Error())
break
}
inBytes = append(inBytes, b[:res]...)
msg := string(inBytes)
fmt.Println("receive msg from server:" + msg)
receive <- msg
}
}
func getConn() net.Conn {
con := <-tcpPool
return con
}
func main() {
NewClient(20, "localhost:8101")
con := <-tcpPool
e := SendMessage(con, []byte("hello, i am client"))
if e != nil {
fmt.Println(e.Error())
return
}
go ReceiveMessage(con)
var msg string
for {
select {
case msg = <-receive:
fmt.Println(msg)
}
}
}
server.go
package main
import (
"fmt"
"io"
"net"
)
func StartTCPServer(network, addr string) error {
listener, err := net.Listen(network, addr)
if err != nil {
return err
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err.Error())
continue
}
onConn(conn)
}
}
//onConn recieves a tcp connection and waiting for incoming messages
func onConn(conn net.Conn) {
inBytes := make([]byte, 0)
// load msg
for {
buf := make([]byte, 512)
res, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
return
}
fmt.Println(err.Error())
return
}
inBytes = append(inBytes, buf[:res]...)
fmt.Println("receive from client:" + string(inBytes))
conn.Write([]byte("hello"))
}
}
func main() {
if e := StartTCPServer("tcp", ":8101"); e != nil {
fmt.Println(e.Error())
return
}
}
这有效,没有错误。 顺便说一句,我看不到con.Close()在客户端还是服务器端。必须关闭它。这意味着一旦从池中获得连接,您就不会将其放回去。当您认为连接已结束时,请关闭它并建立一个新连接以填充池而不是放回池中,因为将闭合的con放回池中是致命的操作。