Network IO-TCPIP protocol stack

This document is to record the specific tracking process for the concept

A TCP handshake/wave

1 server code

package debug.io.bio.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Objects;

/**
 * <p>BIO的服务端</p>
 * @since 2022/5/20
 * @author dingrui
 */
public class ServerSocketTest {

    private static final int PORT = 9992;

    public static void main(String[] args) throws IOException {
        // 创建服务端
        ServerSocket server = new ServerSocket();
        // 绑定端口
        server.bind(new InetSocketAddress(PORT));
        // 监听端口
        Socket socket = server.accept();
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter writer = new PrintWriter(socket.getOutputStream());
        writer.println("服务端 -> 连接ok");
        writer.flush();

        String msg;
        while (!"bye".equalsIgnoreCase(msg = reader.readLine()) && Objects.nonNull(msg)) {
            System.out.println("客户端 <- " + msg);
            writer.println("服务端 -> " + msg);
            writer.flush();
            msg = null;
        }
        reader.close();
        writer.close();
        socket.close();
        server.close();
    }
}

2 client code

package debug.io.bio.client;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;

/**
 * <p>BIO客户端</p>
 * @since 2022/5/20
 * @author dingrui
 */
public class SocketTest {

    private static final String IP = "127.0.0.1";
    private static final int PORT = 9992;

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(IP, PORT));
        PrintWriter writer = new PrintWriter(socket.getOutputStream());
        Scanner scanner = new Scanner(System.in);
        String readLine = "";
        // 线程负责读
        Thread t = new Thread(() -> {
            int size = -1;
            byte[] bytes = new byte[1024];
            StringBuilder sb = new StringBuilder(1024);
            try {
                while ((size = socket.getInputStream().read(bytes, 0, bytes.length)) > 0) {
                    String msg = new String(bytes, 0, size, "UTF-8");
                    sb.append(msg);
                    if (msg.lastIndexOf("\n") > 0) {
                        System.out.println(sb.toString());
                        sb.delete(0, sb.length());
                    }
                    if (Thread.currentThread().isInterrupted()) break;
                }
            } catch (Exception ignored) {
            }
        });
        t.start();

        // main线程写
        while (!readLine.equalsIgnoreCase("bye")) {
            readLine = scanner.nextLine();
            writer.println(readLine);
            writer.flush();
        }

        scanner.close();
        writer.close();
        socket.close();
        t.interrupt();
    }
}

3 Network capture

3.1 Command Tool

