Go - Creating a TCP Server

发布时间 2023-10-16 11:13:01作者: ZhangZhihuiAAA

Problem: You want to create a TCP server to receive data from a TCP client.


Solution: Use the Listen function in the net package to listen for connections, then accept the connection using Accept.

 

TCP is a connection - oriented protocol at the transport layer. It ensures a reliable and ordered delivery of bidirectional data by managing message acknowledgments and sequences of data packets. As a result, it’s more reliable. When a TCP connection is established, it is maintained until the applications on both ends finish exchanging messages and close it. 

Sockets are application - level connections between two computers and represent endpoints for sending and receiving data to other programs across the network. Sockets abstract the complexities behind networking, allowing programmers to develop programs that communicate through the network. As a result, writing network programs are often about socket programming. 

There are three parts to a simple TCP server program:
1 Listen for incoming connections.
2 Accept the connection.
3 Read and optionally write data to the connection.

 

func   main ()   { 
      listener ,   err   :=   net . Listen ( "tcp" ,   "localhost:9000" ) 
      if   err   !=   nil   { 
          log . Fatal ( err ) 
      } 
      defer   listener . Close () 
      for   { 
          conn ,   err   :=   listener . Accept () 
          if   err   !=   nil   { 
              log . Fatal ( err ) 
          } 

          go   func ( c   net . Conn )   { 
              buf   :=   make ([] byte ,   1024 ) 
              _ ,   err   :=   c . Read ( buf ) 
              if   err   !=   nil   { 
                  log . Fatal () 
              } 
              log . Print ( string ( buf )) 
              conn . Write ([] byte ( "Hello  from  TCP  server" )) 
              c . Close () 
          }( conn ) 
      } 
}

First, you set up the server to listen to a socket using the net.Listen function. Sockets are identified by a combination of the transport protocol, IP address, and port number. Then you loop indefinitely to accept connections. When a connection comes, it is accepted and handled in a separate goroutine. The goroutine reads the data from the connection and prints it out. The connection is then closed. 

The net.Listen function returns a net.Listener interface, a generic network listener for stream - oriented protocols. The net.Listen function takes two arguments. The first is the network protocol, which is tcp in this case. The second is the address to listen on, which is in the form <host>:<port> . If the host is provided, the listener will listen only to that IP address. If it’s left empty, as in this case, for example, :9000 , it will listen on all available unicast and anycast IP addresses of the local system. Interestingly if it’s a single hostname it will also listen only for IPv4 traffic. If you leave it empty it will listen to both IPv4 and IPv6. If the port is 0, a random port is chosen and the Addr method of net.Listener can be used to retrieve the
port number . 

You read from the connection using the Read method. The Read method takes a byte slice as an argument and returns the number of bytes read and an error. The byte slice is used to store the data read from the connection.
You also write to the connection using the Write method. The Write method takes a byte slice as an argument and returns the number of bytes written and an error.

 

Here’s how this works. Start the server first:

$  go  run  main.go

Then you can use the nc (netcat) command to connect and send data to the server. In another terminal, run the following command as the client:

$  echo  "Hello  from  TCP  client"  |  nc  localhost  9000
Hello  from  TCP  server

Echo the string “Hello from TCP client” to the nc command, which sends it to the server. The server then prints out the string and sends back “Hello from TCP server” to the client. “Hello from TCP server” prints out on the client side.

You might wonder what happens if you have more than 1,024 bytes from the client. You can look until io.EOF is reached. Here is the snippet:

go   func ( c   net . Conn )   { 
      bytes   :=   [] byte {} 
      for   { 
          buf   :=   make ([] byte ,   32 ) 
          _ ,   err   :=   c . Read ( buf ) 
          if   err   !=   nil   { 
              if   err   ==   io . EOF   { 
                  break 
              }   else   { 
                  log . Fatal ( err ) 
              } 
          } 
          bytes   =   append ( bytes ,   buf ... ) 
      } 
      log . Print ( string ( bytes )) 
      _ ,   err   =   conn . Write ([] byte ( "Hello  from  TCP  server" )) 
      if   err   !=   nil   { 
          log . Fatal ( err ) 
      } 
      c . Close () 
}( conn )

You will loop until all the data from the client is read (and therefore io.EOF is encountered). 

You’re sending data from the nc client using IPv4. If you want to use IPv6, you can use the - 6 flag at the client but you also need to change the listener:

listener ,   err   :=   net . Listen ( "tcp" ,   ":9000" )

If you do this, you can use the nc command to connect to the server using IPv6:

$  echo  "Hello  from  TCP  client"  |  nc  - 6  localhost  9000
Hello  from  TCP  server

The net.Conn interface returned by the Accept method of net.Listener represents a generic stream - oriented network connection. It is an abstraction of a network connection and has Read and Write methods, meaning it is both a Reader and Writer . 

Both net.Listener and net.Conn are interfaces, and in this case, they are implemented by the net.TCPListener and the netTCPConn structs, respectively. Instead of using the net.Listen and Accept functions, you could have created the ne⁠t.T⁠CPLi⁠st⁠en⁠er and net.TCPConn structs directly using the net.ListenTCP and AcceptTCP functions. However, the net.Listen and Accept functions are more convenient and portable:

func   main ()   { 
      addr ,   err   :=   net . ResolveTCPAddr ( "tcp" ,   ":9000" ) 
      if   err   !=   nil   { 
          log . Fatal ( err ) 
      } 
      listener ,   err   :=   net . ListenTCP ( "tcp" ,   addr ) 
      if   err   !=   nil   { 
         log . Fatal ( err ) 
      } 
      defer   listener . Close () 
      for   { 
          conn ,   err   :=   listener . AcceptTCP () 
          if   err   !=   nil   { 
              log . Fatal ( err ) 
          } 
          go   func ( c   net . Conn )   { 
              buf   :=   make ([] byte ,   1024 ) 
              _ ,   err   :=   c . Read ( buf ) 
              if   err   !=   nil   { 
                  log . Fatal () 
              } 
              log . Print ( string ( buf )) 
              conn . Write ([] byte ( "Hello  from  TCP  server" )) 
              c . Close () 
          }( conn ) 
      } 
}

It might seem redundant that you have more than one way of creating a TCP server. The net.Listen and Accept functions are more convenient and simpler to use. net.ListenTCP and AcceptTCP are more verbose but give you more control over the connection; for example, you could set the KeepAlive property of the connection to true to keep the TCP connection alive longer.