A comparative analysis of golang socket and Linux socket

Keywords: Go socket network Linux Programming

After posix standard was introduced, socket has been well supported on all major OS platforms. Golang is a cross platform programming language with runtime. The socket API provided to developers in Go is based on the native socket interface of the operating system. But the behavior of socket interface in golang is different from that of native interface of operating system. In this paper, a simple hello/hi network chat program will be analyzed.

I. Introduction to socket

Firstly, the premise that processes can communicate with each other is that processes can be uniquely identified, and PID can be used for local communication. However, this method is not feasible in the network. We can uniquely identify a process through IP address + protocol + port number, and then use socket for communication.

socket is an abstract layer in the application layer and transport layer. It does not belong to the seven layer architecture:

                                                     

The socket communication process is as follows:

1. Server creates socket

2. The server binds socket and port number

3. The server listens for the port number

4. The server starts accept() to receive the connection request from the client. If there is a connection, it will continue to execute. Otherwise, it will block here.

5. Client creates socket

6. The client connects to the server through IP address and port number, that is, three handshakes in tcp

7. If the connection is successful, the client can send data to the server

8. The server reads the data sent by the client

9. Any end can be disconnected actively

                                                     

socket programming

With the abstract socket, when using the TCP or UDP protocol for web programming, you can do it in the following ways

Server pseudo code:

listenfd = socket(......)
bind(listenfd, ServerIp:Port, ......)
listen(listenfd, ......)
while(true) {
  conn = accept(listenfd, ......)
  receive(conn, ......)
  send(conn, ......)
}

Client pseudocode:

clientfd = socket(......)
connect(clientfd, serverIp:Port, ......)
send(clientfd, data)
receive(clientfd, ......)
close(clientfd)

In the above pseudo code, listenfd is to realize the socket descriptor created by the server listening, and bind method is to use the port by the server process to avoid other ports being used by other processes. Listen method starts to listen to the port. The following while loop is used to process the client's endless requests. The accept method returns a conn, which is used to distinguish the connections of each client. The subsequent receiving and sending actions are based on this conn. In fact, accept is to complete the three handshakes of TCP with the client's connect.

3. socket in golang

golang provides some network programming API s, including Dial,Listen,Accept,Read,Write,Close, etc

3.1 Listen()

First, use the server net.Listen() method to create socket, bind port and listen port.

1 func Listen(network, address string) (Listener, error) {
2     var lc ListenConfig
3     return lc.Listen(context.Background(), network, address)
4 }

The above is the source code of Listen function provided by golang, where network represents network protocol, such as tcp,tcp4,tcp6,udp,udp4,udp6, etc. Address is the binding address. The Listener returned is actually a socket descriptor. Error stores the error information.

In Linux socket, socket,bind and listen functions are used to perform the same functions

// socket(Protocol domain, socket type, protocol)
int socket(int domain, int type, int protocol);

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

int listen(int sockfd, int backlog);

3.2 Dial()

When the client wants to initiate more than one connection, it will use the net.Dial() method to initiate the connection

func Dial(network, address string) (Conn, error) {
    var d Dialer
    return d.Dial(network, address)
}

Where network is the network protocol, and address is the address to establish the connection. The returned conn actually identifies each client. An interface of Conn is defined in golang:

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
}

type conn struct {
    fd *netFD
}

netFD is the core data structure in the golang network library. It runs through all the API s of the golang network library, encapsulates the underlying socket, and shields the network implementation of different operating systems. Thus, through the returned Conn, we can use the socket underlying function provided by golang.

Use the connect function in Linux socket to create a connection

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

3.3 Accept()

When the server calls net.Listen(), it will start to listen to the specified address, and when the client calls net.Dial(), it will initiate a connection request, and then the server calls net.Accept() to receive the request. Here, the connection between the end and the end is established. In fact, at this step, the three handshakes in TCP are completed.

Accept() (Conn, error)

Golang's socket is actually non blocking, but golang itself does some processing on the socket to make it appear to be blocking.

Use the accept function in Linux socket to achieve the same function

//sockfd is the server socket descriptor, sockaddr returns the protocol address of the client, and socken_ltis the length of the protocol address.
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

3.4 Write()

The connection between the end and the end has been established. Next, read and write operations are started. conn.Write() writes data to the socket

   Write(b []byte) (n int, err error)
func (c *conn) Write(b []byte) (int, error) {
    if !c.ok() {
        return 0, syscall.EINVAL
    }
    n, err := c.fd.Write(b)
    if err != nil {
        err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
    }
    return n, err
}

The data written in is a binary byte stream. n is the length of the returned data. err stores the error information

The corresponding function in Linux socket is the send function

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

3.5 Read()

After the client sends the data, the server can receive data, and conn.Read() is called in golang to read the data. The source code is as follows:

Read(b []byte) (n int, err error)
func (c *conn) Read(b []byte) (int, error) {
    if !c.ok() {
        return 0, syscall.EINVAL
    }
    n, err := c.fd.Read(b)
    if err != nil && err != io.EOF {
        err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
    }
    return n, err
}