man tcpdump
The general format of a TCP protocol line is:
              src > dst: Flags [tcpflags], seq data-seqno, ack ackno, win window, urg urgent, options [opts], length len
       Src  and  dst  are  the  source and destination IP addresses and ports.  Tcpflags are some combination of S (SYN), F
       (FIN), P (PUSH), R (RST), U (URG), W (ECN CWR), E (ECN-Echo) or `.' (ACK), or `none' if no  flags  are  set.   Data-
       seqno  describes  the  portion  of  sequence space covered by the data in this packet (see example below).  Ackno is
       sequence number of the next data expected the other direction on this connection.  Window is the number of bytes  of
       receive  buffer space available the other direction on this connection.  Urg indicates there is `urgent' data in the
       packet.  Opts are TCP options (e.g., mss 1024).  Len is the length of payload data.

3.2 Network Interface Query

sudo tcpdump -D
1.en0 [Up, Running]
2.awdl0 [Up, Running]
3.llw0 [Up, Running]
4.utun0 [Up, Running]
5.utun1 [Up, Running]
6.utun2 [Up, Running]
7.lo0 [Up, Running, Loopback]
8.bridge0 [Up, Running]
9.en1 [Up, Running]
10.en2 [Up, Running]
11.en3 [Up, Running]
12.en4 [Up, Running]
13.pktap0 [Up]
14.gif0 [none]
15.stf0 [none]
16.XHC0 [none]
17.XHC1 [none]
18.ap1 [none]
19.XHC20 [none]
20.VHC128 [none]

3.3 Capture packets

sudo tcpdump -nn -i lo0 port 9992
客户端->服务端 SYN包 第一次握手
20:01:36.652605 IP 127.0.0.1.62866 > 127.0.0.1.9992: Flags [S], seq 2417007309, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 1282302987 ecr 0,sackOK,eol], length 0

服务端->客户端 SYN ACK包 第二次握手
20:01:36.652676 IP 127.0.0.1.9992 > 127.0.0.1.62866: Flags [S.], seq 149762789, ack 2417007310, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 1504529314 ecr 1282302987,sackOK,eol], length 0

客户端->服务端 ACK包 第三次握手
20:01:36.652707 IP 127.0.0.1.62866 > 127.0.0.1.9992: Flags [.], ack 1, win 6379, options [nop,nop,TS val 1282302987 ecr 1504529314], length 0

20:01:36.652720 IP 127.0.0.1.9992 > 127.0.0.1.62866: Flags [.], ack 1, win 6379, options [nop,nop,TS val 1504529314 ecr 1282302987], length 0

20:01:36.654206 IP 127.0.0.1.9992 > 127.0.0.1.62866: Flags [P.], seq 1:23, ack 1, win 6379, options [nop,nop,TS val 1504529315 ecr 1282302987], length 22
20:01:36.654227 IP 127.0.0.1.62866 > 127.0.0.1.9992: Flags [.], ack 23, win 6379, options [nop,nop,TS val 1282302988 ecr 1504529315], length 0
20:01:44.031065 IP 127.0.0.1.62866 > 127.0.0.1.9992: Flags [P.], seq 1:5, ack 23, win 6379, options [nop,nop,TS val 1282310317 ecr 1504529315], length 4
20:01:44.031132 IP 127.0.0.1.9992 > 127.0.0.1.62866: Flags [.], ack 5, win 6379, options [nop,nop,TS val 1504536644 ecr 1282310317], length 0

客户端->服务端 FIN包 第一次挥手
20:01:44.031836 IP 127.0.0.1.62866 > 127.0.0.1.9992: Flags [F.], seq 5, ack 23, win 6379, options [nop,nop,TS val 1282310317 ecr 1504536644], length 0

服务端->客户端 ACK包 第二次挥手
20:01:44.031862 IP 127.0.0.1.9992 > 127.0.0.1.62866: Flags [.], ack 6, win 6379, options [nop,nop,TS val 1504536644 ecr 1282310317], length 0

服务端->客户端 FIN包 第三次挥手
20:01:44.032483 IP 127.0.0.1.9992 > 127.0.0.1.62866: Flags [F.], seq 23, ack 6, win 6379, options [nop,nop,TS val 1504536645 ecr 1282310317], length 0

客户端->服务端 ACK包 第四次挥手
20:01:44.032565 IP 127.0.0.1.62866 > 127.0.0.1.9992: Flags [.], ack 24, win 6379, options [nop,nop,TS val 1282310318 ecr 1504536645], length 0

The above can clearly see the so-called 3-way handshake and 4-way wave of tcp connection and disconnection

  1. The seq in the tcp packet is random
  2. Confirmation of the package ack=seq+1
  3. During the sending process, tell each other the win information, and solve the sending rate problem in the network congestion scenario through the window mechanism

After trying several times to capture packets, I found that after the 3 handshakes, after the two parties establish a tcp connection, the server will send an empty ack packet immediately, and I will save this place and come back to fill in the pit (todo)

Two Socket

1 server code

package debug.io.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *
 * @since 2022/5/20
 * @author dingrui
 */
public class ServerTest {

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(9993);
        System.out.println("new了一个服务端");

        System.in.read();

        while (true) {
            // 接收客户端连接 阻塞
            Socket socket = server.accept();
            new Thread(() -> {
                try {
                    InputStream in = socket.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    while (true) {
                        // 读取 阻塞
                        System.out.println(reader.readLine());
                    }
                } catch (Exception ignored) {
                }
            }).start();
        }
    }
}

We block the code after creating the ServerSocket, that is to say, we only do one thing at the program level, and we observe the phenomenon to deduce what the kernel has done

2 client code

package debug.io.socket;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * @since 2022/5/20
 * @author dingrui
 */
public class ClientTest {

    private static final String IP = "127.0.0.1";
    private static final int PORT = 9993;

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(IP, PORT));
        PrintWriter writer = new PrintWriter(socket.getOutputStream());
        String msg = "hello, this is from: " + Thread.currentThread().getId();
        writer.println(msg);
        writer.flush();

        System.in.read();

        writer.close();
        socket.close();
    }
}

Under normal circumstances, after the client tries to connect with the server tcp, it sends a data, and then blocks here, keeping the connection state

3 The terminal checks the file descriptor and network status

jpsView the process number corresponding to the service

sudo lsof -p {pid}View process number information

3.1 Only start the server

Server

The current service is listening on port 9993

3.2 Start a client

Server

client

netstat

It is clear

  1. On the server side, you can still only see that the server program is listening to the port, but you cannot see that the server has established a tcp connection with a certain client.
  2. On the client side, you can see a tcp connection with the server, local: 51466->local: 9993
  3. The server has actually received the message sent by the client and put it in Recv-Q

3.3 Start another client

Server

client

netstat

Learning address: Dpdk/network protocol stack/vpp/OvS/DDos/NFV/virtualization/high performance expert (free subscription, permanent learning)

[Article benefits] Need more DPDK/SPDK learning materials to add group 793599096 (materials include C/C++, Linux, golang technology, kernel, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, CDN, P2P, K8S, Docker , TCP/IP, coroutines, DPDK, Dachang interview questions, etc.) You can add learning exchange groups by yourself, click here ~

As expected

  1. The server side still cannot see the established connection
  2. The new client can see that a tcp connection has been established with the server
  3. The client has sent a message to the server, the server has also received it, and put it in Recv-Q

4 Let go of server-side code blocking

The program can continue to execute. In polling, the connection information is obtained from the kernel whilethrough system calls twice , which is actually the encapsulation of the above two file descriptors 9u and 10userver.accept()

Regardless of whether the program explicitly applies to the OS through code, these two sockets really exist

5 quadruples

At this point, we can understand the essence of socket again

Socket is a quadruple. The unique combination of source and destination ip and port is socket. For os, it must also be a resource.

Looking back at the traceback process above, before the server program has been blocked Socket socket = ServerSocket#accept(), although the program did not continue to execute, the client and server have established a tcp connection normally, and sent a message to complete the communication

To sum it up is

  1. The tcp connection is related to the source and target (ip, port). Given the combination of these two tuples, the os can create resources (the essence of the resource should be the fd identified by the os and the allocated memory)
  2. The message will be stored in the corresponding memory cache area, and these operations are entrusted to the kernel of the OS to complete
  3. The user program Socket socket = ServerSocket#accept()does not interfere with tcp itself, but obtains the fd of the established tcp connection from the kernel of the os through sc, and then java encapsulates an fd of the established tcp connection returned by the kernel into a Socket object
  4. After that, the user program relies on the operation (reading and writing) of the scoket object to obtain interactive memory information with the kernel (Recv-Q, Send-Q)

6 ServerSocket construction parameter backlog

public ServerSocket(int port, int backlog) throws IOException {
    this(port, backlog, null);
}

ServerSocketThe construction method provides backlogparameters. According to the doc description, requested maximum length of the queue of incoming connectionsit can be seen that a connection threshold is set, and the maximum number of connection requests supported = backlog+1

code verification

package debug.io.socket;

import java.io.IOException;
import java.net.ServerSocket;

/**
 *
 * @since 2022/5/21
 * @author dingrui
 */
public class ServerConnectTest {

    public static final int PORT = 9993;

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(PORT, 3);
        System.out.println("new了一个服务端");
        System.in.read();
    }
}

package debug.io.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 *
 * @since 2022/5/21
 * @author dingrui
 */
public class ClientConnectTest {

    public static void main(String[] args) throws IOException {
        for (int i = 0; i < 51; i++) {
            Socket socket = new Socket();
            // 轮询向服务请求连接 当达到服务端设置的backlog阈值后 阻塞住
            socket.connect(new InetSocketAddress(ServerTest.PORT));
            System.out.println("申请向服务端的连接");
        }
    }
}

If the value is set backlogto 3, the client code polls the port to be monitored and socket.connect()blocks after reaching the threshold

Reposted from: https://zhuanlan.zhihu.com/p/588111102

Guess you like

Origin blog.csdn.net/lingshengxiyou/article/details/130158689