One place for hosting & domains

      Application (HTTP) vs Network (TCP) Load Balancers

      Load balancing is the process of distributing client requests across multiple servers. Originally, load balancers were dedicated hardware appliances connected to physical servers in data centers. Today, software products such as Akamai NodeBalancers perform the same role with cloud-based servers.

      Whether hardware or software, the concept is the same. Load balancers act as a reverse proxy for client requests, parceling out requests across servers to avoid resource exhaustion. Load balancers take many forms and offer many features. However, they all route client requests to back-end servers using either application-layer (HTTP/S) or transport-layer (TCP) criteria. Distributing the load in this way can help ensure the best performance, availability, scalability, and security. With Akamai NodeBalancers, it’s simple to take advantage of the features available at either layer.

      Transmission Control Protocol (TCP) resides at the transport layer (L4) in the seven-layer OSI model. Meanwhile, Hypertext Transport Protocol (HTTP) and secure HTTP (HTTPS) reside at the higher application layer (L7). Both TCP and HTTP have their place when it comes to load balancing.

      If your application is a website or uses a web-accessible front end, it can be preferable to use application-layer (HTTP) load balancing. Using an HTTP load balancer allows you to view the contents of a client’s HTTP request before you determine which back-end machine it is routed to. This enables you to make use of application-layer data in HTTP headers. Depending on the features of the load balancer, it may be able to read these headers to dynamically route the traffic (such as routing requests for example.com/app to a different set of back-ends than example.com/blog).

      There are many applications that do not use HTTP traffic, such as email, instant messaging, and SQL databases. For these, TCP load balancing is the right choice. In addition to working with a wider range of applications, it also typically has faster performance as it is not terminating HTTP requests and analyzing those requests.

      In this guide, learn what benefits each protocol offers, and which makes the most sense for common use cases.

      HTTP (Application/L7) Load Balancing Overview

      With HTTP/S load balancing, the NodeBalancer examines each packet’s application layer HTTP headers to decide how to route client requests to back-end servers. Besides improving application performance, availability, and horizontal scalability for Web applications, HTTP load balancing also affords web-specific benefits not available from TCP load balancing.

      HTTP Load Balancer Benefits

      HTTP load balancing works with unencrypted Web traffic, but also in HTTPS mode using Transport Layer Security (TLS). With TLS, the load balancer hosts the certificate and private key for a given web site. It then decrypts client requests and passes them along to back-end servers.

      Some load balancers such as HAProxy can even make URL-based routing decisions. This can be helpful if your cloud infrastructure is set up for some servers to handle video or image requests, while others return text objects.

      HTTP Load Balancer Use Cases

      Web-based load balancing makes sense for virtually any application that runs over HTTP/S. This is true whether it’s a static Web site or a complex multi-tier application that uses a Web front end.

      E-commerce sites require an item directory and a shopping cart, along with financial and shipping functions to complete transactions. In this case, the application load balancer sends browsing requests that contain product images or video to servers that don’t maintain open connections. Meanwhile, shopping cart and checkout requests are sent to those servers who retain client connections and cart contents.

      TCP (Transport/L4) Load Balancing Overview

      TCP load balancing algorithms use a client request’s destination TCP port number to make forwarding decisions. Optionally, a load balancer may compute a hash of TCP source and destination port numbers to ensure session persistence. This ensures that the same client always reaches the same server. This is useful for applications or services that employ unique tokens for each session.

      TCP Load Balancing Benefits

      TCP load balancing is relatively simple to implement and operate. It’s useful for applications that run over TCP but not HTTP/S. It does work with HTTP/S traffic by pointing to port 80 or 443, but without any HTTP-specific capabilities.

      The level of a load balancer refers to how far up the network stack a communication has to travel before it reaches its destination. Since TCP operates in the OSI model’s fourth level, an application load balancer route based on TCP has less latency. This is because the communication doesn’t have to go all the way up and down the network stack.

      TCP Load Balancing Use Cases

      Websites that require extreme performance and high availability to handle latency-sensitive applications, spike-heavy workloads, and high-volume inbound TCP requests benefit from TCP load balancing. TCP load balancing is also a good option when you need to support a static or elastic IP address. Another appropriate use case is if you are using container services and want to support more than one port on a compute instance.

      Conclusion

      Load balancing is one of the best and easiest ways to boost the performance, availability, and scalability of your applications. With just a few clicks, you can set up an Akamai NodeBalancer to use Web- or TCP-specific features to minimize downtime and maximize performance.

      Create a TCP and UDP Client and Server using Go


      Updated by Linode Contributed by Mihalis Tsoukalos

      Go is a compiled, statically typed programming language developed by Google. Many modern applications, including Docker, Kubernetes, and Terraform, are written in Go. Go packages allow developers to organize and reuse Go code in a simple and maintainable manner.

      In this guide, you will use the net package, which is a part of Go’s standard library, to create TCP and UDP servers and clients. This guide is meant to provide instructional examples to help you become more familiar with the Go programming language.

      Scope of this Guide

      Throughout this guide you will create the following:

      • A TCP server and client. The TCP server accepts incoming messages from a TCP client and responds with the current date and time.
      • A UDP server and client. The UDP server accepts incoming messages from a UDP client and responds with a random number.
      • A concurrent TCP server that accepts incoming messages from several TCP clients and responds with the number of clients currently connected to it.

      Before You Begin

      1. If you are not familiar with using Go packages, review the Getting Started with Go Packages guide.

      2. Install Go on your computer if it is not already installed. You can follow our guide How to Install Go on Ubuntu for installation steps.

        This guide requires Go version 1.8 or higher. It is considered good practice to have the latest version of Go installed. You can check your Go version by executing the following command:

        go version
        

      Note

      This guide is written for a non-root user. Depending on the TCP/IP port number that you use when running the TCP and UDP servers, you may need to prefix commands with sudo. If you are not familiar with the sudo command, see the Users and Groups guide.

      Protocol Definitions

      ProtocolDefinition
      TCP (Transmission Control Protocol)TCP’s principal characteristic is that it is a reliable protocol by design. If there is no proof of a packet’s delivery, TCP will resend the packet. Some of the tasks TCP packets can be used for are establishing connections, transferring data, sending acknowledgements, and closing connections.
      IP (Internet Protocol)The IP protocol adheres to the end-to-end principle, which places all network intelligence in the end nodes and not in the intermediary nodes. This design favors a reduction in network complexity over reliability. For this reason, the Internet Protocol does not guarantee a reliable delivery of packets over a network. Instead, IP works together with TCP to reliably deliver packets over a network.
      UDP (User Datagram Protocol):UDP provides a simpler implementation of the transport layer protocol that, while less reliable than TCP, is much faster. UDP does not provide error checking, correction or packet retransmission, which makes it very fast. When speed is more important than reliability, UDP is generally chosen over TCP. UDP is commonly used for online gaming, video chatting, and other real-time applications.

      The net Package

      Go’s net package provides a portable interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain sockets. You will use this package to create TCP and UDP servers and clients in this guide.

      net Package Functions

      Use the table below as a quick reference for some of the net package functions used throughout this guide. To view all types and functions included in the net package, see Golang’s official documentation.

      Note

      All versions of net.Dial() and net.Listen() return data types that implement the io.Reader and io.Writer interfaces. This means that you can use regular File I/O functions to send and receive data from a TCP/IP connections.
      TypeFunction
      type Listenerfunc Listen(network, address string) (Listener, error)

         • The network parameter defines the type of network to use and accepts values tcp, tcp4 (IPv4-only), tcp6 (IPv6-only), unix (Unix sockets), or unixpacket.

         • The address parameter defines the server address and port number that the server will listen on.

      type UDPConnfunc ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error)

         • Used to create UDP servers.

         • The network parameter must be a UDP network name.

         • The laddr parameter defines the server address and port number that the server will listen on.

      func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)

         • Used to specify the kind of client you will create.

         • The network parameter must be a UDP network name.

         • The laddr is the listening address (server). If laddr is nil, a local address is automatically chosen.

         • raddr is the response address (client). If the IP field of raddr is nil or an unspecified IP address, the local system is assumed.

      type UDPAddrfunc ResolveUDPAddr(network, address string) (*UDPAddr, error)

         • This function returns the address of a UDP end point.

         • The network parameter must be a UDP network name.

         • The address parameter has the form host:port. The host must be a an IP address, or a host name that can be resolved to IP addresses.

      type TCPAddrfunc ResolveTCPAddr(network, address string) (*TCPAddr, error)

         • This function returns the address of a TCP end point.

         • The network parameter must be a TCP network name.

         • The address parameter has the form host:port. The host must be a an IP address, or a host name that can be resolved to IP addresses.

      type Connfunc Dial(network, address string) (Conn, error)

         • This function connects to the address on the named network.

         • The network parameter can be tcp, tcp4 (IPv4-only), tcp6 (IPv6-only), udp, udp4 (IPv4-only), udp6 (IPv6-only), ip, ip4 (IPv4-only), ip6 (IPv6-only), unix, unixgram and unixpacket.

         • When using TCP or UDP networks, the address parameter has the form host:port. The host must be a an IP address, or a host name that can be resolved to IP addresses.

      type TCPConnfunc DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)

         • This function connects to the address on the TCP networks.

         • The network parameter must be a TCP network name.

         • The laddr is the listening address (server). If laddr is nil, a local address is automatically chosen.

         • raddr is the response address (client). If the IP field of raddr is nil or an unspecified IP address, the local system is assumed.

      Create a TCP Client and Server

      In this section, you will create a generic TCP client and server using Go. After creating the client and server, you will run them to test their connection with each other.

      Note

      Create the TCP Client

      The TCP client that you will create in this section will allow you to interact with any TCP server.

      1. In your current working directory, create a file named tcpC.go with the following content:

        ./tcpC.go
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        
        package main
        
        import (
                "bufio"
                "fmt"
                "net"
                "os"
                "strings"
        )
        
        func main() {
                arguments := os.Args
                if len(arguments) == 1 {
                        fmt.Println("Please provide host:port.")
                        return
                }
        
                CONNECT := arguments[1]
                c, err := net.Dial("tcp", CONNECT)
                if err != nil {
                        fmt.Println(err)
                        return
                }
        
                for {
                        reader := bufio.NewReader(os.Stdin)
                        fmt.Print(">> ")
                        text, _ := reader.ReadString('n')
                        fmt.Fprintf(c, text+"n")
        
                        message, _ := bufio.NewReader(c).ReadString('n')
                        fmt.Print("->: " + message)
                        if strings.TrimSpace(string(text)) == "STOP" {
                                fmt.Println("TCP client exiting...")
                                return
                        }
                }
        }
            
        • This file creates the main package, which declares the main() function. The function will use the imported packages to create a TCP client.
        • The main() function gathers command line arguments in the arguments variable and makes sure that a value for host:port was sent.
        • The CONNECT variable stores the value of arguments[1]to be used in the net.Dial() call.
        • A call to net.Dial() begins the implementation of the TCP client and will connect you to the desired TCP server. The second parameter of net.Dial() has two parts; the first is the hostname or the IP address of the TCP server and the second is the port number the TCP server listens on.
        • bufio.NewReader(os.Stdin) and ReadString() is used to read user input. Any user input is sent to the TCP server over the network using Fprintf().
        • bufio reader and the bufio.NewReader(c).ReadString('n') statement read the TCP server’s response. The error variable is ignored here for simplicity.
        • The entire for loop that is used to read user input will only terminate when you send the STOP command to the TCP server.

      Create the TCP Server

      You are now ready to create the TCP server. The TCP server will return the current date and time to the TCP client using a single network packet.

      1. In your current working directory, create a file named tcpS.go with the following content:

        ./tcpS.go
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        
        package main
        
        import (
                "bufio"
                "fmt"
                "net"
                "os"
                "strings"
                "time"
        )
        
        func main() {
                arguments := os.Args
                if len(arguments) == 1 {
                        fmt.Println("Please provide port number")
                        return
                }
        
                PORT := ":" + arguments[1]
                l, err := net.Listen("tcp", PORT)
                if err != nil {
                        fmt.Println(err)
                        return
                }
                defer l.Close()
        
                c, err := l.Accept()
                if err != nil {
                        fmt.Println(err)
                        return
                }
        
                for {
                        netData, err := bufio.NewReader(c).ReadString('n')
                        if err != nil {
                                fmt.Println(err)
                                return
                        }
                        if strings.TrimSpace(string(netData)) == "STOP" {
                                fmt.Println("Exiting TCP server!")
                                return
                        }
        
                        fmt.Print("-> ", string(netData))
                        t := time.Now()
                        myTime := t.Format(time.RFC3339) + "n"
                        c.Write([]byte(myTime))
                }
        }
            
        • This file creates the main package, which declares the main() function. The function will use the imported packages to create a TCP server.
        • The main() function gathers command line arguments in the arguments variable and includes error handling.
        • The net.Listen() function makes the program a TCP server. This functions returns a Listener variable, which is a generic network listener for stream-oriented protocols.
        • It is only after a successful call to Accept() that the TCP server can begin to interact with TCP clients.
        • The current implementation of the TCP server can only serve the first TCP client that connects to it, because the Accept() call is outside of the for loop. In the Create a Concurrent TCP Server section of this guide, you will see a TCP server implementation that can serve multiple TCP clients using Goroutines.
        • The TCP server uses regular File I/O functions to interact with TCP clients. This interaction takes place inside the for loop. Similarly to the TCP client, when the TCP server receives the STOP command from the TCP client, it will terminate.

      Test the TCP Client and Server

      You can now test your TCP client and server. You will need to execute the TCP server first so that the TCP client has somewhere it can connect to.

      1. Run your TCP server. From the directory containing the tcpS.go file, run the following command:

        go run tcpS.go 1234
        

        The server will listen on port number 1234. You will not see any output as a result of this command.

      2. Open a second shell session to execute the TCP client and to interact with the TCP server. Run the following command:

        go run tcpC.go 127.0.0.1:1234
        

        Note

        If the TCP server is not running on the expected TCP port, you will get the following error message from tcpC.go:

        dial tcp [::1]:1234: connect: connection refused
        
      3. You will see a >> prompt waiting for you to enter some text. Type in Hello! to receive a response from the TCP server:

        Hello!
        

        You should see a similar output:

          
        >> Hello!
        ->: 2019-05-23T19:43:21+03:00
            
        
      4. Send the STOP command to exit the TCP client and server:

        STOP
        

        You should see a similar output in the client:

          
        >> STOP
        ->: TCP client exiting...
            
        

        The output on the TCP server side will resemble the following:

          
        -> Hello!
        Exiting TCP server!
            
        

      Note

      The TCP server waits before writing back to the TCP client, whereas the client writes to the TCP server first and then waits to receive an answer. This behavior is part of the protocol definition that governs a TCP or a UDP connection. In this example, you have implemented an unofficial protocol that is based on TCP.

      Create a UDP Client and Server

      In this section, you will create a UDP client and server. After creating the client and server, you will run them both to test their connection with each other. A UDP client can be generic and can communicate with multiple UDP servers. On the other hand, a UDP server cannot be completely generic, because it typically implements a specific functionality. In the case of our UDP server example, it will return random numbers to UDP clients that connect to it.

      Create the UDP Client

      The UDP client that you will create in this section will allow you to interact with any UDP server.

      1. In your current working directory, create a file named udpC.go with the following content:

        ./udpC.go
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        
        package main
        
        import (
                "bufio"
                "fmt"
                "net"
                "os"
                "strings"
        )
        
        func main() {
                arguments := os.Args
                if len(arguments) == 1 {
                        fmt.Println("Please provide a host:port string")
                        return
                }
                CONNECT := arguments[1]
        
                s, err := net.ResolveUDPAddr("udp4", CONNECT)
                c, err := net.DialUDP("udp4", nil, s)
                if err != nil {
                        fmt.Println(err)
                        return
                }
        
                fmt.Printf("The UDP server is %sn", c.RemoteAddr().String())
                defer c.Close()
        
                for {
                        reader := bufio.NewReader(os.Stdin)
                        fmt.Print(">> ")
                        text, _ := reader.ReadString('n')
                        data := []byte(text + "n")
                        _, err = c.Write(data)
                        if strings.TrimSpace(string(data)) == "STOP" {
                                fmt.Println("Exiting UDP client!")
                                return
                        }
        
                        if err != nil {
                                fmt.Println(err)
                                return
                        }
        
                        buffer := make([]byte, 1024)
                        n, _, err := c.ReadFromUDP(buffer)
                        if err != nil {
                                fmt.Println(err)
                                return
                        }
                        fmt.Printf("Reply: %sn", string(buffer[0:n]))
                }
        }
              
        • This file creates the main package, which declares the main() function. The function will use the imported packages to create a UDP client.
        • The main() function gathers command line arguments in the arguments variable and includes error handling.
        • Regular File I/O functions are used by the UDP client to interact with the UDP server. The client will terminate when you send the STOP command to the UDP server. This is not part of the UDP protocol, but is used in the example to provide the client with a way to exit.
        • A UDP end point address is returned by the net.ResolveUDPAddr() function. The UDP end point is of type UDPAddr and contains IP and port information.
        • The connection to the UDP server is established with the use of the net.DialUDP() function.
        • bufio.NewReader(os.Stdin) and ReadString() is used to read user input.
        • The ReadFromUDP() function reads a packet from the server connection and will return if it encounters an error.

      Create the UDP Server

      You are now ready to create the UDP server. You will write the UDP server code to respond to any connected client with random numbers.

      1. In your current working directory, create a file named udps.go with the following content:

        ./udpS.go
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        
        package main
        
        import (
                "fmt"
                "math/rand"
                "net"
                "os"
                "strconv"
                "strings"
                "time"
        )
        
        func random(min, max int) int {
                return rand.Intn(max-min) + min
        }
        
        func main() {
                arguments := os.Args
                if len(arguments) == 1 {
                        fmt.Println("Please provide a port number!")
                        return
                }
                PORT := ":" + arguments[1]
        
                s, err := net.ResolveUDPAddr("udp4", PORT)
                if err != nil {
                        fmt.Println(err)
                        return
                }
        
                connection, err := net.ListenUDP("udp4", s)
                if err != nil {
                        fmt.Println(err)
                        return
                }
        
                defer connection.Close()
                buffer := make([]byte, 1024)
                rand.Seed(time.Now().Unix())
        
                for {
                        n, addr, err := connection.ReadFromUDP(buffer)
                        fmt.Print("-> ", string(buffer[0:n-1]))
        
                        if strings.TrimSpace(string(buffer[0:n])) == "STOP" {
                                fmt.Println("Exiting UDP server!")
                                return
                        }
        
                        data := []byte(strconv.Itoa(random(1, 1001)))
                        fmt.Printf("data: %sn", string(data))
                        _, err = connection.WriteToUDP(data, addr)
                        if err != nil {
                                fmt.Println(err)
                                return
                        }
                }
        }
            
        • This file creates the main package, which declares the main() function. The function will use the imported packages to create a UDP server.
        • The main() function gathers command line arguments in the arguments variable and includes error handling.
        • The net.ListenUDP() function tells the application to listen for incoming UDP connections, which are served inside the for loop. This is the function call that makes the program a UDP server.
        • The ReadFromUDP() and WriteToUDP() functions are used to read data from a UDP connection and write data to a UDP connection, respectively. A byte slice is stored in the data variable and used to write the desired data. The buffer variable also stores a byte slice and is used to read data.
        • Since UDP is a stateless protocol, each UDP client is served and then the connection closes automatically. The UDP server program will only exit when it receives the STOP keyword from a UDP client. Otherwise, the server program will continue to wait for more UDP connections from other clients.

      Test the UDP Client and Server

      You can now test your UDP client and server. You will need to execute the UDP server first so that the UDP client has somewhere it can connect to.

      1. Run your UDP server. From the directory containing the udpS.go file, run the following command:

        go run udpS.go 1234
        

        The server will listen on port number 1234. You will not see any output as a result of this command.

      2. Open a second shell session to execute the UDP client and to interact with the UDP server. Run the following command:

        go run udpC.go 127.0.0.1:1234
        
      3. You will see a >> prompt waiting for you to enter some text. Type in Hello! to receive a response from the UDP server:

        Hello!
        

        You should see a similar output:

          
        The UDP server is 127.0.0.1:1234
        >> Hello!
        Reply: 82
            
        
      4. Send the STOP command to exit the UDP client and server:

        You should see a similar output on the client side:

          
        >> STOP
        Exiting UDP client!
            
        

        The output on the UDP server side will be as follows:

          
        -> STOP
        Exiting UDP server!
            
        

      Create a Concurrent TCP Server

      This section demonstrates the implementation of a concurrent TCP server. The benefit of a concurrent TCP server is that it can serve multiple clients. In Go, this is accomplished by creating a separate Goroutine to serve each TCP client.

      The example TCP server keeps a running count of the number of TCP clients it has served so far. The counter increases by one each time a new TCP client connects to the TCP server. The current value of that counter is returned to each TCP client.

      1. In your current working directory, create a file named concTCP.go with the following content:

        ./concTCP.go
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        
        package main
        
        import (
                "bufio"
                "fmt"
                "net"
                "os"
                "strconv"
                "strings"
        )
        
        var count = 0
        
        func handleConnection(c net.Conn) {
                fmt.Print(".")
                for {
                        netData, err := bufio.NewReader(c).ReadString('n')
                        if err != nil {
                                fmt.Println(err)
                                return
                        }
        
                        temp := strings.TrimSpace(string(netData))
                        if temp == "STOP" {
                                break
                        }
                        fmt.Println(temp)
                        counter := strconv.Itoa(count) + "n"
                        c.Write([]byte(string(counter)))
                }
                c.Close()
        }
        
        func main() {
                arguments := os.Args
                if len(arguments) == 1 {
                        fmt.Println("Please provide a port number!")
                        return
                }
        
                PORT := ":" + arguments[1]
                l, err := net.Listen("tcp4", PORT)
                if err != nil {
                        fmt.Println(err)
                        return
                }
                defer l.Close()
        
                for {
                        c, err := l.Accept()
                        if err != nil {
                                fmt.Println(err)
                                return
                        }
                        go handleConnection(c)
                        count++
                }
        }
              
        • This file creates the main package, which declares the handleConnection() and main() functions.
        • The main() function will use the imported packages to create a concurrent TCP server. It gathers command line arguments in the arguments variable and includes error handling.
        • Each TCP client is served by a separate Goroutine that executes the handleConnection() function. This means that while a TCP client is served, the TCP server is free to interact with more TCP clients. TCP clients are connected using the Accept() function.
        • Although the Accept() function can be executed multiple times, the net.Listen() function needs to be executed only once. For this reason the net.Listen() function remains outside of the for loop.
        • The for loop in the main() function is endless because TCP/IP servers usually run nonstop. However, if the handleConnection() function receives the STOP message, the Goroutine that runs it will exit and the related TCP connection will close.

      Test the Concurrent TCP Server

      In this section, you will test the concurrent TCP server using the netcat command line utility.

      1. Run your concurrent TCP server. From the directory containing the concTCP.go file, run the following command:

        go run concTCP.go 1234
        

        The command creates a TCP server that listens on port number 1234. You can use any port number, however, ensure it is not already in use and that you have the required privileges. Reference the list of well-known TCP and UDP ports, if needed.

      2. Use netcat to establish a connection with the TCP server. By default, netcat will establish a TCP connection with a remote host on the specified port number.

        nc 127.0.0.1 1234
        
      3. After issuing the previous command, you will not see any change in your output. Type Hello! to send a packet to the TCP server:

        Hello!
        

        The TCP server will return the number of current client connections as its response. Since this is your first connection established with the TCP server, you should expect an output of 1.

          
        Hello!
        1
            
        

        If you’d like, you can open a new shell session and use netcat to establish a second connection with the TCP server by repeating Step 2. When you send the server a second Hello!, you should receive a response of 2 this time.

      4. You can also connect to the TCP server using the TCP client you created in the Create the TCP Client section of the guide. Ensure you are in the directory containing the tcpC.go file and issue the following command:

        go run tcpC.go 127.0.0.1:1234
        
      5. You will see a >> prompt waiting for you to enter some text. Type in Hello! to receive a response from the TCP server:

        Hello!
        

        You should see a similar output indicating 3 client connections:

          
        >> Hello!
        ->: 3
            
        
      6. Send the STOP command to exit the TCP client:

        You should see a similar output on the client:

          
        >> STOP
        ->: TCP client exiting...
              
        

        The output on the TCP server side will be as follows:

          
        .Hello!
        .Hello!
        .Hello!
              
        

        Note

        From the shell session running the TCP server, type CTRL-c to interrupt program execution and then, CTRL-D to close all client connections and to stop the TCP server.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      How To Develop a Node.js TCP Server Application using PM2 and Nginx on Ubuntu 16.04


      The author selected OSMI to receive a donation as part of the Write for DOnations program.

      Introduction

      Node.js is a popular open-source JavaScript runtime environment built on Chrome’s V8 Javascript engine. Node.js is used for building server-side and networking applications.TCP (Transmission Control Protocol) is a networking protocol that provides reliable, ordered and error-checked delivery of a stream of data between applications. A TCP server can accept a TCP connection request, and once the connection is established both sides can exchange data streams.

      In this tutorial, you’ll build a basic Node.js TCP server, along with a client to test the server. You’ll run your server as a background process using a powerful Node.js process manager called PM2. Then you’ll configure Nginx as a reverse proxy for the TCP application and test the client-server connection from your local machine.

      Prerequisites

      To complete this tutorial, you will need:

      Step 1 — Creating a Node.js TCP Application

      We will write a Node.js application using TCP Sockets. This is a sample application which will help you understand the Net library in Node.js which enables us to create raw TCP server and client applications.

      To begin, create a directory on your server in which you would like to place your Node.js application. For this tutorial, we will create our application in the ~/tcp-nodejs-app directory:

      Then switch to the new directory:

      Create a new file named package.json for your project. This file lists the packages that the application depends on. Creating this file will make the build reproducible as it will be easier to share this list of dependencies with other developers:

      You can also generate the package.json using the npm init command, which will prompt you for the details of the application, but we'll still have to manually alter the file to add additional pieces, including a startup command. Therefore, we'll manually create the file in this tutorial.

      Add the following JSON to the file, which specifies the application's name, version, the main file, the command to start the application, and the software license:

      package.json

      {
        "name": "tcp-nodejs-app",
        "version": "1.0.0",
        "main": "server.js",
        "scripts": {
          "start": "node server.js"
        },
        "license": "MIT"
      }
      

      The scripts field lets you define commands for your application. The setting you specified here lets you run the app by running npm start instead of running node server.js.

      The package.json file can also contain a list of runtime and development dependencies, but we won't have any third party dependencies for this application.

      Now that you have the project directory and package.json set up, let's create the server.

      In your application directory, create a server.js file:

      Node.js provides a module called net which enables TCP server and client communication. Load the net module with require(), then define variables to hold the port and host for the server:

      server.js

      const net = require('net');
      const port = 7070;
      const host = '127.0.0.1';
      

      We'll use port 7070 for this app, but you can use any available port you'd like. We're using 127.0.0.1 for the HOST which ensures that our server is only listening on our local network interface. Later we will place Nginx in front of this app as a reverse proxy. Nginx is well-versed at handling multiple connections and horizontal scaling.

      Then add this code to spawn a TCP server using the createServer() function from the net module. Then start listening for connections on the port and host you defined by using the listen() function of the net module:

      server.js

      ...
      const server = net.createServer();
      server.listen(port, host, () => {
          console.log('TCP Server is running on port ' + port +'.');
      });
      
      

      Save server.js and start the server:

      You'll see this output:

      Output

      TCP Server is running on port 7070

      The TCP server is running on port 7070. Press CTRL+C to stop the server.

      Now that we know the server is listening, let's write the code to handle client connections.

      When a client connects to the server, the server triggers a connection event, which we'll observe. We'll define an array of connected clients, which we'll call sockets, and add each client instance to this array when the client connects.

      We'll use the data event to process the data stream from the connected clients, using the sockets array to broadcast data to all the connected clients.

      Add this code to the server.js file to implement these features:

      server.js

      
      ...
      
      let sockets = [];
      
      server.on('connection', function(sock) {
          console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
          sockets.push(sock);
      
          sock.on('data', function(data) {
              console.log('DATA ' + sock.remoteAddress + ': ' + data);
              // Write the data back to all the connected, the client will receive it as data from the server
              sockets.forEach(function(sock, index, array) {
                  sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + 'n');
              });
          });
      });
      

      This tells the server to listen to data events sent by connected clients. When the connected clients send any data to the server, we echo it back to all the connected clients by iterating through the sockets array.

      Then add a handler for close events which will be trigerred when a connected client terminates the connection. Whenever a client disconnects, we want to remove the client from the sockets array so we no longer broadcast to it. Add this code at the end of the connection block:

      server.js

      
      let sockets = [];
      server.on('connection', function(sock) {
      
          ...
      
          // Add a 'close' event handler to this instance of socket
          sock.on('close', function(data) {
              let index = sockets.findIndex(function(o) {
                  return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
              })
              if (index !== -1) sockets.splice(index, 1);
              console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
          });
      });
      

      Here is the complete code for server.js:

      server.js

      const net = require('net');
      const port = 7070;
      const host = '127.0.0.1';
      
      const server = net.createServer();
      server.listen(port, host, () => {
          console.log('TCP Server is running on port ' + port + '.');
      });
      
      let sockets = [];
      
      server.on('connection', function(sock) {
          console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
          sockets.push(sock);
      
          sock.on('data', function(data) {
              console.log('DATA ' + sock.remoteAddress + ': ' + data);
              // Write the data back to all the connected, the client will receive it as data from the server
              sockets.forEach(function(sock, index, array) {
                  sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + 'n');
              });
          });
      
          // Add a 'close' event handler to this instance of socket
          sock.on('close', function(data) {
              let index = sockets.findIndex(function(o) {
                  return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
              })
              if (index !== -1) sockets.splice(index, 1);
              console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
          });
      });
      

      Save the file and then start the server again:

      We have a fully functional TCP Server running on our machine. Next we'll write a client to connect to our server.

      Step 2 — Creating a Node.js TCP Client

      Our Node.js TCP Server is running, so let's create a TCP Client to connect to the server and test the server out.

      The Node.js server you just wrote is still running, blocking your current terminal session. We want to keep that running as we develop the client, so open a new Terminal window or tab. Then connect into the server again from the new tab.

      Once connected, navigate to the tcp-nodejs-app directory:

      In the same directory, create a new file called client.js:

      The client will use the same net library used in the server.js file to connect to the TCP server. Add this code to the file to connect to the server using the IP address 127.0.0.1 on port 7070:

      client.js

      const net = require('net');
      const client = new net.Socket();
      const port = 7070;
      const host = '127.0.0.1';
      
      client.connect(port, host, function() {
          console.log('Connected');
          client.write("Hello From Client " + client.address().address);
      });
      

      This code will first try to connect to the TCP server to ensure that the server we created is running. Once the connection is established, the client will send "Hello From Client " + client.address().address to the server using the client.write function. Our server will receive this data and echo it back to the client.

      Once the client receives the data back from the server, we want it to print the server's response. Add this code to catch the data event and print the server's response to the command line:

      client.js

      client.on('data', function(data) {
          console.log('Server Says : ' + data);
      });
      

      Finally, handle disconnections from the server gracefully by adding this code:

      client.js

      client.on('close', function() {
          console.log('Connection closed');
      });
      
      

      Save the client.js file.

      Run the following command to start the client:

      The connection will establish and the server will recieve the data, echoing it back to the client:

      client.js Output

      Connected Server Says : 127.0.0.1:34548 said Hello From Client 127.0.0.1

      Switch back to the terminal where the server is running, and you'll see the following output:

      server.js Output

      CONNECTED: 127.0.0.1:34550 DATA 127.0.0.1: Hello From Client 127.0.0.1

      You have verified that you can establish a TCP connection between your server and client apps.

      Press CTRL+C to stop the server. Then switch to the other terminal session and press CTRL+C to stop the client. You can now disconnect this terminal session from your server and return to your original terminal session.

      In the next step we'll launch the server with PM2 and run it in the background.

      Step 3 — Running the Server with PM2

      You have a working server that accepts client connections, but it runs in the foreground. Let's run the server using PM2 so it runs in the backgrand and can restart gracefully.

      First, install PM2 on your server globally using npm:

      Once PM2 is installed, use it to run your server. Instead of running npm start to start the server, you'll use the pm2 command. Start the server:

      You'll see output like this:

      [secondary_label Output
      [PM2] Spawning PM2 daemon with pm2_home=/home/sammy/.pm2
      [PM2] PM2 Successfully daemonized
      [PM2] Starting /home/sammy/tcp-nodejs-app/server.js in fork_mode (1 instance)
      [PM2] Done.
      ┌────────┬──────┬────────┬───┬─────┬───────────┐
      │ Name   │ mode │ status │ ↺ │ cpu │ memory    │
      ├────────┼──────┼────────┼───┼─────┼───────────┤
      │ server │ fork │ online │ 0 │ 5%  │ 24.8 MB   │
      └────────┴──────┴────────┴───┴─────┴───────────┘
       Use `pm2 show <id|name>` to get more details about an app
      
      

      The server is now running in the background. However, if we reboot the machine, it won't be running anymore, so let's create a systemd service for it.

      Run the following command to generate and install PM2's systemd startup scripts. Be sure to run this with sudo so the systemd files install automatically.

      You'll see this output:

      Output

      [PM2] Init System found: systemd Platform systemd ... [PM2] Writing init configuration in /etc/systemd/system/pm2-root.service [PM2] Making script booting at startup... [PM2] [-] Executing: systemctl enable pm2-root... Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service. [PM2] [v] Command successfully executed. +---------------------------------------+ [PM2] Freeze a process list on reboot via: $ pm2 save [PM2] Remove init script via: $ pm2 unstartup systemd

      PM2 is now running as a systemd service.

      You can list all the processes PM2 is managing with the pm2 list command:

      You'll see your application in the list, with the ID of 0:

      Output

      ┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬───────┬──────────┐ │ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │ ├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼───────┼──────────┤ │ server │ 0 │ fork │ 9075 │ online │ 0 │ 4m │ 0% │ 30.5 MB │ sammy │ disabled │ └──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴───────┴──────────┘

      In the preceding output, you'll notice that watching is disabled. This is a feature that reloads the server when you make a change to any of the application files. It's useful in development, but we don't need that feature in production.

      To get more info about any of the running processes, use the pm2 show command, followed by its ID. In this case, the ID is 0:

      This output shows the uptime, status, log file paths, and other info about the running application:

      Output

      Describing process with id 0 - name server ┌───────────────────┬──────────────────────────────────────────┐ │ status │ online │ │ name │ server │ │ restarts │ 0 │ │ uptime │ 7m │ │ script path │ /home/sammy/tcp-nodejs-app/server.js │ │ script args │ N/A │ │ error log path │ /home/sammy/.pm2/logs/server-error-0.log │ │ out log path │ /home/sammy/.pm2/logs/server-out-0.log │ │ pid path │ /home/sammy/.pm2/pids/server-0.pid │ │ interpreter │ node │ │ interpreter args │ N/A │ │ script id │ 0 │ │ exec cwd │ /home/sammy/tcp-nodejs-app │ │ exec mode │ fork_mode │ │ node.js version │ 8.11.2 │ │ watch & reload │ ✘ │ │ unstable restarts │ 0 │ │ created at │ 2018-05-30T19:29:45.765Z │ └───────────────────┴──────────────────────────────────────────┘ Code metrics value ┌─────────────────┬────────┐ │ Loop delay │ 1.12ms │ │ Active requests │ 0 │ │ Active handles │ 3 │ └─────────────────┴────────┘ Add your own code metrics: http://bit.ly/code-metrics Use `pm2 logs server [--lines 1000]` to display logs Use `pm2 monit` to monitor CPU and Memory usage server

      If the application status shows an error, you can use the error log path to open and review the error log to debug the error:

      • cat /home/tcp/.pm2/logs/server-error-0.log

      If you make changes to the server code, you'll need to restart the application's process to apply the changes, like this:

      PM2 is now managing the application. Now we'll use Nginx to proxy requests to the server.

      Step 4 — Set Up Nginx as a Reverse Proxy Server

      Your application is running and listening on 127.0.0.1, which means it will only accept connections from the local machine. We will set up Nginx as a reverse proxy which will handle incoming traffic and direct it to our server.

      To do this, we'll modify the Nginx configuration to use the stream {} and stream_proxy features of Nginx to forward TCP connections to our Node.js server.

      We have to edit the main Nginx configuration file as the stream block that configures TCP connection forwarding only works as a top-level block. The default Nginx configuration on Ubuntu loads server blocks within the http block of the file, and the stream block can't be placed within that block.

      Open the file /etc/nginx/nginx.conf in your editor:

      • sudo nano /etc/nginx/nginx.conf

      Add the following lines at the end of your configuration file:

      /etc/nginx/nginx.conf

      
      ...
      
      stream {
          server {
            listen 3000;
            proxy_pass 127.0.0.1:7070;        
            proxy_protocol on;
          }
      }
      

      This listens for TCP connections on port 3000 and proxies the requests to your Node.js server running on port 7070. If your application is set to listen on a different port, update the proxy pass URL port to the correct port number. The proxy_protocol directive tells Nginx to use the PROXY protocol to send client information to backend servers, which can then process that information as needed.

      Save the file and exit the editor.

      Check your Nginx configuration to ensure you didn't introduce any syntax errors:

      Next, restart Nginx to enable the TCP and UDP proxy functionality:

      • sudo systemctl restart nginx

      Next, allow TCP connections to our server on that port. Use ufw to allow connections on port 3000:

      Assuming that your Node.js application is running, and your application and Nginx configurations are correct, you should now be able to access your application via the Nginx reverse proxy.

      Step 5 — Testing the Client-Server Connection

      Let's test the server out by connecting to the TCP server from our local machine using the client.js script. To do so, you'll need to download the client.js file you developed to your local machine and change the port and IP address in the script.

      First, on your local machine, download the client.js file using scp:

      • [environment local
      • scp sammy@your_server_ip:~/tcp-nodejs-app/client.js client.js

      Open the client.js file in your editor:

      • [environment local
      • nano client.js

      Change the port to 3000 and change the host to your server's IP address:

      client.js

      // A Client Example to connect to the Node.js TCP Server
      const net = require('net');
      const client = new net.Socket();
      const port = 3000;
      const host = 'your_server_ip';
      ...
      
      

      Save the file, exit the editor, and test things out by running the client:

      You'll see the same output you saw when you ran it before, indicating that your client machine has connected through Nginx and reached your server:

      client.js Output

      Connected Server Says : 127.0.0.1:34584 said PROXY TCP4 your_local_ip_address your_server_ip 52920 3000 Hello From Client your_local_ip_address

      Since Nginx is proxying client connections to your server, your Node.js server won't see the real IP addresses of the clients; it will only see Nginx's IP address. Nginx doesn't support sending the real IP address to the backend directly without making some changes to your system that could impact security, but since we enabled the PROXY protocol in Nginx, the Node.js server is now receiving an additional PROXY message that contains the real IP. If you need that IP address, you can adapt your server to process PROXY requests and parse out the data you need.

      You now have your Node.js TCP application running behind an Nginx reverse proxy and can continue to develop your server further.

      Conclusion

      In this tutorial you created a TCP application with Node.js, ran it with PM2, and served it behind Nginx. You also created a client application to connect to it from other machines. You can use this application to handle large chunks of data streams or to build real-time messaging applications.



      Source link