Сокеты
Введение
Сокет — это программные интерфейс, обеспечивающий обмен данными между процессами. Процессы могут выполняться как на одном компьютере, так и на различных, связанных сетью. Сокеты бывают серверными и клиентскими. Главное их различие в том, что серверный сокет прослушивает выделенный ему порт в ожидании новых клиентов и может работать сразу с несколькими клиентскими сокетами. Инициатором соединения всегда является клиентский сокет. Сокет является основой множества сетевых протоколов, в том числе http/https, ftp, websockets, pop3, smtp, ssh и др.
В пакете java.net есть классы Socket и ServerSocket для создания клиентского и серверного сокетного соединения соответственно.
Клиентский сокет
Класс Socket имеет несколько конструкторов. Рассмотрю пару распространенных:
public Socket(String host, int port); public Socket(InetAddress inetAddress, int port);
В первый аргумент первого конструктора передаем ip-адрес или DNS-имя машины, с которой хотим осуществить соединение. Второй аргумент - номер порта, к которому будем осуществлять подключение. Во втором конструкторе можем использовать объект класса InetAddress, который обеспечивает работу с DNS-именами и ip-адресами. Вот несколько примеров:
Socket s1 = new Socket("icq.com",5190); Socket s2 = new Socket(InetAddress.getByName("icq.com"),5190); byte[] address = new byte[]{(byte)178,(byte)237,23,(byte)237}; Socket s3 = new Socket(InetAddress.getByAddress(address), 5190);
Разработчику дана возможность управлять таймаутом соединения с помощью метода setSoTimeout(). Если в течении указанного времени с сокетом не произошли никакие действия, то он автоматически закрывается. Запретить автоматическое закрытие сокета можно установив таймаут равным 0.
public void setSoTimeout(int timeout)
Серверный сокет
Он создается также легко, как и клиентский.
public ServerSocket(int port);
Этот конструктор создает серверный сокет с прослушкой указанного порта. Важная часть работы с серверным сокетом - метод accept(), вызов которого возвращает сокет, связанный с клиентским. Сразу пример для лучшего понимания:
ServerSocket serverSocket = new ServerSocket(5190);//создали серверный сокет Socket socket = serverSocket.accept();//метод accept() ждет клиентов
Метод accept() заставляет серверный сокет ждать клиентов и, как только кто-то подключился, возвращает связанный с клиентом сокет в переменную socket. Дальше можно начинать общение клиента с сервером.
Клиент-серверное общение
Работая с экземплярами класса Socket, мы можем получить доступ к входящему и исходящему потокам сокета методами getInputStream() и getOutputStream() соответственно. Сразу приведу пример клиент-серверной связки.
Исходный код сервера:
import java.io.*; import java.net.*; public class ServerSocketExample{ ServerSocketExample() { try { ServerSocket ss = new ServerSocket(5524);//Создаем серверный сокет Socket s = ss.accept();//Слушаем подключения InputStream is = s.getInputStream();//Получаем ссылку на входящий поток InputStreamReader isr = new InputStreamReader(is);//Оборачиваем ее в ридер BufferedReader br = new BufferedReader(isr);//Оборачиваем ридер в буфер String messageFromClient = br.readLine();//Читаем строку System.out.println("Сообщение от клиента: " + messageFromClient); } catch (IOException ex) { System.err.println("Ошибка ввода-вывода на сервере"); } } public static void main(String[] args) { new ServerSocketExample(); } }
Исходный код клиента:
import java.io.*; import java.net.*; public class ClientSocketExample() { ClientSocketExample(){ Socket s = null; try { s = new Socket("localhost", 5524);//Подключаемся к серверу OutputStream os = s.getOutputStream();//Получаем ссылку на исходящий поток OutputStreamWriter osw = new OutputStreamWriter(os);//Оборачиваем ее в писателя BufferedWriter bw = new BufferedWriter(osw);//Оборачиваем писателя в буфер bw.write("Клиентский сокет установил соединение с сервером\n\t");//Пишем строку bw.flush();//Проталкиваем данные из буфера непосредственно в поток } catch (UnknownHostException ex) { System.out.println("Хост не найден"); } catch (IOException ex) { System.err.println("Ошибка ввода-вывода на клиенте"); } finally{//В случае неудачи необходимо корректно закрыть сокет if(s!=null){ try { s.close(); } catch (IOException ex) { System.out.println("Ошибка при закрытии клиентского сокета"); } } } } public static void main(String[] args) { new ClientSocketExample(); } }
Код прокомментирован, но, думаю, стоит еще немного добавить. В серверном и клиентском примерах мы оборачивали поток сначала для чтения/записи тектовых данных, затем делали поток буферизованным. Буферизация нужна для того, чтобы поток постоянно не обращался к операционной системе за новыми данными, а сразу бы имел достаточное их количество у себя. Что касается чтения текстовых данных, то мы это обеспечили обертками InputStreamReader и OutputStreamWriter. Если необходимо читать бинарные данные, то лучше потоки оборачивать в BufferedInputStream и BufferedOutputStream.
Заключение
Эта статья всего лишь знакомит читателя с работой с сокетами на языке java. Следующим шагом для реализации клиент-серверных решений будет изучение библиотеки NIO, распололженной в пакете java.nio. С ее помощью можно достичь большего быстродействия, чем с сокетами.