JavaRush/Java Blog/Random EN/The Socket and ServerSocket classes, or “Hello, server? C...

The Socket and ServerSocket classes, or “Hello, server? Can you hear me?"

Published in the Random EN group
members
Introduction: “There was a computer on the table, behind it there was an encoder...” The Socket and ServerSocket classes, or “Hello, server?  Can you hear me?"  - 1 Once upon a time, one of my classmates posted another result of his study of Java, in the form of a screenshot of a new program. This program was a multi-user chat. At that time I was just beginning my own journey in mastering programming in this language, but I definitely noted to myself that “I want it!” Time passed and, having finished working on the next project as part of deepening my programming knowledge, I remembered that incident and decided it was time. Somehow I already started to dig into this topic purely out of curiosity, but in my main Java textbook (it was Schildt's complete manual) only 20 pages were provided for the java.net package. This is understandable - the book is already very large. There were tables of methods and constructors of the main classes, but that’s all. The next step is, of course, the almighty Google: myriads of various articles where the same thing is presented - two or three words about sockets, and a ready-made example. The classic approach (at least in my style of study) is to first understand what I need from the tools for work, what they are, why they are needed, and only then, if the solution to the problem is not obvious, pick at the ready-made listings, unscrewing the nuts and bolts. But I figured out what was what and eventually wrote a multi-user chat. Outwardly, it turned out something like this: The Socket and ServerSocket classes, or “Hello, server?  Can you hear me?"  - 2Here I will try to give you an understanding of the basics of client-server applications based on Java sockets using the example of chat design. In the Javarash course you will do chat. It will be on a completely different level, beautiful, large, multifunctional. But first of all, you always need to lay the foundation, so here we need to figure out what underlies such a section. (If you find any shortcomings or errors, write in a PM or in a comment under the article). Let's begin. Head One: “The house that...” To explain how a network connection occurs between a server and one client, let’s take the now classic example of an apartment building. Let's say a client needs to somehow establish a connection with a specific server. What does the searcher need to know about the search object? Yes, the address. The server is not a magical entity on the cloud, and therefore it must be located on a specific machine. By analogy with a house, where a meeting of two agreed upon parties should take place. And in order to find each other in an apartment building, one address of the building is not enough; you must indicate the number of the apartment in which the meeting will take place. Likewise, on one computer there can be several servers at once, and in order for the client to contact a specific one, he also needs to specify the port number through which the connection will occur. So, the address and port number. An address means an identifier of a machine in the Internet space. It can be a domain name, for example, "javarush.ru" , or a regular IP. Port- a unique number with which a specific socket is associated (this term will be discussed later), in other words, it is occupied by a certain service so that it can be used to contact it. So in order for at least two objects to meet on the territory of one (server), the owner of the area (server) must occupy a specific apartment (port) on it (car), and the second must find the meeting place knowing the address of the house (domain or ip ), and apartment number (port). Head Two: Meet Socket Among the concepts and terms related to working on the network, one very important one is Socket. It denotes the point through which the connection occurs. Simply put, a socket connects two programs on a network. The class Socketimplements the idea of ​​a socket. The client and the server will communicate through its input/output channels: The Socket and ServerSocket classes, or “Hello, server?  Can you hear me?"  - 3 This class is declared on the client side, and the server recreates it, receiving a connection signal. This is how online communication works. To begin with, here are the possible class constructors Socket:
Socket(String Name_хоста, int порт) throws UnknownHostException, IOException
Socket(InetAddress IP-address, int порт) throws UnknownHostException
“host_name” - implies a specific network node, IP address. If the socket class could not convert it to a real, existing address, then an exception will be thrown UnknownHostException. Port is a port. If 0 is specified as the port number, the system itself will allocate a free port. An exception may also occur if the connection is lost IOException. It should be noted that the address type in the second constructor is InetAddress. It comes to the rescue, for example, when you need to specify a domain name as an address. Also, when a domain means several IP addresses, InetAddressyou can use them to get an array of them. However, it works with IP too. You can also get the host name, the byte array that makes up the IP address, etc. We'll touch on it a little further, but you'll have to go to the official documentation for full details. When an object of type is initialized Socket, the client to which it belongs announces on the network that it wants to connect to the server at a specific address and port number. Below are the most frequently used methods of the class Socket: InetAddress getInetAddress()– returns an object containing data about the socket. If the socket is not connected - null int getPort()- returns the port on which the connection to the server occurs int getLocalPort()- returns the port to which the socket is bound. The fact is that the client and server can “communicate” on one port, but the ports to which they are bound can be completely different boolean isConnected()- returns true if the connection is established void connect(SocketAddress address)- indicates a new connection boolean isClosed()- returns true, if the socket is closed boolean isBound()- returns true, if the socket is actually bound to an address, the class Socketimplements the interface AutoCloseable, so it can be used in the try-with-resources. However, you can also close a socket in the classic way, using close(). Head Three: and this is a ServerSocket Let's say we declared, in the form of a class Socket, a connection request on the client side. How will the server guess our wish? For this, the server has a class like ServerSocket, and the accept() method in it. Its constructors are presented below:
ServerSocket() throws IOException
ServerSocket(int порт) throws IOException
ServerSocket(int порт, int максимум_подключений) throws IOException
ServerSocket(int порт, int максимум_подключений, InetAddress локальный_address) throws IOException
When declaring, ServerSocket you do not need to specify the connection address, because communication takes place on the server machine. Only with a multi-channel host you need to specify which IP the server socket is bound to. Head Three.One: The Server That Says No Since providing a program with more resources than it needs is both costly and unreasonable, therefore in the constructor ServerSocketyou are asked to declare the maximum connections accepted by the server during operation. If it is not specified, then by default this number will be considered equal to 50. Yes, in theory we can assume that ServerSocketthis is the same socket, only for the server. But it plays a completely different role than class Socket. It is only needed at the connection creation stage. Having created a type object, ServerSocketyou need to find out that someone wants to connect to the server. The accept() method is connected here. The target waits until someone wants to connect to it, and when this happens it returns an object of type Socket, that is, a recreated client socket. And now that the client socket has been created on the server side, two-way communication can begin. Creating a type object Socketon the client side and re-creating it using ServerSocketthe server side is the minimum required for the connection. Head Four: Letter to Santa Claus Вопрос: How exactly do the client and server communicate? Ответ:Through I/O streams. What do we already have? A socket with the server address and the client’s port number, and the same thing, thanks to accept(), on the server side. So it is reasonable to assume that they will communicate via a socket. To do this, there are two methods that give access to streams InputStreamand OutputStreamobjects of type Socket. Here they are:
InputStream getInputStream()
OutputStream getOutputStream()
Since reading and writing bare bytes is not as efficient, streams can be wrapped in adapter classes, buffered or not. For example:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
For communication to be bidirectional, such operations must be performed on both sides. Now you can send something using in, and receive something using out, and vice versa. Actually, this is practically the only function of the class Socket. And yes, do not forget about the flush() method BufferedWriter- it flushes the contents of the buffer. If this is not done, the information will not be transmitted and, therefore, will not be received. The receiving thread also waits for the end-of-line indicator – “\n”, otherwise the message will not be accepted, since in fact the message is not completed and is not complete. If this seems inconvenient to you, don’t worry, you can always use the class PrintWriter, which needs to wrap out, specify true as the second argument, and then popping from the buffer will occur automatically:
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
Also, there is no need to indicate the end of the line; this class does it for you. But is string I/O the limit of what a socket can do? No, do you want to send objects via socket streams? For God's sake. Serialize them and you're good to go:
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Head Five: Real communication via the Internet Since in order to connect via a real network with a real IP address you need to have a full-fledged server, and since:
  1. Our future chat, as a utility, does not have such abilities. It can only establish a connection and receive/send a message. That is, it does not have real server capabilities.
  2. Our server, containing only socket data and I/O streams, cannot work as a real WEB or FTP server, then with only this we will not be able to connect over the Internet.
