1
public class ServerThread implements Runnable 
{

    private static final int port = 10000;

    @Override
    public void run() {
        ServerSocket serverSocket = new ServerSocket(port);

        while (true) {
            Socket clientSocket = serverSocket.accept();
            ClientThread clientThread = new ClientThread(clientSocket);
            // handle the client request in a separate thread
        }
    }
}

Will this work If I have let's say 10 different threads running ServerThread.run()? Or should I use the same ServerSocket object for all threads?

The docs say:

The constructor for ServerSocket throws an exception if it can't listen on the specified port (for example, the port is already being used)

You may be wondering why I want to do this in the first place and not simply have a single thread running ServerSocket.accept(). Well, my assumption is (correct me if I'm wrong) that the accept() method may take some time to finish establishing a connection, especially if the ServerSocket is SSL (because of handshaking). So if two clients want to connect at the same time, one has to wait for the other. This would be very bad for a high traffic server.

Update: It seems that the accept() method will return as soon as a connection belonging to a queue is established. This means if there's a queue of clients waiting to connect, the server thread can handle the requests in the fastest way possible and only one thread is needed. (apart from the time it takes to create a new thread for each request and starting the thread, but that time is negligible when using a thread pool)

The ServerSocket also has a parameter called "backlog", where you can set the maximum number of connections in the queue. According to the book "Fundamental Networking in Java" 3.3.3

TCP itself can get ahead of a TCP server application in accepting connections. It maintains a ‘backlog queue’ of connections to a listening socket which TCP iself has completed but which have not yet been accepted by the application. This queue exists between the underlying TCP implementation and the server process which created the listening socket. The purpose of pre-completing connections is to speed up the connection phase, but the queue is limited in length so as not to pre-form too many connections to servers which are not accepting them at the same rate for any reason. When an incoming connection request is received and the backlog queue is not full, TCP completes the connection protocol and adds the connection to the backlog queue. At this point, the client application is fully connected, but the server application has not yet received the connection as a result value of ServerSocket.accept. When it does so, the entry is removed from the queue.

I'm still not sure though in the case of SSL, if the handshaking is also done in parallel by ServerSocket.accept() for simultaneous connections.

Update 2 The ServerSocket.accept() method itself doesn't do any real networking at all. It will return as soon as the operating system has established a new TCP connection. The operating system itself holds a queue of waiting TCP connections, which can be controlled by the "backlog" parameter in the ServerSocket constructor:

ServerSocket serverSocket = new ServerSocket(port, 50);
//this will create a server socket with a maximum of 50 connections in the queue

SSL handshaking is done after the client calls Socket.connect(). So one thread for ServerSocket.accept() is always enough.

tache
  • 173
  • 1
  • 8
  • 2
    If you are worried about stalling connections, consider a load balancer that will delegate connections quickly to proxy applications. I would suggest **not** putting this in your core application because it will bloat your application and it probably isn't something that your application should worry about. – zero298 Jul 24 '19 at 20:18
  • 1
    Although threading can help with parallelism chances are your network adapter cannot do things in parallel (it may, however, support multiple connections through timeslicing). I do not think it is a good idea to try reusing ports in this example. Creating a new thread for each connection after it is accepted is probably the best option. If high traffic becomes an issue you should have more than one server in the first place, and I don't think this would actually help. – h0r53 Jul 24 '19 at 20:18
  • 3
    You cannot open more than one `ServerSocket` on the same IP/Port address. What you do is accept connections on a single `ServerSocket` and then launch a new thread (or reuse one from a thread pool) to handle each connection. How to do this is covered in great detail in literally hundreds of books and programming tutorial sites (of which StackOverflow is NOT one). – Jim Garrison Jul 24 '19 at 20:46
  • @Jim Garrison duh.. as I showed in the code above?.. thanks for the -1 btw – tache Jul 24 '19 at 21:38
  • 2
    The SSL handshake is not performed by "TCP" but by Java behind the hood, as it implies certificates stores and key validation. TCP "pre-accepts" connections by sending the ACK/SYN packets automatically before handing the socket over to Java. EJP (who wrote that awesome book) is still active here I hope and could confirm/infirm that. – Matthieu Jul 24 '19 at 22:40
  • @Matthieu I know, but what if the handshake takes a long time (the client could stall on purpose, for instance), would the accept method then block all that time, even though another client is waiting? Should it not handle both handshakes at the same time and return as quickly as possible? That seems to be the most logical to me, although it's not documented by Oracle in any way. – tache Jul 24 '19 at 23:04
  • 2
    The `SSLServerSocket.accept()` method does not perform any SSL operations. It doesn't do anything on the network at all. Your question is based on a fallacy. You don't need to do this. @Matthieu Thanks for the comment. EJP – user207421 Jul 24 '19 at 23:11
  • @user207421 Ah interesting.. So when does the SSL handshake happen then, if SSLServerSocket.accept() doesn't do it? I thought that after the client calls Socket.connect() the handshake would have happened by then. – tache Jul 24 '19 at 23:24
  • 2
    The SSL handshakes starts when you call `startHandshake()`. If you don't call it, which is the usual case, it is called automatically when you do the first I/O on the accepted socket. – user207421 Jul 24 '19 at 23:25
  • 2
    See an edit I made including a [comment](https://community.oracle.com/thread/2212224) by @user207421 who answers that concern – Matthieu Jul 24 '19 at 23:29
  • @user207421 ah.. now it's clear.. thanks! – tache Jul 24 '19 at 23:30
  • 2
    And similarly for the connected socket at the client. No SSL handshake until ditto. – user207421 Jul 24 '19 at 23:35

1 Answers1

2

Here are a few thoughts regarding your problem:

You can't listen() on the same IP+port with several ServerSocket. If you could, to which one of the socket would the OS transfer the SYN packet?*

TCP indeed maintains a backlog of pre-accepted connections so a call to accept() will return (almost) immediately the first (oldest) socket in the backlog queue. It does so by sending the SYN-ACK packet automatically in reply to a SYN sent by the client, and waits for the reply-ACK (the 3-way handshake). But, as @zero298 suggests, accepting connections as fast as possible isn't usually the problem. The problem will be the load incurred by processing communication with all the sockets you'll have accepted, which may very well put your server down its knees (it's actually a DoS attack). In fact the backlog parameter is usually here so too many simultaneous connections waiting for too long in the backlog queue to be accept()ed will be dropped by TCP before reaching your application.

Instead of creating one thread per client socket, I would suggest you use an ExecutorService thread pool running some maximum number of threads, each handling communication with one client. That allows for a graceful degradation of system resources, instead of creating millions of threads which would in turn create thread starvation, memory issues, file descriptor limits, ... Coupled with a carefully-chosen backlog value you'll be able to get the maximum throughput you server can offer without crashing it. And if you're worried about DoS on SSL, the very first thing the run() method of your client-thread should do, is call startHandshake() on the newly-connected socket.

Regarding the SSL part, TCP itself cannot do any SSL pre-accept, as it need to perform encryption/decoding, talking to a keystore, etc. which are well beyond its specification. Note that you should also use an SSLServerSocket in that case.

To go around the use-case you gave (clients willingly delaying handshake to DoS your server) you'll be interested reading an Oracle forum post about it where EJP (again) answers:

The backlog queue is for connections that have been completed by the TCP stack but not yet accepted by the application. Nothing to do with SSL yet. JSSE doesn't do any negotiation until you do some I/O on the accepted socket, or call startHandshake() on it, both of which you would do in the thread dealing with the connection. I don't see how you can make a DOS vulnerability out of that, at least not an SSL-specific one. If you are experiencing DOS conditions, most probably you are doing I/O in the accept() thread that should be done in the connection-handling thread.

*: Though Linux >=3.9 does some kind of load-balancing, but for UDP only (so not SSLServerSocket) and with option SO_REUSEPORT, which is not available on all platforms anyway.

Matthieu
  • 2,736
  • 4
  • 57
  • 87
  • 2
    This is correct. There is no SSL part to `SSLServerSocket.accept()`. EJP – user207421 Jul 24 '19 at 23:12
  • 2
    Re the Oracle Forum post, the OP should read the whole thread. Same question, same answer. Well done for finding it. – user207421 Jul 24 '19 at 23:37
  • 1
    @user207421, I finally start learning... :) – Matthieu Jul 24 '19 at 23:39
  • 2
    The original Sun forums on their original platform were the greatest source of knowledge and learning about Java on the planet. I certainly learned heaps there, and tried to pass on some as well. Shame they broke it. – user207421 Jul 24 '19 at 23:41
  • 2
    I believe you can share TCP listening ports on Windows, but as usual with Microsoft you can never find out it actually means or what actually happens. And on Unix, Linux, etc., you can spawn multiple children and have them all inherit the listening socket and accept from it. – user207421 Jul 25 '19 at 00:04
  • 1
    @user207421, right, I didn't think about fork as a load-balancer. It is indeed a bit more resilient as the OS has more control on processes than on threads (depending on threads implementation in the JVM+OS combo). – Matthieu Jul 25 '19 at 00:19
  • 1
    I don't think this is correct: "In fact the backlog parameter is usually here so too many simultaneous connections for too long will be dropped before affecting your application" The backlog parameter is for maximum number of new connections waiting to be processed by the application. You can have a backlog of 50, but still have 10,000 simultaneous connections. – tache Jul 25 '19 at 00:35
  • 1
    @tache does it look better now? – Matthieu Jul 27 '19 at 03:14
  • 1
    @Matthieu yes, but this topic was put "on hold", even though there's still some useful informaton in it, IMO.. The topic question is no longer relevant anymore though. Maybe the question should be: "What does ServerSocket.accept() do exactly?".. I think the only thing it does is polling the O/S to see if a new connection was made. That's why only one thread with ServerSocket.accept() is necessary. – tache Jul 27 '19 at 10:54
  • @tache maybe "How to prevent DoS when accepting SSL sockets connections"? Well at least you got valuable information, though a better title would give the question better visibility... – Matthieu Jul 27 '19 at 16:16