This is a creation in Article, where the information may have evolved or changed.
What's wasting today is tomorrow to those who died yesterday; What's the future of hate now?
What you are wasting today is the tomorrow that the man who died yesterday expects, and what you dislike is now the future you will never go back to.
How do I send data from process A to process B with a simple TCP/IP connection?
In many cases, using a higher-level network protocol will undoubtedly do better, hiding all the technical details under a fancy API. And there is already a lot to choose from, depending on the need: Message Queuing protocol, grpc,protobuf,flatbuffers,restful Web Api,websockets, and so on.
However, in some cases (especially in small projects), any method you choose may seem completely oversized.
1. Connections is an IO stream
Net. The conn implements IO. Reader, Io. Writer, Io. Closer interface. So we're going to use the TCP link just like the IO stream.
First, let's look at the definition of these three types in the IO package of the Golang source code:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
Then look at the definition of the Golang source NET package conn Type:
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
LocalAddr() Addr
RemoteAddr() Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
So we can send string strings over a TCP link, but how to send a complex type?
2. Go Coding Complex Types
JSON is easy to think of when it comes to sending structured data over the network, but go itself provides a GOB package that operates directly on IO stream data, serializes and deserializes data, does not require JSON to add tags, and then laborious JSON. Unmarshal () into binary data.
3. Basic elements for sending string data over TCP:
1. Sending party
1. Open a link to a receiving process
2. Writing a string
3. Close the link
The Golang NET package already provides all of the above methods.
RESOLVETCPADDR () accepts a string representing the TCP address (localhost, 127.0.0.1:80, [:: 1]:80 all represent local 80 ports), and returns a net. TCPADDR (), if this address cannot be resolved, returns an error.
DIALTCP () accepts a net. ADDR () then connects to this address and returns an open net after success. Tcpconn the linked object.
If we don't need to be more detailed about dialing settings. We can use net directly. Dial to replace.
If the link succeeds, the linked object can be encapsulated as a bufio. Readwriter,
type ReadWriter struct {
*Reader
*Writer
}
We can read the data using the ReadString () writestring () Readbytes () method
Note that buffered writes need to be called flush () after writing to forward all data to the underlying network connection
2. Sending party
1. Start listening to local ports
2. When a request is accepted, a goroutine is initiated to process the request
3. Read the data in this goroutine, optionally send a response.
4. Close the link
4. Processing of complex types
The service side gives the object's handling based on the requested data type. How to run it briefly:
Step One: When Listen () receives a new link, a new goroutine is generated to execute the request method of the corresponding data type Handlemessage (). The function reads the command name from the connection, finds the appropriate handler function from the map, and invokes the function.
Part Two: The selected handler reads and processes the request data.
Detailed Description:
Request data type and the corresponding processing method----new Goroutine--
The detailed code:
1. Project directory Schema
2. library files
Package Lib
Import (
"Bufio"
"Net"
"github.com/pkg/errors"
"FMT"
"Sync"
"IO"
"Strings"
"encoding/gob"
)
//Mixed type struct
type ComplexData struct{
N int
S string
M map[string]int
P []byte
C *ComplexData
}
Const (
Port = ": 61000" / / the port accepted by the server
)
* *
Net.conn implements io.reader io.writer io.closer interface
Open returns a TCP link buffer with timeout readwrite
* /
func Open(addr string) (*bufio.ReadWriter, error) {
// Dial the remote process.
// Note that the local port is chosen on the fly. If the local port
// must be a specific one, use DialTCP() instead.
fmt.Println("Dial " + addr)
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, errors.Wrap(err, "Dialing "+addr+" failed")
}
return bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)), nil
}
type HandleFunc func(*bufio.ReadWriter)
type EndPoint struct{
listener net.Listener
//Handlefunc is a function type that handles incoming commands. It receives open connections packaged in a reader writer interface.
handler map[string]HandleFunc
//Map is not thread safe, so read-write lock control is required
m sync.RWMutex
}
func NewEndPoint() *EndPoint{
return &EndPoint{
handler:map[string]HandleFunc{},
}
}
//Add data type processing method
func (e *EndPoint)AddHandleFunc(name string , f HandleFunc){
E.m.Lock ()
e.handler[name] = f
E.m.Unlock ()
}
//Verify the request data type and send it to the corresponding processing function
func (e *EndPoint)handleMessage(conn net.Conn){
rw := bufio.NewReadWriter(bufio.NewReader(conn),
bufio.NewWriter(conn))
defer conn.Close()
For{
cmd, err := rw.ReadString('\n')
Switch {
case err == io.EOF:
FMT. Println ("read complete.")
Return
case err != nil:
Fmt.println ("read error")
Return
}
cmd = strings.Trim(cmd, "\n ")
E.m.RLock ()
handleCmd , ok := e.handler[cmd]
If! Ok{
FMT. Println ("unregistered request data type.")
Return
}
//Specific processing of linked data
handleCmd(rw)
}
}
func (e *EndPoint) Listen()error{
var err error
e.listener, err = net.Listen("tcp", Port)
if err != nil{
Return errors.wrap (err, "TCP service cannot listen on port" + port)
}
FMT. Println ("service listening succeeded:", e.listener. Addr(). String())
For{
conn, err := e.listener.Accept()
if err != nil{
FMT. Println ("heart request listening failed!")
Continue
}
//Start processing new linked data
go e.handleMessage(conn)
}
}
func HandleStrings(rw *bufio.ReadWriter){
s, err := rw.ReadString('\n')
if err!= nil{
FMT. Println ("link unreadable.")
Return
}
s = strings.Trim(s , "\n ")
/ /...
_, err = rw.writestring ("processing completed... \ n")
if err != nil{
Fmt.println ("link write response failed")
Return
}
//Write underlying network link
err = rw.Flush()
if err != nil{
Fmt.println ("flush write failed")
Return
}
}
func HandleGob(rw *bufio.ReadWriter){
var data ComplexData
dec := gob.NewDecoder(rw)
err := dec.Decode(&data)
if err != nil{
FMT. Println ("unresolved binary data.")
Return
}
FMT. Println ("output:", data, data. C)
}
3. Service files
Server.go
package main
import(
. "tcpNetWorking/lib"
"fmt"
"github.com/pkg/errors"
)
func server()error{
endpoint := NewEndPoint()
endpoint.AddHandleFunc("string", HandleStrings)
endpoint.AddHandleFunc("gob", HandleGob)
// start to listen
return endpoint.Listen()
}
func main(){
err := server()
if err != nil {
fmt.Println("Error:", errors.WithStack(err))
}
}
Client.go
Package main
Import (
"FMT"
. "tcpNetWorking/lib"
"github.com/pkg/errors"
"encoding/gob"
"Strconv"
"Log"
)
func client(ip string) error {
cpData := ComplexData{
N: 10,
S: "Test string data",
M: map[string]int{"A": 1, "B": 2},
P: [] byte ("test [] byte data"),
C: &ComplexData{
N: 256,
S: "Recursive structs? Piece of cake!",
M: map[string]int{"01": 1, "10": 2, "11": 3},
}
}
rw, err := Open(ip + Port)
if err != nil {
Fmt.Println ("client cannot link to change address:" + IP + Port)
Return err
}
n, err := rw.WriteString("string\n")
if err != nil {
return errors.Wrap(err, "Could not send the STRING request ("+strconv.Itoa(n)+" bytes written)")
}
n, err = rw.WriteString("Additional data.\n")
if err != nil {
return errors.Wrap(err, "Could not send additional STRING data ("+strconv.Itoa(n)+" bytes written)")
}
err = rw.Flush()
if err != nil {
return errors.Wrap(err, "Flush failed.")
}
// Read the reply.
response, err := rw.ReadString('\n')
if err != nil {
return errors.Wrap(err, "Client: Failed to read the reply: '"+response+"'")
}
log.Println("STRING request: got a response:", response)
log.Println("Send a struct as GOB:")
log.Printf("Outer complexData struct: \n%#v\n", cpData)
log.Printf("Inner complexData struct: \n%#v\n", cpData.C)
enc := gob.NewEncoder(rw)
n, err = rw.WriteString("gob\n")
if err != nil {
return errors.Wrap(err, "Could not write GOB data ("+strconv.Itoa(n)+" bytes written)")
}
err = enc.Encode(cpData)
if err != nil {
return errors.Wrapf(err, "Encode failed for struct: %#v", cpData)
}
err = rw.Flush()
if err != nil {
return errors.Wrap(err, "Flush failed.")
}
Return nil
}
Func main () {
err := client("localhost")
if err != nil {
fmt.Println("Error:", errors.WithStack(err))
}
}
The logic is basically the same as the Web routing service I wrote earlier, except that data processing uses the GOB package binary form. Take a look at it, please.
https://my.oschina.net/90design/blog/1604539