Погоди, сначала разберемся в логике работы соединения. Есть как минимум два варианта работы:
А) Односторонний запрос-ответ, или ведущий-ведомый (то, что реализовано в приведенном коде). Т.е. клиент(ведущий) шлет запрос, получает ответ от ведомого, и либо закрывает соединение, либо шлёт еще запрос. Так работает HTTP, к примеру.
Б) произвольный порядок. И клиент, и сервер могут послать сообщение другой стороне в произвольный момент, но другая сторона не обязана отвечать немедленно. Так обычно работают IM-системы.
Для второго варианта работы логика будет примерно такой:
1. Установить соединение
2. Ждать, пока не появятся принятые данные, не поступят сообщения для отправки или не будет получен сигнал об остановке.
3. Если получен сигнал остановки, см. п. 11.
4. Проверить, есть ли принятые данные, если нет, см. п. 7.
5. Принять данные и разобрать их формат, преобразовав их в сообщения.
6. Обработать принятые сообщения, например сгенерировать событие.
7. Проверить, есть ли сообщения на отправку, если нет, см. п. 10.
8. Сформировать данные для отправки.
9. Отправить данные.
10. См. п. 2
11. Закрыть соединение.
При этом данные - это та строка байт, которая отправляется через сокет, а сообщение - это те структуры данных, которыми оперирует твоя программа. В простейшем случае это будет одно и то же.
Реализовать такую логику можно следующим образом: создаешь отдельный поток обработки соединения, который в цикле выполняет шаги 2-10. Этот поток должен предоставлять метод для постановки сообщения в очередь на отправку, и событие для реакции на принятые сообщения*.
В этом случае твоя основаная программа стартует этот поток, подписывается на его события, и дергает его метод отправки по мере надобности. Таким образом можно разделить код работы с сетью и GUI.
* Еще пригодится служебная мелочь, вроде команды на остановку потока и закрытие соединения, проверку наличия ошибок и тд. В простейшей программе без этого можно обойтись.