TCP Streaming Socket Programming

Time:2024-1-24
Article Catalog

preamble

Previously we learned to use UDP datagrams to implement socket programming, because there are some differences between UDP and TCP, so there will be some different practices in the implementation of TCP stream socket programming, this article I will share how to use TCP stream to implement socket programming.

Comparison of TCP and UDP features

To implement socket programming for TCP streams you need to know the difference between TCP and UDP first.
  1. TCP is connected, UDP is connectionless.
  2. TCP is a reliable transport and UDP is an unreliable transport.
  3. TCP is byte stream oriented and UDP is datagram oriented.
  4. Both TCP and UDP are full duplex.
The difference between the implementation of TCP stream socket programming and UDP datagram socket programming is mainly reflected in the fact that TCP is connected and UDP is connectionless; TCP is byte stream oriented and UDP is datagram oriented.

TcpEchoServer server-side implementation

1. Create the ServerSocket class to realize the communication between the two sides to establish a connection.

To realize TCP communication, you first need to establish a connection between the two communicating parties. In Java, a TCP connection is established between two parties byServerSocket Interface.ServerSocket The underlying layer is similar to ablocking queue, when the client and server have established a connection, theServerSocket The kernel stores these established connections in order, and when the two parties need to communicate, the server takes the connection out of the kernel and communicates.
public class TcpEchoServer {
    ServerSocket serverSocket = null;
    
    public TcpEchoServer(int port) throws IOException {
    	//Server needs to specify the port number
        serverSocket = new ServerSocket(port);
    }
}

2. Check out the established connection to enable communication between the two parties

pass (a bill or inspection etc)accept() method can take out the established connection for communication between the two parties.
public void start() throws IOException {
	System.out.println("Server Started");
    while (true) {
        Socket clientSocket = serverSocket.accept();
        //Communication
        processConnection(clientSocket);
    }
}

3. Server-side business logic realization

TCP is byte-stream oriented, unlike UDP, which relies on datagrams, so it is necessary to use the previous file manipulationInputStream respond in singingOutputStream to read the request and send the response.
public void processConnection(Socket clientSocket) {
	//Prompting the client to come online
    System. The out. Printf (" % d [% s] customer ends \ n ", clientSocket. GetInetAddress (),
            clientSocket.getPort());
    try (InputStream inputStream = clientSocket.getInputStream();
         OutputStream outputStream = clientSocket.getOutputStream()) {
            
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
Reading byte data can be cumbersome, so you can use theScanner to simplify the reading process. scanner.next() method reads to the end of a blank character (space, carriage return, tab), which corresponds exactly to the end of our client input request with a blank character.
Scanner scanner = new Scanner(inputStream);
while (true) {
	if (!scanner.hasNext()) {
        System. Out. Printf (" % d [% s] client referrals ", clientSocket. GetInetAddress (),
        clientSocket.getPort());
        break;
    }
    String request = scanner.next();
}
process(String request) method implements the processing of the requested data.
String response = process(request);

public String process(String request) {
    return request;
}
When the server receives theprocess() method, you need to return the processed data to the server, because it is also byte stream oriented, so you need to rely on the OutputStream class to transfer the data to the server, but the byte stream operation is more troublesome, so you can use thePrintWriter Wrap it up in a class.OutputStream class to simplify operations.
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
// Print out the server-side log
System.out.printf("[%s %d] req=%s res=%s\n",clientSocket.getInetAddress(),
                clientSocket.getPort(),request,response);

Close resource

When implementing socket programming in TCP streams, if you don’t turn off the resources, there will be a resource leakage problem, so why is there a resource leakage? When the server and the new client realize the communication between the two sides is to call theserverSocket.accept() method creates a newSocket resources, and thisSocket Resources are not always used throughout the server-side program, so here it is necessary to close the resource in time to prevent the occurrence of resource leakage problems. But then, didn’t we usetry-with-resources model, when the execution of the code in the model is completed, it will not automatically call the object inside theclose method? Yes, there’s no problem here, but what we’re creating in this model is theInputStream object andOutputStream object, it is the stream object that is closed at the end of the program, and theSocket object is not closed, so we need to manually close theSocket Resources. We are using here thefinally code block to close the resource, preventing intermediate code exceptions from causing the resource not to be closed successfully.
finally {
    clientSocket.close();
}

Overall server-side code

package netWork;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        //Server needs to specify the port number
        serverSocket = new ServerSocket(port);
    }
    
    public void start() throws IOException {
        System.out.println("Server Started");
        while (true) {
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    public void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s %d] client on line\n",clientSocket.getInetAddress(),
                clientSocket.getPort());
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while (true) {
                if (!scanner.hasNext()) {
                    System. Out. Printf (" % d [% s] client referrals ", clientSocket. GetInetAddress (),
                            clientSocket.getPort());
                    break;
                }
                String request = scanner.next();
                String response = process(request);
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s %d] req=%s res=%s\n",clientSocket.getInetAddress(),
                clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            clientSocket.close();
        }
    }

    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer( 9906);
        tcpEchoServer.start();
    }
}

