В главном треде сервера создаете ServerSocket и вызываете метод bind() для возможности входящих соединений на соответствующем порту. Затем в цикле вызываете метод acсерт(), который принимает входящее соединение от очередного клиента и возвращает объект Socket. Вызываете методы getInputStream() и getOutputStrream(), чтобы получить входной и выходной поток данных. Добавляете этот сокет с его потоками в какой-нибудь свой список установленных соединений, Создаете отдельный объект Thread для входного потока каждого нового сокета и запускаете его вызовом start(). Этот тред в своем методе run() в цикле читает данные из входного потока, выясняет, кому их нужно передать, находит соответствующий сокет в списке, и записывает данные в его выходной поток. И так в цикле читает/пишет, пока не получит при чтении EOFException (или любой IOException) по которому нужно закончить цикл чтения, закрыть сокет вызовом close(), удалить его из списка, и завершить метод run() треда.
Таким образом главный тред динамически порождает параллельно работающие треды, обслуживающие входящие соединения. Он может делать это в бесконечном цикле (пока процесс сервера не убъют извне), либо завершаться сам по какому-нибудь условию. При необходимости можно дождаться завершения всех порожденных тредов, вызвав у них метод join().
При таком подходе нужно не забыть поместить работу со списком сокетов (добавление, удаление, поиск) в synchronized блок для предотвращения коллизий. Так же synchronized нужно обложить запись в поток вывода, если несколько клиентов могут передавать данные одному адресату.