And besides, we are just starting to develop the program, which means that it is not stable enough to immediately work with a real network, so we will use the local host as the connection address. That is, in theory, the client and server will still not be connected in any way except through a socket, but for debugging the program they will be on the same machine, without real contact over the network. In order to indicate in the constructor Socketthat the address is local, there are 2 ways:
  1. Write “localhost” as the address argument, meaning a local stub. “127.0.0.1” is also suitable for this - this is just a digital form of a stub.
  2. Using InetAddress:
    1. InetAddress.getByName(null)- null points to localhost
    2. InetAddress.getByName("localhost")
    3. InetAddress.getByName("127.0.0.1")
For simplicity, we will use "localhost" of type String. But all other options are also workable. Head Six: It's time for a conversation So, we already have everything we need to implement a conversation session with the server. All that remains is to put it together: The following listing shows how the client connects to the server, sends it one message, and the server, in turn, confirms that it received the message using it as an argument in its: "Server.java"
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    private static Socket clientSocket; // socket for communication
    private static ServerSocket server; // server socket
    private static BufferedReader in; // socket read stream
    private static BufferedWriter out; // socket write stream

    public static void main(String[] args) {
        try {
            try  {
                server = new ServerSocket(4004); // server socket listening on port 4004
                System.out.println("Server is running!"); // server would be nice
                // announce your launch
                clientSocket = server.accept(); // accept() will wait until
                //someone won't want to connect
                try { // after establishing a connection and recreating the socket for communication with the client, you can go
                    // to create I/O streams.
                    // now we can receive messages
                    in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                    // and send
                    out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

                    String word = in.readLine(); // wait for the client to write something to us
                    System.out.println(word);
                    // without hesitation responds to the client
                    out.write("Hi, this is the Server! I confirm you wrote: " + word + "\n");
                    out.flush(); // push everything out of the buffer

                } finally { // in any case, the socket will be closed
                    clientSocket.close();
                    // streams would also be nice to close
                    in.close();
                    out.close();
                }
            } finally {
                System.out.println("Server closed!");
                    server.close();
            }
        } catch (IOException e) {
            System.err.println(e);
        }
    }
