C/S model: TCP, UDP build client and server (BIO implementation)

Keywords: socket Java Programming JDK

socket programming is provided in Java to build client and server side

TCP

Steps to build the server side:
(1) bind: Bind port number
(2) listen: listen for client connection requests
(3) accept: an instance of returning a connection to the client
(4) read/write: read/write operations, i.e., interaction with the client
(5) close: Close resources
ServiceSocket keyword is provided in Java to build the server. In Java, listen and accept are merged into an accept operation. The following five steps are illustrated by code

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        //Bind: bind ip and port
        serverSocket.bind(new InetSocketAddress(6666));
        //listen listens and accpet returns the socket instance
        Socket accept = serverSocket.accept();
        //Get input and output streams through socket
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
        PrintStream printStream = new PrintStream(accept.getOutputStream());
        //Read the data from the client and print it
        String s = bufferedReader.readLine();
        System.out.println("Receive client data:"+s);
        //Output data to client
        printStream.println("Lala Server Returns Message:"+s);
        //Close the stream and socket
        printStream.close();
        bufferedReader.close();
        serverSocket.close();
    }
}

We can also bind the port number while creating the ServerSocket, that is to say

ServerSocket serverSocket = new ServerSocket(6666);

The steps to build the client are as follows:
(1) connect: connect to the server through IP address and port number
(2) read/write: read/write operations, i.e., information exchange with the server
(3) close: Close resources
As you can see, the operation of the client side is much simpler than that of the server side.
Because what we write is under TCP protocol, we should start the server first. After the server starts, the client will wait for the connection in accept, that is to say, the code will block here, and the client can connect at this time.
The following steps are illustrated in code:

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        //Connecting to the server through ip and port number
        socket.connect(new InetSocketAddress("127.0.0.1",6666));
        //Define read-write streams
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintStream printStream = new PrintStream(socket.getOutputStream());
        //Send data to the server
        printStream.println("Haha, I'm a client.");
        //Receiving data recovered by server
        String s = bufferedReader.readLine();
        System.out.println(s);
        //close resource
        printStream.close();
        bufferedReader.close();
        socket.close();
    }
}

Similarly, we can connect to the server side while creating the Socket.

Socket socket = new Socket("127.0.0.1",6666);

Both demo s use buffers and print streams for read and write operations. Here's a look at the results



Let's describe this process by drawing a picture.

It's also clear from this picture when the three handshakes and the four waves occur, respectively.

Next, consider a requirement. What if there are multiple clients and servers communicating information?

As mentioned earlier, the accpet operation on the server side is an operation waiting for the client to connect, so writing a loop, one accept in each loop body can not solve the problem of connecting multiple clients to the server side, but it should be noted that accpet is a blocking operation, so it requires multiple threads. Yes, here's why:
The first thing you need to understand is that you only need to modify the server side, so this piece is for the server side.
If there is only one thread, then the thread will be used to read and write through the accpet connection, which will also block when reading and writing. If the next accpet is blocked when reading and writing, then the next accpet will not connect properly, and the thread will stop until the first connection is finished. It is obvious that this process is a serial process, which can not achieve the desired effect. This requires multi-threading. The main thread is only used to maintain the connection (accpet), and then the sub-threads are responsible for reading and writing interaction with the client, so that the sub-threads will not affect the main thread when the read and write blockage occurs.

Let's first look at the code for multiple clients and one server:
The following code only shows the server side, the client side and the top remain unchanged

public class MutileServer {
    public static void main(String[] args) {
    	//Create a thread pool with three fixed number of threads
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        try {
            ServerSocket sockets = new ServerSocket(6666);
            System.out.println("The server has started and is waiting for a connection");
            while(true) {
                Socket accept = sockets.accept();
                System.out.println("Client:"+accept.getInetAddress().getHostAddress());
                executorService.execute(new MyThread(accept));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class MyThread implements Runnable {
    private Socket socket;
    private BufferedReader bufferedReader;
    private PrintStream printStream;
    public MyThread(Socket socket) {
        this.socket = socket;
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            printStream = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            String s = bufferedReader.readLine();
            System.out.println("Client sends message "+s);
            printStream.println("echo"+s);
            printStream.flush();
            bufferedReader.close();
            printStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Several points need to be clarified:
(1) In this section, I use thread pool, which is convenient for unified management and efficient without creating threads many times. Multiple threads can also be created individually, and through loops.
(2) I wrote a MyThread class to deal with the information exchange between the reader and the client, that is, the sub-thread completes the read and write operation, and the main thread only needs to care about the connection with the server.
(3) When resources are exhausted, they must be shut down and exceptions should not be thrown as far as possible, so that they can be handled in the current method.

shortcoming

There are drawbacks in using BIO to implement TCP client and server. BIO is a synchronous blocking model, which was used until JDK 1.4. But to think of such a problem, every time a client comes to connect to the server, there must be a thread corresponding to it. So when there are many clients, the server thread will be. Very many, and thread context switching will become a very large consumption, this is the shortcoming of BIO, if we want to improve this shortcoming, we need to introduce NIO.

UDP

Unlike TCP, UDP is not a reliable connection, that is to say, it guarantees the correct delivery of data, and UDP will spread data in the form of datagrams. For example, listen to the radio, when the program arrives, it must turn on the radio in advance, so as not to miss it. UDP is the same. When sending data on the server side, the client needs to wait in advance so as not to miss the data.
UDP requires two classes to complete client and server: DatagramPacket and DatagramSocket
DatagramSocket is a socket for establishing connections, which can be understood as a wharf for transporting goods.
Datagram Packet is a data package, that is to say, the data is packaged into a data package in the process of transmission. If Datagram Socket is a wharf, then Datagram Packet is a box for loading goods.

Look at the code:
Server:

public class Server {
    public static void main(String[] args) {
        //Data to be sent
        String data = "The data was sent...";
        byte[] bytes = data.getBytes();

        try {
            //Packed in Packet Form
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length,InetAddress.getByName("127.0.0.1"), 8888);
            //Initialize DatagramSocket
            DatagramSocket socket = new DatagramSocket();
            //send data
            socket.send(packet);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client:

public class Client {
    public static void main(String[] args) {
        try {
            //Select the port number to listen on
            DatagramSocket socket = new DatagramSocket(8888);
            //Initialization of received data packets
            byte[] bytes = new byte[1024];
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
            //receive data
            socket.receive(packet);
            //Parsing data package
            String s = new String(packet.getData(), 0,packet.getLength());
            //Print the received data
            System.out.println(s);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Start the client first, then the server

Posted by davex on Wed, 31 Jul 2019 19:00:32 -0700