JavaTips - Сетевое программирование.
В связи с увеличивающимся использованием веб приложений работа с сетями получает
большое распространение в мире программирования. Например, типичная программа CGI
обслуживает HTML страницу для броузера через HTTP. HTTP протокол использует сокеты (гнезда)
для выполнения этой операции.
Сокет (гнездо) - это эффективное средство для межпроцессорной связи. Например, если
вы имеете два процесса, выполняющиеся на одной машине и хотите, чтобы они совместно
использовали некоторые данные, то вместо одного процесса записи в файл и другого,
выполняющего чтение, часто бывает лучше установить гнездовое соединение между ними.
Соеты (гнезда) поддерживают дуплексную связь, то есть, вы можете одновременно посылать и
получать данные.
Сокетное программирование основано на архитектуре клиент-сервер, в которой
сервер обслуживает запросы от одного или более клиентов. Вообще, сервер предоставляет
информацию клиенту, а клиент заботится о ее форматировании или обеспечении.
Сокеты имеют две разновидности. TCP/IP (Протокол управления транспортным
уровнем/интернет протокол) сокеты, также называемые сокетами с
установлением логического соединения, используются для надежного предоставления
данных. UDP (Протокол датаграмм пользователя) сокеты, также называемые
сокетами без установления логического соединения, используются для более
быстрой выдачи данных, когда надежность не важна.
Сервер обычно следует следующим шагам:
1. Открывает сокет на порте и ждет соединение на нем.
2. После получения запроса он выходит из состояния ожидания, и возвращает
сокет, которое может использоваться для выборки запросов и посылки данных.
Причина возвращения сокета состоит в том, чтобы позволить серверу продолжать слушать
дальнейшие запросы соединения на том же самом сокете. Все данные, посланные от
клиента, приходят через входной поток сокета, и вывод может быть записан в
выходной поток сокета так, чтобы клиент смог выбрать именно его. Данный процесс может
повторяться, пока сервер не будет выключен.
3. Во время выключения он закрывает сокет, синхронно открытый на шаге 1.
Java делает этот процесс достаточно простым, поддерживая класс java.net.ServerSocket,
который берет hostname и порт как параметры для конструктора. Он открывает
сокет сервера (поскольку имя подразумевает часть сервера связи с клиентским
сервером). Используйте метод accept() для выполнения функции ожидания соединения. После получения
соединения, метод accept() возвращает сокет, который поддерживает удобный
API подобно Socket.getInputStream() и Socket.getOutputStream() для
восстановления входного сокета и выходных потоков соответственно. Процесс чтения и
записи подобен другим потокам. Сокет сервера закрывается, используя метод close().
Клиент обычно следует следующим шагам:
1. Устанавливает соединение с сервером.
2. Читает/записывает данные или команды в сервер.
3. Закрывает сокет.
Java поддерживает класс java.net.Socket, который имеет конструктор для установления
соединения с определенной главной ЭВМ на специфическом порте. Как и с сервером,
вход сокета и выходные потоки используется для обмена данными.
Давайте посмотрим на типовой пример клиента и сервера.
// Server.java
import java.net.*;
import java.io.*;
/** The server. */
public class Server {
/** The default port for the server. */
public final static int DEFAULT_PORT = 8000;
/** The server version. */
public final static byte[] VERSION = { 'V', '1', '.', '0' };
/** The version command. */
public final static String VERSION_CMD = "VERS";
/** The quit command. */
public final static String QUIT_CMD = "QUIT";
/** The server socket. */
private ServerSocket ss;
/** The stop flag used to stop the server. */
private boolean stopFlag;
/** Initialize the server. */
public Server() throws IOException {
this.ss = new ServerSocket(DEFAULT_PORT);
}
/** Runs the server. */
public void runServer() throws IOException {
// Reset the flag
this.stopFlag = false;
System.out.println("Server: Started");
// Loop till the server is not stopped
while(!stopFlag) {
// Wait for a connection from the client
Socket s = ss.accept();
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
// Get the socket input and output streams
in = new BufferedInputStream(s.getInputStream());
out = new BufferedOutputStream(s.getOutputStream());
// Process the client request
byte[] buf = new byte[4];
while(in.read(buf, 0, buf.length) != -1) {
String cmd = new String(buf);
System.out.println("Server: Command: " + cmd);
// Handle the command
if (cmd.equals(VERSION_CMD)) {
// Send the server's version to the client
out.write(VERSION, 0, VERSION.length);
out.flush();
}
else if (cmd.equals(QUIT_CMD)) {
// Stop the server
stopFlag = true;
}
}
}
finally {
// Close
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (s != null) {
s.close();
}
}
catch(IOException ex) {}
System.out.println("Server: Stopped");
}
} // while(!stopFlag)
}
/** Main. */
public static void main(String[] args) {
Server svr = null;
try {
svr = new Server();
svr.runServer();
}
catch(Exception ex) {
ex.printStackTrace();
}
finally {
// Close
try {
if (svr != null) {
svr.ss.close();
}
}
catch(IOException ex) {}
}
}
}
// Client.java
import java.net.*;
import java.io.*;
/** The client. */
public class Client {
/** Start the client. */
public void runClient() throws IOException {
System.out.println("Client: Started");
Socket s = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
// Connect to the server
s = new Socket("127.0.0.1", 8000);
// Get the input and output streams
in = new BufferedInputStream(s.getInputStream());
out = new BufferedOutputStream(s.getOutputStream());
// Send command to get the server version
System.out.println("Client: Getting server version");
byte[] verCmd = { 'V', 'E', 'R', 'S' };
out.write(verCmd, 0, verCmd.length);
out.flush();
// Get the server version
byte[] buf = new byte[4];
in.read(buf, 0, buf.length);
String ver = new String(buf);
// Print the server version
System.out.println("Client: Server version: " + ver);
// Wait for 3 seconds
try {
Thread.sleep(3000);
}
catch(InterruptedException ex) {}
// Send quit command to the server
System.out.println("Client: Shutdown server");
byte[] quitCmd = { 'Q', 'U', 'I', 'T' };
out.write(quitCmd, 0, quitCmd.length);
out.flush();
}
finally {
// Close
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (s != null) {
s.close();
}
}
catch(IOException ex) {}
}
}
/** Main. */
public static void main(String[] args) {
try {
Client client = new Client();
client.runClient();
}
catch(IOException ex) {
ex.printStackTrace();
}
}
}
Существуют два класса, показанные выше: Server.java и Client.java,
которые представляют сервер и клиента соответственно. Метод main() для
сервера создает образец сервера, который создает сокет сервера, на котором сервер
получает запросы соединения. Затем, вызывается метод runServer(), который
запускает режим ввода, ждущий соединения от клиента. После получения запроса
соединения, сервер выбирает клиентский запрос и обрабатывает его. Если клиент
посылает команду QUIT, сервер останавливается. Хотя, это не является
общепринятой ситуацией в реализациях клиент-сервер, я делаю это ради простоты.
Перед возвратом, метод runServer() закрывает сокет сервера.
Клиентская часть довольно проста. Она запускается соединением с сервером. Обратите
внимание, что конструктор сокета заботится о соединении для определенного сервера.
Затем клиент посылает команду для выбора версии сервера и, в заключение, посылает
команду QUIT для закрытия системы сервера. Перед возвратом, метод
runClient() закрывает сокет.
Это был действительно простой сервер. Он ведь был одно-поточным и итерационным.
Итерационный сервер выполняет текущий запрос перед принятием любых дальнейших
соединений. В реальном мире серверы являются параллельными, то есть они
обрабатывают больше одного запроса одновременно. Такой сервер обычно порождает
процесс/вычислительный поток для обработки каждого запроса.
В этой статье мы говорили относительно TCP/IP сокеты, которые являются удобными для
надежной связи. В следующий раз мы будем говорить относительно UDP чокетов, которые
являются более быстрыми, но не гарантируют надежную передачу данных.
Nitin
Источник: www.javapower.ru
|