"Client.java"
import java.io.*;
import java.net.Socket;

public class Client {

    private static Socket clientSocket; // socket for communication
    private static BufferedReader reader; // we need a reader that reads from the console, otherwise
    // do we know what the client wants to say?
    private static BufferedReader in; // socket read stream
    private static BufferedWriter out; // socket write stream

    public static void main(String[] args) {
        try {
            try {
                // address - local host, port - 4004, same as the server
                clientSocket = new Socket("localhost", 4004); // with this line we request
                // the server has access to the connection
                reader = new BufferedReader(new InputStreamReader(System.in));
                // read messages from the server
                in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                // write there
                out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

                System.out.println("Did you have something to say? Enter it here:");
                // if the connection happened and the threads were successfully created - we can
                // work further and offer the client something to enter
                // if not, an exception will be thrown
                String word = reader.readLine(); // wait for the client to do something
                // will not write to the console
                out.write(word + "\n"); // send a message to the server
                out.flush();
                String serverWord = in.readLine(); // wait for the server to say
                System.out.println(serverWord); // received - display
            } finally { // in any case, you need to close the socket and streams
                System.out.println("The client has been closed...");
                clientSocket.close();
                in.close();
                out.close();
            }
        } catch (IOException e) {
            System.err.println(e);
        }

    }
}
Of course, you should start the server first, because what will the client connect to at startup if there is no something that will connect it? :) The output will be like this: /* Did you want to say something? Enter it here: Hello, server? Can you hear me? Hello, this is the Server! I confirm, you wrote: Hello, server? Can you hear me? The client was closed... */ Hurray! We taught the server to communicate with the client! So that communication occurs not in two replicas, but as many as you like, simply wrap the reading and writing of threads in a while (true) loop and indicate for the exit that, according to a certain message, for example, “exit”, the cycle was interrupted, and the program would end. Head Seven: Multi-user is better. The fact that the server can hear us is good, but it would be much better if we could communicate with someone of our own kind. I will attach all the sources at the end of the article, so here I will show not always large, but important pieces of code that will make it possible, if used correctly, to concoct a multi-user chat. So, we want to be able to communicate with some other client through the server. How to do it? Obviously, since the client program has its own method main, it means it can be launched separately from the server and in parallel with other clients. What does this give us? Somehow, it is necessary that with each new connection the server does not immediately go to communication, but writes this connection into some kind of list and proceeds to wait for a new connection, and some kind of auxiliary service is engaged in communication with a specific client. And clients must write to the server and wait for a response independently of each other. Threads come to the rescue. Let's say we have a class responsible for remembering new connections: It should have the following specified:
  1. Port number.
  2. The list in which it writes the new connection.
  3. And ServerSocket, in a single (!) copy.
public class Server {

    public static final int PORT = 8080;
    public static LinkedList<ServerSomthing> serverList = new LinkedList<>(); // list of all threads

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(PORT);
            try {
            while (true) {
                // Blocks until a new connection is made:
                Socket socket = server.accept();
                try {
                    serverList.add(new ServerSomthing(socket)); // add a new connection to the list
                } catch (IOException e) {
                    // If it fails, the socket is closed,
                    // otherwise, the thread will close it when it exits:
                    socket.close();
                }
            }
        } finally {
            server.close();
        }
    }
}
Okay, now each recreated socket will not be lost, but will be stored on the server. Further. Every customer needs someone to listen. Let's create a thread with the server functions from the last chapter.
class ServerSomthing extends Thread {

    private Socket socket; // socket through which the server communicates with the client,
    // except for it - the client and server are not connected in any way
    private BufferedReader in; // socket read stream
    private BufferedWriter out; // socket write stream

