├── .travis.yml ├── LICENSE ├── go.mod ├── readme.md ├── tcp_server.go └── tcp_server_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.13 4 | script: 5 | - go test -v ./... -race 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 firstrow@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/firstrow/tcp_server 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/firstrow/tcp_server.svg?branch=master)](https://travis-ci.org/firstrow/tcp_server) 2 | 3 | # TCPServer 4 | Package tcp_server created to help build TCP servers faster. 5 | 6 | ### Install package 7 | 8 | ``` bash 9 | go get -u github.com/firstrow/tcp_server@latest 10 | ``` 11 | 12 | ### Usage: 13 | 14 | NOTICE: `OnNewMessage` callback will receive new message only if it's ending with `\n` 15 | 16 | ``` go 17 | package main 18 | 19 | import "github.com/firstrow/tcp_server" 20 | 21 | func main() { 22 | server := tcp_server.New("localhost:9999") 23 | 24 | server.OnNewClient(func(c *tcp_server.Client) { 25 | // new client connected 26 | // lets send some message 27 | c.Send("Hello") 28 | }) 29 | server.OnNewMessage(func(c *tcp_server.Client, message string) { 30 | // new message received 31 | }) 32 | server.OnClientConnectionClosed(func(c *tcp_server.Client, err error) { 33 | // connection with client lost 34 | }) 35 | 36 | server.Listen() 37 | } 38 | ``` 39 | 40 | # Contributing 41 | 42 | To hack on this project: 43 | 44 | 1. Install as usual (`go get -u github.com/firstrow/tcp_server`) 45 | 2. Create your feature branch (`git checkout -b my-new-feature`) 46 | 3. Ensure everything works and the tests pass (`go test`) 47 | 4. Commit your changes (`git commit -am 'Add some feature'`) 48 | 49 | Contribute upstream: 50 | 51 | 1. Fork it on GitHub 52 | 2. Add your remote (`git remote add fork git@github.com:firstrow/tcp_server.git`) 53 | 3. Push to the branch (`git push fork my-new-feature`) 54 | 4. Create a new Pull Request on GitHub 55 | 56 | Notice: Always use the original import path by installing with `go get`. 57 | -------------------------------------------------------------------------------- /tcp_server.go: -------------------------------------------------------------------------------- 1 | package tcp_server 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "log" 7 | "net" 8 | ) 9 | 10 | // Client holds info about connection 11 | type Client struct { 12 | conn net.Conn 13 | Server *server 14 | } 15 | 16 | // TCP server 17 | type server struct { 18 | address string // Address to open connection: localhost:9999 19 | config *tls.Config 20 | onNewClientCallback func(c *Client) 21 | onClientConnectionClosed func(c *Client, err error) 22 | onNewMessage func(c *Client, message string) 23 | } 24 | 25 | // Read client data from channel 26 | func (c *Client) listen() { 27 | c.Server.onNewClientCallback(c) 28 | reader := bufio.NewReader(c.conn) 29 | for { 30 | message, err := reader.ReadString('\n') 31 | if err != nil { 32 | c.conn.Close() 33 | c.Server.onClientConnectionClosed(c, err) 34 | return 35 | } 36 | c.Server.onNewMessage(c, message) 37 | } 38 | } 39 | 40 | // Send text message to client 41 | func (c *Client) Send(message string) error { 42 | return c.SendBytes([]byte(message)) 43 | } 44 | 45 | // Send bytes to client 46 | func (c *Client) SendBytes(b []byte) error { 47 | _, err := c.conn.Write(b) 48 | if err != nil { 49 | c.conn.Close() 50 | c.Server.onClientConnectionClosed(c, err) 51 | } 52 | return err 53 | } 54 | 55 | func (c *Client) Conn() net.Conn { 56 | return c.conn 57 | } 58 | 59 | func (c *Client) Close() error { 60 | return c.conn.Close() 61 | } 62 | 63 | // Called right after server starts listening new client 64 | func (s *server) OnNewClient(callback func(c *Client)) { 65 | s.onNewClientCallback = callback 66 | } 67 | 68 | // Called right after connection closed 69 | func (s *server) OnClientConnectionClosed(callback func(c *Client, err error)) { 70 | s.onClientConnectionClosed = callback 71 | } 72 | 73 | // Called when Client receives new message 74 | func (s *server) OnNewMessage(callback func(c *Client, message string)) { 75 | s.onNewMessage = callback 76 | } 77 | 78 | // Listen starts network server 79 | func (s *server) Listen() { 80 | var listener net.Listener 81 | var err error 82 | if s.config == nil { 83 | listener, err = net.Listen("tcp", s.address) 84 | } else { 85 | listener, err = tls.Listen("tcp", s.address, s.config) 86 | } 87 | if err != nil { 88 | log.Fatal("Error starting TCP server.\r\n", err) 89 | } 90 | defer listener.Close() 91 | 92 | for { 93 | conn, _ := listener.Accept() 94 | client := &Client{ 95 | conn: conn, 96 | Server: s, 97 | } 98 | go client.listen() 99 | } 100 | } 101 | 102 | // Creates new tcp server instance 103 | func New(address string) *server { 104 | log.Println("Creating server with address", address) 105 | server := &server{ 106 | address: address, 107 | } 108 | 109 | server.OnNewClient(func(c *Client) {}) 110 | server.OnNewMessage(func(c *Client, message string) {}) 111 | server.OnClientConnectionClosed(func(c *Client, err error) {}) 112 | 113 | return server 114 | } 115 | 116 | func NewWithTLS(address, certFile, keyFile string) *server { 117 | cert, err := tls.LoadX509KeyPair(certFile, keyFile) 118 | if err != nil { 119 | log.Fatal("Error loading certificate files. Unable to create TCP server with TLS functionality.\r\n", err) 120 | } 121 | config := &tls.Config{ 122 | Certificates: []tls.Certificate{cert}, 123 | } 124 | server := New(address) 125 | server.config = config 126 | return server 127 | } 128 | -------------------------------------------------------------------------------- /tcp_server_test.go: -------------------------------------------------------------------------------- 1 | package tcp_server 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func Test_accepting_new_client_callback(t *testing.T) { 11 | server := New("localhost:9999") 12 | 13 | var wg sync.WaitGroup 14 | wg.Add(3) 15 | 16 | var messageText string 17 | 18 | server.OnNewClient(func(c *Client) { 19 | wg.Done() 20 | }) 21 | server.OnNewMessage(func(c *Client, message string) { 22 | wg.Done() 23 | messageText = message 24 | }) 25 | server.OnClientConnectionClosed(func(c *Client, err error) { 26 | wg.Done() 27 | }) 28 | go server.Listen() 29 | 30 | // Wait for server 31 | // If test fails - increase this value 32 | time.Sleep(10 * time.Millisecond) 33 | 34 | conn, err := net.Dial("tcp", "localhost:9999") 35 | if err != nil { 36 | t.Fatal("Failed to connect to test server") 37 | } 38 | _, err = conn.Write([]byte("Test message\n")) 39 | if err != nil { 40 | t.Fatal("Failed to send test message.") 41 | } 42 | conn.Close() 43 | 44 | wg.Wait() 45 | 46 | if messageText != "Test message\n" { 47 | t.Error("received wrong message") 48 | } 49 | } 50 | --------------------------------------------------------------------------------