├── .gitignore ├── LICENSE ├── README.md ├── doc.go ├── samples ├── chatroom │ ├── miniclient │ │ └── main.go │ ├── protocol │ │ └── protocol.go │ └── server │ │ └── server.go └── tcpping │ ├── client │ └── tcpping.go │ ├── protocol │ └── protocol.go │ └── server │ └── tcpping.go ├── tcpclient.go ├── tcpconn.go ├── tcpserver.go └── tcpsock.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, 无尽愿 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tcpsock 2 | Package tcpsock provides easy to use interfaces for TCP I/O.

3 | 4 | # How to use
5 | ## server: 6 | ```Go 7 | server := tcpsock.NewTcpServer(listenPort, acceptTimeout, onConnConnect, onConnClose, onProtocol) 8 | go server.Serve() 9 | <-shutdown 10 | server.Close() 11 | ``` 12 | ## client: 13 | ```Go 14 | client := tcpsock.NewTcpClient(ServerAddr, onConnect, onClose, onProtocol) 15 | go client.Run() 16 | <-shutdown 17 | client.Close() 18 | ``` 19 | ## There're more detailed demos which use custom binary protocols, like:
20 | * [chatroom](https://github.com/ecofast/tcpsock/tree/master/samples/chatroom)
21 | * [tcpping](https://github.com/ecofast/tcpsock/tree/master/samples/tcpping)
22 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // tcpsock project doc.go 2 | 3 | /* 4 | tcpsock document 5 | */ 6 | package tcpsock 7 | -------------------------------------------------------------------------------- /samples/chatroom/miniclient/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/ecofast/tcpsock" 14 | 15 | . "github.com/ecofast/tcpsock/samples/chatroom/protocol" 16 | ) 17 | 18 | const ( 19 | ServerAddr = "139.129.96.130:9999" 20 | 21 | charTableLen = 62 22 | charTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 23 | ) 24 | 25 | var ( 26 | shutdown = make(chan bool, 1) 27 | 28 | tcpConn *tcpsock.TcpConn 29 | userName string 30 | ) 31 | 32 | func init() { 33 | rand.Seed(time.Now().UnixNano()) 34 | 35 | signals := make(chan os.Signal, 1) 36 | signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) 37 | go func() { 38 | <-signals 39 | shutdown <- true 40 | }() 41 | } 42 | 43 | func main() { 44 | genUserName() 45 | 46 | client := tcpsock.NewTcpClient(ServerAddr, onConnect, onClose, onProtocol) 47 | go client.Run() 48 | go input() 49 | <-shutdown 50 | client.Close() 51 | } 52 | 53 | func onProtocol() tcpsock.Protocol { 54 | proto := &ChatProtocol{} 55 | proto.OnMessage(onMsg) 56 | return proto 57 | } 58 | 59 | func onConnect(c *tcpsock.TcpConn) { 60 | log.Println("successfully connect to server", c.RawConn().RemoteAddr().String()) 61 | tcpConn = c 62 | } 63 | 64 | func onClose(c *tcpsock.TcpConn) { 65 | log.Println("disconnect from server", c.RawConn().RemoteAddr().String()) 66 | tcpConn = nil 67 | } 68 | 69 | func onMsg(c *tcpsock.TcpConn, p *ChatPacket) { 70 | fmt.Println(p) 71 | } 72 | 73 | func genUserName() { 74 | var buf bytes.Buffer 75 | for i := 0; i < 8; i++ { 76 | buf.WriteByte(charTable[rand.Intn(charTableLen)]) 77 | } 78 | userName = buf.String() 79 | 80 | fmt.Println("your random name is:", userName) 81 | } 82 | 83 | func input() { 84 | s := "" 85 | for { 86 | if n, err := fmt.Scan(&s); n == 0 || err != nil { 87 | break 88 | } 89 | if tcpConn == nil { 90 | break 91 | } 92 | tcpConn.Write(genPacket(s)) 93 | } 94 | } 95 | 96 | func genPacket(s string) *ChatPacket { 97 | var head PacketHead 98 | head.Signature = ChatSignature 99 | copy(head.UserName[:], []byte(userName)) 100 | body := []byte(s) 101 | head.BodyLen = uint32(len(body)) 102 | copy(body[:], body[:]) 103 | return NewChatPacket(head, body) 104 | } 105 | -------------------------------------------------------------------------------- /samples/chatroom/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/ecofast/tcpsock" 10 | 11 | . "github.com/ecofast/rtl/timeutils" 12 | ) 13 | 14 | const ( 15 | ChatSignature = 0xFFFFFFFF 16 | MaxUserNameLen = 8 17 | PacketHeadSize = 4 + MaxUserNameLen + 4 18 | ) 19 | 20 | type PacketHead struct { 21 | Signature uint32 22 | UserName [MaxUserNameLen]byte 23 | BodyLen uint32 24 | } 25 | 26 | func (head *PacketHead) Bytes() []byte { 27 | var buf bytes.Buffer 28 | binary.Write(&buf, binary.LittleEndian, head) 29 | return buf.Bytes() 30 | } 31 | 32 | type ChatPacket struct { 33 | PacketHead 34 | Body []byte 35 | } 36 | 37 | func NewChatPacket(head PacketHead, body []byte) *ChatPacket { 38 | return &ChatPacket{ 39 | PacketHead: head, 40 | Body: body, 41 | } 42 | } 43 | 44 | func (p *ChatPacket) Marshal() []byte { 45 | buf := make([]byte, PacketHeadSize+len(p.Body)) 46 | copy(buf[:PacketHeadSize], p.PacketHead.Bytes()[:]) 47 | copy(buf[PacketHeadSize:], p.Body[:]) 48 | return buf 49 | } 50 | 51 | func (self *ChatPacket) String() string { 52 | return fmt.Sprintf("%s %s: %s", DateTimeToStr(time.Now()), string(self.UserName[:]), string(self.Body)) 53 | } 54 | 55 | type ChatProtocol struct { 56 | recvBuf []byte 57 | recvBufLen int 58 | onMsg func(c *tcpsock.TcpConn, p *ChatPacket) 59 | } 60 | 61 | func (self *ChatProtocol) Parse(b []byte, recvChan chan<- tcpsock.Packet) { 62 | count := len(b) 63 | if count+self.recvBufLen > tcpsock.RecvBufLenMax { 64 | return 65 | } 66 | 67 | self.recvBuf = append(self.recvBuf, b[0:count]...) 68 | self.recvBufLen += count 69 | offsize := 0 70 | offset := 0 71 | var head PacketHead 72 | for self.recvBufLen-offsize > PacketHeadSize { 73 | offset = 0 74 | head.Signature = uint32(uint32(self.recvBuf[offsize+3])<<24 | uint32(self.recvBuf[offsize+2])<<16 | uint32(self.recvBuf[offsize+1])<<8 | uint32(self.recvBuf[offsize+0])) 75 | offset += 4 76 | copy(head.UserName[:], self.recvBuf[offsize+offset:offsize+offset+MaxUserNameLen]) 77 | offset += MaxUserNameLen 78 | head.BodyLen = uint32(uint32(self.recvBuf[offsize+offset+3])<<24 | uint32(self.recvBuf[offsize+offset+2])<<16 | uint32(self.recvBuf[offsize+offset+1])<<8 | uint32(self.recvBuf[offsize+offset+0])) 79 | offset += 4 80 | if head.Signature == ChatSignature { 81 | pkglen := int(PacketHeadSize + head.BodyLen) 82 | if pkglen >= tcpsock.RecvBufLenMax { 83 | offsize = self.recvBufLen 84 | break 85 | } 86 | if offsize+pkglen > self.recvBufLen { 87 | break 88 | } 89 | 90 | recvChan <- NewChatPacket(head, self.recvBuf[offsize+offset : offsize+offset+int(head.BodyLen)][:]) 91 | offsize += pkglen 92 | } else { 93 | offsize++ 94 | } 95 | } 96 | 97 | self.recvBufLen -= offsize 98 | if self.recvBufLen > 0 { 99 | self.recvBuf = self.recvBuf[offsize : offsize+self.recvBufLen] 100 | } else { 101 | self.recvBuf = nil 102 | } 103 | } 104 | 105 | func (self *ChatProtocol) Process(conn *tcpsock.TcpConn, p tcpsock.Packet) { 106 | packet := p.(*ChatPacket) 107 | self.onMsg(conn, packet) 108 | } 109 | 110 | func (self *ChatProtocol) OnMessage(fn func(c *tcpsock.TcpConn, p *ChatPacket)) { 111 | self.onMsg = fn 112 | } 113 | -------------------------------------------------------------------------------- /samples/chatroom/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/signal" 8 | "sync" 9 | "syscall" 10 | "time" 11 | 12 | . "github.com/ecofast/rtl/netutils" 13 | 14 | "github.com/ecofast/tcpsock" 15 | 16 | . "github.com/ecofast/tcpsock/samples/chatroom/protocol" 17 | ) 18 | 19 | var ( 20 | shutdown = make(chan bool, 1) 21 | 22 | mutex sync.Mutex 23 | clients map[uint32]*tcpsock.TcpConn 24 | ) 25 | 26 | func init() { 27 | signals := make(chan os.Signal, 1) 28 | signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) 29 | go func() { 30 | <-signals 31 | shutdown <- true 32 | }() 33 | } 34 | 35 | func main() { 36 | clients = make(map[uint32]*tcpsock.TcpConn) 37 | 38 | server := tcpsock.NewTcpServer(9999, 5, onConnConnect, onConnClose, onProtocol) 39 | log.Println("=====service start=====") 40 | go server.Serve() 41 | 42 | ticker := time.NewTicker(2 * time.Minute) 43 | go func() { 44 | for range ticker.C { 45 | //log.Printf("num of conn: %d\n", server.NumOfConn()) 46 | // broadcast() 47 | } 48 | }() 49 | 50 | <-shutdown 51 | log.Println("server shutdown...") 52 | server.Close() 53 | log.Println("=====service end=====") 54 | } 55 | 56 | func onConnConnect(conn *tcpsock.TcpConn) { 57 | conn.Write(genChatPacket("master01", fmt.Sprintf("Welcome! Your IP is %s", IPFromNetAddr(conn.RawConn().RemoteAddr())))) 58 | 59 | mutex.Lock() 60 | defer mutex.Unlock() 61 | clients[conn.ID()] = conn 62 | } 63 | 64 | func onConnClose(conn *tcpsock.TcpConn) { 65 | mutex.Lock() 66 | defer mutex.Unlock() 67 | delete(clients, conn.ID()) 68 | } 69 | 70 | func onMsg(conn *tcpsock.TcpConn, p *ChatPacket) { 71 | fmt.Println(p) 72 | 73 | mutex.Lock() 74 | defer mutex.Unlock() 75 | for _, c := range clients { 76 | c.Write(p) 77 | } 78 | } 79 | 80 | func onProtocol() tcpsock.Protocol { 81 | proto := &ChatProtocol{} 82 | proto.OnMessage(onMsg) 83 | return proto 84 | } 85 | 86 | func genChatPacket(userName, words string) *ChatPacket { 87 | var head PacketHead 88 | head.Signature = ChatSignature 89 | copy(head.UserName[:], ([]byte(userName[:]))[:]) 90 | body := []byte(words) 91 | head.BodyLen = uint32(len(body)) 92 | return NewChatPacket(head, body) 93 | } 94 | 95 | func broadcast() { 96 | mutex.Lock() 97 | defer mutex.Unlock() 98 | packet := genChatPacket("master01", "broadcast test") 99 | for _, c := range clients { 100 | c.Write(packet) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /samples/tcpping/client/tcpping.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | 12 | . "github.com/ecofast/rtl/netutils" 13 | "github.com/ecofast/tcpsock" 14 | . "github.com/ecofast/tcpsock/samples/tcpping/protocol" 15 | ) 16 | 17 | type pingStats struct { 18 | sendNum int 19 | lags []int 20 | } 21 | 22 | var ( 23 | shutdown = make(chan bool, 1) 24 | 25 | tcpConn *tcpsock.TcpConn 26 | packet *PingPacket 27 | 28 | packetLen int = 32 // byte 29 | pingInterval int = 1 // second 30 | pingTimes int = 10 31 | 32 | canPing bool = true 33 | sendTick time.Time 34 | 35 | stats pingStats 36 | ) 37 | 38 | func init() { 39 | signals := make(chan os.Signal, 1) 40 | signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) 41 | go func() { 42 | <-signals 43 | shutdown <- true 44 | }() 45 | } 46 | 47 | func main() { 48 | parseFlag() 49 | genPacket() 50 | 51 | client := tcpsock.NewTcpClient(flag.Args()[0], onConnect, onClose, onProtocol) 52 | go client.Run() 53 | 54 | ticker := time.NewTicker(time.Duration(pingInterval) * time.Second) 55 | go func() { 56 | cnt := 0 57 | for range ticker.C { 58 | if tcpConn != nil && canPing && stats.sendNum < pingTimes { 59 | canPing = false 60 | sendTick = time.Now() 61 | tcpConn.Write(packet) 62 | stats.sendNum++ 63 | } 64 | cnt++ 65 | if cnt > pingTimes { 66 | shutdown <- true 67 | break 68 | } 69 | } 70 | }() 71 | 72 | <-shutdown 73 | ticker.Stop() 74 | client.Close() 75 | } 76 | 77 | func onProtocol() tcpsock.Protocol { 78 | proto := &PingProtocol{} 79 | proto.OnMessage(onMsg) 80 | return proto 81 | } 82 | 83 | func onConnect(c *tcpsock.TcpConn) { 84 | log.Println("successfully connect to server", IPFromNetAddr(c.RawConn().RemoteAddr())) 85 | tcpConn = c 86 | fmt.Printf("TCPPing %s with %d bytes of data...\n", flag.Args()[0], packetLen) 87 | } 88 | 89 | func onClose(c *tcpsock.TcpConn) { 90 | printStats() 91 | log.Println("disconnect from server", IPFromNetAddr(c.RawConn().RemoteAddr())) 92 | tcpConn = nil 93 | } 94 | 95 | func onMsg(c *tcpsock.TcpConn, p *PingPacket) { 96 | canPing = true 97 | lag := int(time.Now().Sub(sendTick) / time.Millisecond) 98 | stats.lags = append(stats.lags, lag) 99 | fmt.Printf("%d bytes from %s: time=%dms\n", packetLen, IPFromNetAddr(c.RawConn().RemoteAddr()), lag) 100 | } 101 | 102 | func parseFlag() { 103 | flag.IntVar(&packetLen, "p", packetLen, "packet length(byte)") 104 | flag.IntVar(&pingInterval, "i", pingInterval, "ping interval(second)") 105 | flag.IntVar(&pingTimes, "t", pingTimes, "Ping times") 106 | flag.Parse() 107 | 108 | if len(flag.Args()) == 0 { 109 | fmt.Println("Usage: tcpping ip:port [arguments]\n") 110 | os.Exit(1) 111 | } 112 | } 113 | 114 | func genPacket() { 115 | packet = &PingPacket{ 116 | BodyLen: uint16(packetLen) - PacketHeadSize, 117 | Body: make([]byte, packetLen), 118 | } 119 | } 120 | 121 | func printStats() { 122 | fmt.Printf("---%s tcpping statistics---\n", flag.Args()[0]) 123 | fmt.Printf("%d packets transmitted.\n", stats.sendNum) 124 | if stats.sendNum > 0 { 125 | sum := 0 126 | min := stats.lags[0] 127 | max := 0 128 | for _, v := range stats.lags { 129 | sum += v 130 | if v < min { 131 | min = v 132 | } 133 | if v > max { 134 | max = v 135 | } 136 | } 137 | fmt.Printf("min/avg/max lag = %d/%d/%d ms\n", min, sum/len(stats.lags), max) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /samples/tcpping/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/ecofast/tcpsock" 7 | ) 8 | 9 | const ( 10 | PacketHeadSize = 2 11 | ) 12 | 13 | type PingPacket struct { 14 | BodyLen uint16 15 | Body []byte 16 | } 17 | 18 | func NewPingPacket(body []byte) *PingPacket { 19 | return &PingPacket{ 20 | BodyLen: uint16(len(body)), 21 | Body: body, 22 | } 23 | } 24 | 25 | func (self *PingPacket) Marshal() []byte { 26 | buf := make([]byte, PacketHeadSize+self.BodyLen) 27 | binary.LittleEndian.PutUint16(buf, self.BodyLen) 28 | copy(buf[PacketHeadSize:], self.Body[:]) 29 | return buf 30 | } 31 | 32 | type PingProtocol struct { 33 | recvBuf []byte 34 | recvBufLen int 35 | onMsg func(c *tcpsock.TcpConn, p *PingPacket) 36 | } 37 | 38 | func (self *PingProtocol) Parse(b []byte, recvChan chan<- tcpsock.Packet) { 39 | count := len(b) 40 | if count+self.recvBufLen > tcpsock.RecvBufLenMax { 41 | return 42 | } 43 | 44 | self.recvBuf = append(self.recvBuf, b[0:count]...) 45 | self.recvBufLen += count 46 | offsize := 0 47 | offset := 0 48 | var pkt PingPacket 49 | for self.recvBufLen-offsize > 2 { 50 | offset = 0 51 | pkt.BodyLen = binary.LittleEndian.Uint16(self.recvBuf[offsize+0 : offsize+2]) 52 | offset += 2 53 | pkglen := int(2 + pkt.BodyLen) 54 | if pkglen >= tcpsock.RecvBufLenMax { 55 | offsize = self.recvBufLen 56 | break 57 | } 58 | if offsize+pkglen > self.recvBufLen { 59 | break 60 | } 61 | 62 | recvChan <- NewPingPacket(self.recvBuf[offsize+offset : offsize+offset+int(pkt.BodyLen)]) 63 | offsize += pkglen 64 | } 65 | 66 | self.recvBufLen -= offsize 67 | if self.recvBufLen > 0 { 68 | self.recvBuf = self.recvBuf[offsize : offsize+self.recvBufLen] 69 | } else { 70 | self.recvBuf = nil 71 | } 72 | } 73 | 74 | func (self *PingProtocol) Process(conn *tcpsock.TcpConn, p tcpsock.Packet) { 75 | packet := p.(*PingPacket) 76 | self.onMsg(conn, packet) 77 | } 78 | 79 | func (self *PingProtocol) OnMessage(fn func(c *tcpsock.TcpConn, p *PingPacket)) { 80 | self.onMsg = fn 81 | } 82 | -------------------------------------------------------------------------------- /samples/tcpping/server/tcpping.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | . "github.com/ecofast/rtl/netutils" 12 | "github.com/ecofast/tcpsock" 13 | . "github.com/ecofast/tcpsock/samples/tcpping/protocol" 14 | ) 15 | 16 | var ( 17 | shutdown = make(chan bool, 1) 18 | 19 | listenPort int = 12345 20 | ) 21 | 22 | func init() { 23 | signals := make(chan os.Signal, 1) 24 | signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) 25 | go func() { 26 | <-signals 27 | shutdown <- true 28 | }() 29 | } 30 | 31 | func parseFlag() { 32 | flag.IntVar(&listenPort, "p", listenPort, "listen port") 33 | flag.Parse() 34 | } 35 | 36 | func main() { 37 | parseFlag() 38 | 39 | fmt.Printf("tcpping listening on port: %d\n", listenPort) 40 | server := tcpsock.NewTcpServer(listenPort, 2, onConnConnect, onConnClose, onProtocol) 41 | log.Println("=====service start=====") 42 | go server.Serve() 43 | 44 | <-shutdown 45 | log.Println("shutdown server") 46 | server.Close() 47 | log.Println("=====service stop=====") 48 | } 49 | 50 | func onConnConnect(conn *tcpsock.TcpConn) { 51 | log.Printf("accept connection from %s\n", IPFromNetAddr(conn.RawConn().RemoteAddr())) 52 | } 53 | 54 | func onConnClose(conn *tcpsock.TcpConn) { 55 | log.Printf("connection closed from %s\n", IPFromNetAddr(conn.RawConn().RemoteAddr())) 56 | } 57 | 58 | func onMsg(conn *tcpsock.TcpConn, p *PingPacket) { 59 | log.Printf("recved ping message from %s with %d bytes of data\n", IPFromNetAddr(conn.RawConn().RemoteAddr()), PacketHeadSize+p.BodyLen) 60 | conn.Write(p) 61 | } 62 | 63 | func onProtocol() tcpsock.Protocol { 64 | proto := &PingProtocol{} 65 | proto.OnMessage(onMsg) 66 | return proto 67 | } 68 | -------------------------------------------------------------------------------- /tcpclient.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 ecofast(胡光耀). All rights reserved. 2 | // Use of this source code is governed by a BSD-style license. 3 | 4 | package tcpsock 5 | 6 | import ( 7 | "net" 8 | "sync" 9 | 10 | . "github.com/ecofast/rtl/sysutils" 11 | ) 12 | 13 | type TcpClient struct { 14 | svrAddr *net.TCPAddr 15 | *tcpSock 16 | } 17 | 18 | func NewTcpClient(svrAddr string, onConnConnect, onConnClose OnTcpConnCallback, onCustomProtocol OnTcpCustomProtocol) *TcpClient { 19 | tcpAddr, err := net.ResolveTCPAddr("tcp", svrAddr) 20 | CheckError(err) 21 | 22 | if onCustomProtocol == nil { 23 | panic("tcpsock.NewTcpClient: invalid custom protocol") 24 | } 25 | 26 | return &TcpClient{ 27 | svrAddr: tcpAddr, 28 | tcpSock: &tcpSock{ 29 | sendBufCap: SendBufCapMax, 30 | recvBufCap: RecvBufCapMax, 31 | exitChan: make(chan struct{}), 32 | waitGroup: &sync.WaitGroup{}, 33 | onConnConnect: onConnConnect, 34 | onConnClose: onConnClose, 35 | onCustomProtocol: onCustomProtocol, 36 | }, 37 | } 38 | } 39 | 40 | func (self *TcpClient) Run() { 41 | conn, err := net.DialTCP("tcp", nil, self.svrAddr) 42 | CheckError(err) 43 | 44 | self.waitGroup.Add(1) 45 | go func() { 46 | c := newTcpConn(0, self.tcpSock, conn, self.sendBufCap, self.recvBufCap, self.onCustomProtocol(), self.connClose) 47 | if self.onConnConnect != nil { 48 | self.onConnConnect(c) 49 | } 50 | c.run() 51 | self.waitGroup.Done() 52 | }() 53 | } 54 | 55 | func (self *TcpClient) Close() { 56 | close(self.exitChan) 57 | self.waitGroup.Wait() 58 | } 59 | 60 | func (self *TcpClient) connClose(conn *TcpConn) { 61 | if self.onConnClose != nil { 62 | self.onConnClose(conn) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tcpconn.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 ecofast(胡光耀). All rights reserved. 2 | // Use of this source code is governed by a BSD-style license. 3 | 4 | package tcpsock 5 | 6 | import ( 7 | "net" 8 | "sync" 9 | "sync/atomic" 10 | ) 11 | 12 | type OnTcpConnCallback func(c *TcpConn) 13 | 14 | type OnTcpCustomProtocol func() Protocol 15 | 16 | type TcpConn struct { 17 | id uint32 18 | owner *tcpSock 19 | conn *net.TCPConn 20 | sendChan chan Packet 21 | recvChan chan Packet 22 | proto Protocol 23 | closeChan chan struct{} 24 | closeOnce sync.Once 25 | closedFlag int32 26 | onClose OnTcpConnCallback 27 | } 28 | 29 | func newTcpConn(id uint32, owner *tcpSock, conn *net.TCPConn, sendCap, recvCap uint32, proto Protocol, onClose OnTcpConnCallback) *TcpConn { 30 | return &TcpConn{ 31 | id: id, 32 | owner: owner, 33 | conn: conn, 34 | sendChan: make(chan Packet, sendCap), 35 | recvChan: make(chan Packet, recvCap), 36 | proto: proto, 37 | closeChan: make(chan struct{}), 38 | onClose: onClose, 39 | } 40 | } 41 | 42 | func (self *TcpConn) ID() uint32 { 43 | return self.id 44 | } 45 | 46 | func (self *TcpConn) run() { 47 | startGoroutine(self.reader, self.owner.waitGroup) 48 | startGoroutine(self.writer, self.owner.waitGroup) 49 | startGoroutine(self.handler, self.owner.waitGroup) 50 | } 51 | 52 | func (self *TcpConn) Close() { 53 | self.closeOnce.Do(func() { 54 | atomic.StoreInt32(&self.closedFlag, 1) 55 | close(self.sendChan) 56 | close(self.recvChan) 57 | close(self.closeChan) 58 | self.conn.Close() 59 | if self.onClose != nil { 60 | self.onClose(self) 61 | } 62 | }) 63 | } 64 | 65 | func (self *TcpConn) Closed() bool { 66 | return atomic.LoadInt32(&self.closedFlag) == 1 67 | } 68 | 69 | func (self *TcpConn) RawConn() *net.TCPConn { 70 | return self.conn 71 | } 72 | 73 | func startGoroutine(fn func(), wg *sync.WaitGroup) { 74 | wg.Add(1) 75 | go func() { 76 | fn() 77 | wg.Done() 78 | }() 79 | } 80 | 81 | func (self *TcpConn) reader() { 82 | defer func() { 83 | recover() 84 | self.Close() 85 | }() 86 | 87 | buf := make([]byte, RecvBufLenMax) 88 | for { 89 | select { 90 | case <-self.owner.exitChan: 91 | return 92 | 93 | case <-self.closeChan: 94 | return 95 | 96 | default: 97 | } 98 | 99 | count, err := self.conn.Read(buf) 100 | if err != nil { 101 | return 102 | } 103 | self.proto.Parse(buf[:count], self.recvChan) 104 | } 105 | } 106 | 107 | func (self *TcpConn) writer() { 108 | defer func() { 109 | recover() 110 | self.Close() 111 | }() 112 | 113 | for { 114 | if self.Closed() { 115 | return 116 | } 117 | 118 | select { 119 | case <-self.owner.exitChan: 120 | return 121 | 122 | case <-self.closeChan: 123 | return 124 | 125 | case p := <-self.sendChan: 126 | if _, err := self.conn.Write(p.Marshal()); err != nil { 127 | return 128 | } 129 | } 130 | } 131 | } 132 | 133 | func (self *TcpConn) handler() { 134 | defer func() { 135 | recover() 136 | self.Close() 137 | }() 138 | 139 | for { 140 | if self.Closed() { 141 | return 142 | } 143 | 144 | select { 145 | case <-self.owner.exitChan: 146 | return 147 | 148 | case <-self.closeChan: 149 | return 150 | 151 | case packet := <-self.recvChan: 152 | self.proto.Process(self, packet) 153 | } 154 | } 155 | } 156 | 157 | func (self *TcpConn) Write(p Packet) { 158 | if self.Closed() { 159 | return 160 | } 161 | 162 | defer func() { 163 | recover() 164 | }() 165 | 166 | self.sendChan <- p 167 | } 168 | -------------------------------------------------------------------------------- /tcpserver.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 ecofast(胡光耀). All rights reserved. 2 | // Use of this source code is governed by a BSD-style license. 3 | 4 | package tcpsock 5 | 6 | import ( 7 | "net" 8 | "sync" 9 | "sync/atomic" 10 | "time" 11 | 12 | . "github.com/ecofast/rtl/sysutils" 13 | ) 14 | 15 | type TcpServer struct { 16 | listener *net.TCPListener 17 | acceptTimeout int 18 | *tcpSock 19 | autoIncID uint32 20 | numOfConn uint32 21 | } 22 | 23 | func NewTcpServer(listenPort, acceptTimeout int, onConnConnect, onConnClose OnTcpConnCallback, onCustomProtocol OnTcpCustomProtocol) *TcpServer { 24 | tcpAddr, err := net.ResolveTCPAddr("tcp", ":"+IntToStr(int(listenPort))) 25 | CheckError(err) 26 | listener, err := net.ListenTCP("tcp", tcpAddr) 27 | CheckError(err) 28 | 29 | if onCustomProtocol == nil { 30 | panic("tcpsock.NewTcpServer: invalid custom protocol") 31 | } 32 | 33 | return &TcpServer{ 34 | listener: listener, 35 | acceptTimeout: acceptTimeout, 36 | tcpSock: &tcpSock{ 37 | sendBufCap: SendBufCapMax, 38 | recvBufCap: RecvBufCapMax, 39 | exitChan: make(chan struct{}), 40 | waitGroup: &sync.WaitGroup{}, 41 | onConnConnect: onConnConnect, 42 | onConnClose: onConnClose, 43 | onCustomProtocol: onCustomProtocol, 44 | }, 45 | } 46 | } 47 | 48 | func (self *TcpServer) Serve() { 49 | self.waitGroup.Add(1) 50 | defer func() { 51 | self.listener.Close() 52 | self.waitGroup.Done() 53 | }() 54 | 55 | for { 56 | select { 57 | case <-self.exitChan: 58 | return 59 | 60 | default: 61 | } 62 | 63 | self.listener.SetDeadline(time.Now().Add(time.Duration(self.acceptTimeout) * time.Second)) 64 | conn, err := self.listener.AcceptTCP() 65 | if err != nil { 66 | continue 67 | } 68 | 69 | atomic.AddUint32(&self.numOfConn, 1) 70 | self.waitGroup.Add(1) 71 | go func() { 72 | c := newTcpConn(atomic.AddUint32(&self.autoIncID, 1), self.tcpSock, conn, self.sendBufCap, self.recvBufCap, self.onCustomProtocol(), self.connClose) 73 | if self.onConnConnect != nil { 74 | self.onConnConnect(c) 75 | } 76 | c.run() 77 | self.waitGroup.Done() 78 | }() 79 | } 80 | } 81 | 82 | func (self *TcpServer) Close() { 83 | close(self.exitChan) 84 | self.waitGroup.Wait() 85 | } 86 | 87 | func (self *TcpServer) NumOfConn() uint32 { 88 | return atomic.LoadUint32(&self.numOfConn) 89 | } 90 | 91 | func (self *TcpServer) connClose(conn *TcpConn) { 92 | atomic.AddUint32(&self.numOfConn, ^uint32(0)) 93 | if self.onConnClose != nil { 94 | self.onConnClose(conn) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tcpsock.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 ecofast(胡光耀). All rights reserved. 2 | // Use of this source code is governed by a BSD-style license. 3 | 4 | // Package tcpsock provides easy to use interfaces for TCP I/O. 5 | // Thanks to darksword(gansidui) and AlexStocks for their valuable projects 6 | // which are gotcp(https://github.com/gansidui/gotcp) 7 | // and getty(https://github.com/AlexStocks/getty). 8 | package tcpsock 9 | 10 | import ( 11 | "sync" 12 | ) 13 | 14 | const ( 15 | RecvBufLenMax = 4 * 1024 16 | SendBufLenMax = 4 * 1024 17 | 18 | SendBufCapMax = 10 19 | RecvBufCapMax = 10 20 | ) 21 | 22 | type tcpSock struct { 23 | sendBufCap uint32 24 | recvBufCap uint32 25 | exitChan chan struct{} 26 | waitGroup *sync.WaitGroup 27 | onConnConnect OnTcpConnCallback 28 | onConnClose OnTcpConnCallback 29 | onCustomProtocol OnTcpCustomProtocol 30 | } 31 | 32 | type Protocol interface { 33 | Parse(b []byte, recvChan chan<- Packet) 34 | Process(conn *TcpConn, p Packet) 35 | } 36 | 37 | type Packet interface { 38 | Marshal() []byte 39 | } 40 | --------------------------------------------------------------------------------