    public ServerSomthing(Socket socket) throws IOException {
        this.socket = socket;
        // если потоку ввода/вывода приведут к генерированию исключения, оно пробросится дальше
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        start(); // call run()
    }
    @Override
    public void run() {
        String word;
        try {

            while (true) {
                word = in.readLine();
                if(word.equals("stop")) {
                    break;                }
                for (ServerSomthing vr : Server.serverList) {
                    vr.send(word); // send the received message with
                   // bound client to everyone else including him
                }
            }

        } catch (IOException e) {
        }
    }

    private void send(String msg) {
        try {
            out.write(msg + "\n");
            out.flush();
        } catch (IOException ignored) {}
    }
}
So, in the server thread's constructor, a socket must be initialized through which the thread will communicate with a specific client. Also I/O threads, and everything else you need to start a thread directly from the constructor. Okay, but what happens when the server thread reads a message from the client? Send back only to your client? Not very effective. We are making a multi-user chat, so we need each connected client to receive what one person wrote. You need to use the list of all server threads associated with their clients and send each message sent to a specific thread so that it sends it to its client:
for (ServerSomthing vr : Server.serverList) {
    vr.send(word); // send the received message
    // from the linked client to everyone else, including him
}
private void send(String msg) {
    try {
        out.write(msg + "\n");
        out.flush();
    } catch (IOException ignored) {}
}
Now all clients will know what one of them said! If you do not want the message to be sent to the person who sent it (he already knows what he wrote!), simply when iterating through the threads, specify that when processing an object, the thisloop will move to the next element without performing any actions on it. Or, if you prefer, send a message to the client stating that the message was successfully received and sent. Everything is clear with the server now. Let's move on to the client, or rather to the clients! Everything is the same there, by analogy with the client from the last chapter, only when creating an instance you need, as was shown in this chapter with the server, to create everything necessary in the constructor. But what if, when creating a client, he has not yet had time to enter anything, but something has already been sent to him? (For example, the history of correspondence of those who have already connected to the chat before him). So the cycles in which sent messages will be processed must be separated from those in which messages are read from the console and sent to the server for forwarding to others. Threads come to the rescue again. There is no point in creating a client as a thread. It is more convenient to make a thread with a loop in the run method that reads messages, and also, by analogy, writes:
// thread reading messages from the server
private class ReadMsg extends Thread {
    @Override
    public void run() {

        String str;
        try {
            while (true) {
                str = in.readLine(); // waiting for a message from the server
                if (str.equals("stop")) {

                    break; // exit the loop if it's "stop"
                }
                            }
        } catch (IOException e) {

        }
    }
}
// thread sending messages coming from the console to the server
public class WriteMsg extends Thread {

    @Override
    public void run() {
        while (true) {
            String userWord;
            try {
               userWord = inputUser.readLine(); // messages from the console
                if (userWord.equals("stop")) {
                    out.write("stop" + "\n");
                    break; // exit the loop if it's "stop"
                } else {
                    out.write(userWord + "\n"); // send to the server
                }
                out.flush(); // clean up
            } catch (IOException e) {

            }

        }
    }
}
In the client constructor you just need to start these threads. How to properly close a client’s resources if he wants to leave? Do I need to close server thread resources? To do this, you will most likely need to create a separate method that is called when exiting the message loop. There you will need to close the socket and I/O streams. The same session end signal for a particular client must be sent to its server thread, which must do the same with its socket and remove itself from the list of threads in the main server class. Head Eight: There is no limit to perfection You can endlessly invent new features to improve your project. But what exactly should be transferred to a newly connected client? I think the last ten events happened before his arrival. To do this, you need to create a class in which the last action with any server thread will be entered into the declared list, and if the list is already full (that is, there are already 10), delete the first one and add the last one that came. In order for the contents of this list to be received by a new connection, when creating a server thread, in the output stream, you need to send them to the client. How to do it? For example, like this:
public void printStory(BufferedWriter writer) {
// ...
}
The server thread has already created streams and can pass the output stream as an argument. Next, you just need to pass everything that needs to be transferred to the new client in a search cycle. Conclusion: This is just the basics, and most likely this chat architecture will not work when creating a real application. This program was created for educational purposes and on its basis I showed how you can make the client communicate with the server (and vice versa), how to do this for several connections, and, of course, how this is organized on sockets. The sources are rearranged below, and the source code of the program being analyzed is also attached. This is my first experience of writing an article) Thank you for your attention :)
  1. Thinking in Java Enterprise, by Bruce Eckel et. Al. 2003
  2. Java 8, The Complete Guide, Herbert Schildt, 9th edition, 2017 (Chapter 22)
  3. Socket programming in Java article about sockets
  4. Socket in the official documentation
  5. ServerSocket in the official documentation
  6. sources on GitHub
Comments
  • Popular
  • New
  • Old
You must be signed in to leave a comment
This page doesn't have any comments yet