Use recv function in Linux socket to complete this function

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

3.6 Close()

When the server or client wants to close the socket, call the Close() method to close the connection.

Close() error
func (c
*conn) Close() error { if !c.ok() { return syscall.EINVAL } err := c.fd.Close() if err != nil { err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} } return err }

Use the close function in Linux socket

int close(int socketfd)

4. Implementation of Hello/hi chat program in golang

4.1 server.go

package main
import (
    "fmt"
    "net"
    "strings"
)
//UserMap Save all users of the current chat room id Set
var UserMap map[string]net.Conn = make(map[string]net.Conn)
func main() {
    //Monitor local ownership ip 8000 port of
    listen_socket, err := net.Listen("tcp", "127.0.0.1:8000")
    if err != nil {
        fmt.Println("Service start failed")
    }
    //Turn off listening port
    defer listen_socket.Close()
    fmt.Println("Waiting for users to join the chat room")
    for {
        //Be used for conn Receiving link
        conn, err := listen_socket.Accept()
        if err != nil {
            fmt.Println("connection failed")
        }
        //Print the public network to join the chat room IP address
        fmt.Println(conn.RemoteAddr(), "Successful connection")
        //Define a goroutine,This is mainly for concurrent operation
        go DataProcessing(conn)
    }
}
func DataProcessing(conn net.Conn) {
    for {
        //Define a slice with a length of 255
        data := make([]byte, 255)
        //Read the data from the client, msg_length Save length, err Save error message
        msg_length, err := conn.Read(data)
        if msg_length == 0 || err != nil {
            continue
        }
        //Parsing protocol,By separator"|"Get the data you need,msg_str[0]Storage operation category
        //msg_str[1]Store user name, msg_str[2]Store messages if any
        msg_str := strings.Split(string(data[0:msg_length]), "|")
        switch msg_str[0] {
        case "nick":
            fmt.Println(conn.RemoteAddr(), "The user name of is", msg_str[1])
            for user, message := range UserMap {
                //Send messages to users other than yourself to join the chat room
                if user != msg_str[1] {
                    message.Write([]byte("user" + msg_str[1] + "Join chat"))
                }
            }
            //Add the user to the user id Set
            UserMap[msg_str[1]] = conn
        case "send":
            for user, message := range UserMap {
                if user != msg_str[1] {
                    fmt.Println("Send "+msg_str[2]+" to ", user)
                    //Send chat messages to users other than yourself
                    message.Write([]byte("       user" + msg_str[1] + ": " + msg_str[2]))
                }
            }
        case "quit":
            for user, message := range UserMap {
                if user != msg_str[1] {
                    //Send to users other than yourself the disappearance of exiting chat room
                    message.Write([]byte("user" + msg_str[1] + "Exit chat"))
                }
            }
            fmt.Println("user " + msg_str[1] + "Exit chat")
            //Remove the user name from the user id Delete from collection of
            delete(UserMap, msg_str[1])
        }
    }
}

5.2 client.go

package main
import (
    "bufio"
    "fmt"
    "net"
    "os"
)
var nick string = ""
func main() {
    //Dialing operation
    conn, err := net.Dial("tcp", "127.0.0.1:8000")
    if err != nil {
        fmt.Println("connection failed")
    }
    defer conn.Close()
    fmt.Println("Successfully connected to the service \n")
    //Create user name
    fmt.Printf("Give yourself a name before you enter the chat room:")
    fmt.Scanf("%s", &nick)
    fmt.Println("user" + nick + "Welcome to chat room")
    //Send data to server
    conn.Write([]byte("nick|" + nick))
    //Define a goroutine,This is mainly for concurrent operation
    go SendMessage(conn)
    var msg string
    for {
        msg = ""
        //Because golangz Of fmt The package input string cannot read spaces, so a Scanf function
        Scanf(&msg)
        if msg == "quit" {
            //There quit,send,And the top nick To identify whether the client is setting the user name, sending a message or quitting
            conn.Write([]byte("quit|" + nick))
            break
        }
        if msg != "" {
            conn.Write([]byte("send|" + nick + "|" + msg))
        }
    }
}
func SendMessage(conn net.Conn) {
    for {
        //Define a slice with a length of 255
        data := make([]byte, 255)
        //Read the data from the server, msg_length Save length, err Save error message
        msg_length, err := conn.Read(data)
        if msg_length == 0 || err != nil {
            break
        }
        fmt.Println(string(data[0:msg_length]))
    }
}
//Rewritten Scanf function
func Scanf(a *string) {
    reader := bufio.NewReader(os.Stdin)
    data, _, _ := reader.ReadLine()
    *a = string(data)
}

Using goroutine to achieve concurrency in golang

5.3 operation screenshot

Screenshot of multi person chat (server in the upper left corner)

The user exits the chat room (the upper left corner is the server)

 

Reference:

     https://tonybai.com/2015/11/17/tcp-programming-in-golang/

     https://www.jianshu.com/p/325ac02fc31c

     https://blog.csdn.net/dyd961121/article/details/81252920

Posted by art15 on Mon, 09 Dec 2019 18:41:31 -0800