TcpEchoClient Client Implementation

1. Create a Socket object to communicate with the server.

Client-side implementation of communication with the server relies onSocket Interface.
public class TcpEchoClient {
    Socket socket = null;
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        socket = new Socket(serverIp,serverPort);
    }
}
Although TCP communication, both sides will save each other’s information, but we still need to specify in the client code out of theDestination IP and Destination Port

2. Realization of the main logic of the client

Prompts the user to enter the requested data.
public void run() {
    Scanner scanner = new Scanner(System.in);
    try (InputStream inputStream = socket.getInputStream();
         OutputStream outputStream = socket.getOutputStream()) {
        Scanner netWorkScanner = new Scanner(inputStream);
        while (true) {
            String request = scanner.next();
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
 }
Passes the requested data through theOutputStream Transmission.
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
utilizationInputStream Reads the data passed back from the server.
Scanner netWorkScanner = new Scanner(inputStream);
String response = netWorkScanner.next();
System.out.println(response);

Overall server-side code

package netWork;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // You need to "establish a connection" to the server at the same time as creating the socket, and then you need to tell the socket where the server is ~~.
        // The details of establishing the connection are not handled by our code. The kernel takes care of that.
        // When we new the object, the operating system kernel, in three handshakes of specifics, completes the process of establishing the connection.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        // The client behavior of tcp is similar to that of udp.
        // Both.
        // 3. Read the response from the server.
        // 4. Display the response to the interface.
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            PrintWriter writer = new PrintWriter(outputStream);
            Scanner scannerNetwork = new Scanner(inputStream);
            while (true) {
                // 1. Read user input from the console
                System.out.print("-> ");
                String request = scanner.next();
                // 2. Send the string to the server as a request.
                // Println is used here to make the request followed by a newline.
                // That is, it echoes the server read request, scanner.next.
                writer.println(request);
                writer.flush();
                // 3. Read the response from the server.
                String response = scannerNetwork.next();
                // 4. The content is now displayed on the interface.
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("192.168.144.1", 9090);
        client.start();
    }
}

Functional realization

TCP Streaming Socket Programming TCP Streaming Socket Programming

Multiple clients accessing the server

Since it is network programming, it must be inseparable from the situation of multiple clients accessing a server at the same time, if we want to use multiple clients accessing the same server at the same time can it work? How can I implement two processes for the same code in IDEA? TCP Streaming Socket Programming TCP Streaming Socket Programming TCP Streaming Socket Programming TCP Streaming Socket Programming TCP Streaming Socket Programming TCP Streaming Socket Programming TCP Streaming Socket Programming TCP Streaming Socket Programming TCP Streaming Socket Programming When more than one client accesses this server at the same time, we can find out: this function is not really realized. So why can’t this code realize the situation where multiple clients access the same server at the same time? By looking at the server-side code, you can see that: actually, here is thewith two while loopsThe first loop is used to get connections to multiple clients, but why isn’t there a connection to our second client? The first client is in the second while loop when communicating with the server, and the first client is not disconnected from the server, that is, the server is still in the second while loop waiting for the first client to send a request, so when the second client wants to communicate with the server, it can’t reach the server.serverSocket.accept() method to communicate with the server. So how to solve this problem? This brings us to what we learned earlier about multithreading, creating multiple threads for theprocessConnection() method, so that another client can get a connection to the server in another thread if it wants to communicate with the server.
public void start() throws IOException {
    System.out.println("Server Started");
    while (true) {
        Socket clientSocket = serverSocket.accept();
        Thread t = new Thread(() -> {
            try {
                processConnection(clientSocket);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }
}
TCP Streaming Socket Programming TCP Streaming Socket Programming TCP Streaming Socket Programming But this frequent creation and destruction of threads will also consume more resources, so here you can make optimization: the use of thread pools, although here thread pools can be optimized, but the optimization is not much.
public void start() throws IOException {
    System.out.println("Server Started");
    ExecutorService service = Executors.newCachedThreadPool();
    while (true) {
        Socket clientSocket = serverSocket.accept();
        service.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }
 }
Actually, the best way to handle multiple clients accessing the same server at the same time here is theIO Multiplexing / IO Multiswitching
IO multiplexing is a synchronous IO model that allows multiple IO requests to be handled simultaneously within a single process/thread. Specifically, a process/thread can monitor multiple file handles and once a file handle is ready, it is able to notify the application to perform the appropriate read/write operation. If no file handle is ready, the application will block and surrender the CPU. This model allows multiple requests to share the same process/thread, making it possible to utilize system resources more efficiently when processing a large number of requests. If each request is handled using a separate process/thread, the system would need to create and manage a large number of processes/threads, which would consume a large amount of system resources. Instead, using IO multiplexing techniques, one or several threads can be multiplexed to handle multiple TCP connections, thus greatly reducing system overhead. IO multiplexing techniques emerged mainly to solve the problem of blocking IO. In the operating system, initially there was only BIO mode, i.e. blocking IO. when a request is processed, if the request needs to wait for the IO operation to complete (e.g. read/write operation), the process/thread will be blocked until the IO operation completes. This can lead to a waste of system resources, especially if a large number of requests need to be processed. The introduction of IO multiplexing solves this problem. By monitoring multiple file handles, a process/thread can handle multiple IO requests at the same time, thus improving system efficiency. When a file handle is ready, the process/thread can immediately perform the corresponding read and write operations without waiting for other requests to complete. This approach can effectively reduce the waste of system resources and improve system throughput. IO multiplexing techniques are widely used in network programming, especially server-side programming. Since servers need to handle requests from a large number of clients at the same time, the use of IO multiplexing techniques can improve the performance and responsiveness of the server. For example, the Nginx server uses IO multiplexing to handle a large number of client connections.
Here is how to solve the specific I here and not too much narrative, we are interested in learning to learn.

Optimized server-side code

package netWork1;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("Server started!") ;
        ExecutorService service = Executors.newCachedThreadPool();
        while (true) {
            // Bring the connection already established in the kernel to the application via the accept method.
            // The details of establishing the connection are done automatically by the kernel. The application just needs to "pick up where it left off".
            Socket clientSocket = serverSocket.accept();
            // You shouldn't call processConnection directly here, as the server won't be able to handle multiple clients.
            // It makes more sense to create a new thread to call.
            // It works, but it's not good enough
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();

            // A better approach would be to use a thread pool.
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    // Handle the current connection with this method.
    public void processConnection(Socket clientSocket) {
        // Enter the method, which first prints a log indicating that a client is currently connected.
        System.out.printf("[%s:%d] Client online! \n", clientSocket.getInetAddress(), clientSocket.getPort());
        // The next step is to interact with the data.
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // Use try ( ) so you don't run out of stream objects and forget to close them.
            // As the client sends data, it may be "multiple data", so for multiple data, the loop is processed.
            while (true) {
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    // The connection is broken. The loop should end at this point.
                    System.out.printf("[%s:%d] The client is offline! \n", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                // 1. Read the request and parse it. In this case, next is the way to read the request. The rule for next is, if it reads a "white space" character, it returns.
                String request = scanner.next();
                // 2. Calculate the response based on the request.
                String response = process(request);
                // 3. Write the response back to the client.
                // You can convert a String into a byte array and write it to an OutputStream.
                // You can also use a PrintWriter to wrap an OutputStream around a string.
                PrintWriter printWriter = new PrintWriter(outputStream);
                // Instead of printing to the console, println writes to the stream object corresponding to the outputStream, i.e. to the clientSocket.
                // Naturally, this data is sent out over the network. (to the other side of the current connection)
                // println with \n is also used here so that the client can use scanner.next to read the data later.
                printWriter.println(response);
                // Remember to flush the buffer here. If there is no flush, the data may still be in memory and not written to the card.
                printWriter.flush();
                // 4. Print the contents of this request interaction.
                System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // At this point, close the clientSocket.
                // processConnection is processing a connection. When this method is done, the connection is processed.
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String process(String request) {
        // This is also written to the echo server. The response is the same as the request.
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9906);
        server.start();
    }
}

Implementing a Simple Dictionary Function Based on the Echo Server

The server’s business logic for this feature is a little more complex than a simple display back, but not by much, it just requires a change in the server-sideprocess method, in which theMap Storing translations of a few words would be nice.
package netWork;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class TcpDictServer extends TcpEchoServer {

    Map<String, String> dict = new HashMap<>();
    public TcpDictServer(int port) throws IOException {
        super(port);
        dict.put("cat"," cat");
        dict.put("dog"," puppy ");
        dict.put("bird"," bird");
        dict.put("pig"," pig");
    }

    @Override
    public String process(String request) {
        return dict.getOrDefault(request, "The word you are looking for does not exist in this dictionary");
    }

    public static void main(String[] args) throws IOException {
        TcpDictServer tcpDictServer = new TcpDictServer(9906);
        tcpDictServer.start();
    }
}
TCP Streaming Socket Programming TCP Streaming Socket Programming

Recommended Today

DML statements in SQL

preamble Previously we have explained DDL statements in SQL statements. Today we will continue with the DML statement of SQL. DML is the Data Manipulation Language.Used to add, delete, and change data operations on the tables in the library.。 1. Add data to the specified field(INSERT) 2. Modify data(UPDATE) 3. Delete data(DELETE) catalogs preamble I. […]