Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






Старт обычного потока




завершение обычного потока

Поток-демон (из-за вызова метода sleep(10000)) не успел завершить выполнение своего кода до завершения основного потока приложения, связанного с методом main(). Базовое свойство потоков-демонов заключается в возможности основного потока приложения завершить выполнение потока-демона (в отличие от обычных потоков) с окончанием кода метода main(), не обращая внимания на то, что поток-демон еще работает. Если уменьшать время задержки потока-демона, то он может успеть завершить свое выполнение до окончания работы основного потока.

Потоки в графических приложениях

Добавить анимацию в апплет можно при использовании потоков. Поток, ассоциированный с апплетом, следует запускать тогда, когда апплет становится видимым, и останавливать при сворачивании браузера. В этом случае метод
repaint() обновляет экран, в то время как программа выполняется. Поток создает анимационный эффект повторением вызова метода paint() и отображением графики в новой позиции.

/* пример # 7: освобождение ресурсов апплетом: GraphicThreadsDemo.java */

package chapt14;

import java.awt.Color;

import java.awt.Container;

import java.awt.Graphics;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JPanel;

 

public class GraphicThreadsDemo extends JFrame {

JPanel panel = new JPanel();

Graphics g;

JButton btn = new JButton("Добавить шарик");

int i;

 

public GraphicThreadsDemo() {

setBounds(100, 200, 270, 350);

Container contentPane = getContentPane();

contentPane.setLayout(null);

btn.setBounds(50, 10, 160, 20);

contentPane.add(btn);

panel.setBounds(30, 40, 200, 200);

panel.setBackground(Color.WHITE);

contentPane.add(panel);

btn.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent ev) {

new BallThread(panel).start();

i++;

repaint();

}

});

}

public static void main(String[] args) {

GraphicThreadsDemo frame =

new GraphicThreadsDemo();

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setVisible(true);

}

public void paint(Graphics g){

super. paint(g);

g.drawString("Количество шариков: " + i, 65, 300);

}

}

class BallThread extends Thread {

JPanel panel;

private int posX, posY;

private final int BALL_SIZE = 10;

private double alpha;

private int SPEED = 4;

 

BallThread(JPanel p) {

this. panel = p;

//задание начальной позиции и направления шарика

posX = (int)((panel.getWidth() - BALL_SIZE)

* Math.random());

posY = (int)((panel.getHeight() - BALL_SIZE)

* Math.random());

alpha = Math.random() * 10;

}

public void run() {

while (true) {

posX += (int)(SPEED * Math.cos(alpha));

posY += (int)(SPEED * Math.sin(alpha));

//вычисление угла отражения

if (posX >= panel.getWidth() - BALL_SIZE)

alpha = alpha + Math.PI - 2 * alpha;

else if (posX <= 0)

alpha = Math.PI - alpha;

if (posY >= panel.getHeight() - BALL_SIZE)

alpha = -alpha;

else if (posY <= 0)

alpha = -alpha;

paint(panel.getGraphics());

}

}

public void paint(Graphics g) {

//прорисовка шарика

g.setColor(Color.BLACK);

g.fillArc(posX, posY, BALL_SIZE, BALL_SIZE, 0, 360);

g.setColor(Color.WHITE);

g.drawArc(posX + 1, posY + 1, BALL_SIZE,

BALL_SIZE, 120, 30);

try {

sleep(30);

} catch (InterruptedException e) {

e.printStackTrace();

}

//удаление шарика

g.setColor(panel.getBackground());

g.fillArc(posX, posY, BALL_SIZE, BALL_SIZE, 0, 360);

}

}

Рис.14.2. Потоки в апплетах

При вызове метода stop() апплета поток перестает существовать, так как ссылка на него устанавливается в null и освобождает ресурсы. Для следующего запуска потока необходимо вновь инициализировать ссылку и вызвать метод start() потока.

Методы synchronized

Очень часто возникает ситуация, когда несколько потоков, обращающихся к некоторому общему ресурсу, начинают мешать друг другу; более того, они могут повредить этот общий ресурс. Например, когда два потока записывают информа­цию в файл/объект/поток. Для предотвращения такой ситуации может использоваться ключевое слово synchronized. Синхронизации не требуют только атомарные процессы по записи/чтению, не превышающие по объему 32 бит.

В качестве примера будет рассмотрен процесс записи информации в файл двумя конкурирующими потоками. В методе main() классa SynchroThreads создаются два потока. В этом же методе создается экземпляр класса Synchro, содержащий поле типа FileWriter, связанное с файлом на диске. Экземпляр Synchro передается в качестве параметра обоим потокам. Первый поток записывает строку методом writing() в экземпляр класса Synchro. Второй поток также пытается сделать запись строки в тот же самый объект Synchro. Для избежания одновременной записи такие методы объявляются как synchronized. Синхронизированный метод изолирует объект, после чего объект становится недоступным для других потоков. Изоляция снимается, когда поток полностью выполнит соответствующий метод. Другой способ снятия изоляции – вызов метода wait() из изолированного метода.

В примере продемонстрирован вариант синхронизации файла для защиты от одновременной записи информации в файл двумя различными потоками.

/* пример # 8: синхронизация записи информации в файл: MyThread.java: Synchro.java: SynchroThreads.java */

package chapt14;

import java.io.*;

 

public class Synchro {

private FileWriter fileWriter;

 

public Synchro(String file) throws IOException {

fileWriter = new FileWriter(file, true);

}

public void close() {

try {

fileWriter.close();

} catch (IOException e) {

e.printStackTrace();

}

}

public synchronized void writing(String str, int i) {

try {

System. out. print(str + i);

fileWriter.append(str + i);

Thread. sleep ((long)(Math. random () * 50));

System. out. print("->" + i + " ");

fileWriter.append("->" + i + " ");

} catch (IOException e) {

System. err. print("ошибка файла");

e.printStackTrace();

} catch (InterruptedException e) {

System. err. print("ошибка потока");

e.printStackTrace();

}

}

}

package chapt14;

 

public class MyThread extends Thread {

private Synchro s;

 

public MyThread(String str, Synchro s) {

super (str);

this. s = s;

}

public void run() {

for (int i = 0; i < 5; i++) {

s.writing(getName(), i);

}

}

}

package chapt14;

import java.io.*;

 

public class SynchroThreads {

public static void main(String[] args) {

try {

Synchro s = new Synchro("c:\\temp\\data.txt");

 

MyThread t1 = new MyThread("First", s);

MyThread t2 = new MyThread("Second", s);

t1.start();

t2.start();

t1.join();

t2.join();

s.close();

} catch (IOException e) {

System. err. print("ошибка файла");

e.printStackTrace();

} catch (InterruptedException e) {

System. err. print("ошибка потока");

e.printStackTrace();

}

}

}

В результате в файл будет выведено:

First0->0 Second0->0 First1->1 Second1->1 First2->2

Second2->2 First3->3 Second3->3 First4->4 Second4->4

Код построен таким образом, что при отключении синхронизации метода writing() при его вызове одним потоком другой поток может вклиниться и произвести запись своей информации, несмотря на то, что метод не завершил запись, инициированную первым потоком.

Вывод в этом случае может быть, например, следующим:

First0Second0->0 Second1->0 First1->1 First2->1 Second2->2 First3->3 First4->2 Second3->3 Second4->4 ->4

Инструкция synchronized

Синхронизировать объект можно не только при помощи методов с соответству-
ющим модификатором, но и при помощи синхронизированного блока кода. В этом случае происходит блокировка объекта, указанного в инструкции synchronized,
и он становится недоступным для других синхронизированных методов и блоков. Обычные методы на синхронизацию внимания не обращают, поэтому ответственность за грамотную блокировку объектов ложится на программиста.

/* пример # 9: блокировка объекта потоком: TwoThread.java */

package chapt14;

public class TwoThread {

public static void main(String args[]) {

final StringBuffer s = new StringBuffer();

new Thread() {

public void run() {

int i = 0;

synchronized (s) {

while (i++ < 3) {

s.append("A");

try {

sleep (100);

} catch (InterruptedException e) {

System. err. print(e);

}

System. out. println(s);

}

} //конец synchronized

}

}.start();

new Thread() {

public void run() {

int j = 0;

synchronized (s) {

while (j++ < 3) {

s.append("B");

System. out. println(s);

}

} //конец synchronized

}

}.start();

}

}

В результате компиляции и запуска будет, скорее всего (так как и второй поток может заблокировать объект первым), выведено:

A

AA

AAA

AAAB

AAABB

AAABBB

Один из потоков блокирует объект, и до тех пор, пока он не закончит выполнение блока синхронизации, в котором производится изменение значения объекта, ни один другой поток не может вызвать синхронизированный блок для этого объекта.

Если в коде убрать синхронизацию объекта s, то вывод будет другим, так как другой поток сможет получить доступ к объекту и изменить его раньше, чем первый закончит выполнение цикла.

В следующем примере рассмотрено взаимодействие методов wait() и
notify() при освобождении и возврате блокировки в synchronized блоке. Эти методы используются для управления потоками в ситуации, когда необходимо задать определенную последовательность действий без повторного запуска потоков.

Метод wait(), вызванный внутри синхронизированного блока или метода, останавливает выполнение текущего потока и освобождает от блокировки захваченный объект, в частности объект lock. Возвратить блокировку объекта потоку можно вызовом метода notify() для конкретного потока или
notifyAll() для всех потоков. Вызов может быть осуществлен только из другого потока, заблокировавшего, в свою очередь, указанный объект.

/* пример # 10: взаимодействие wait() и notify(): Blocked.java: Runner.java */

package chapt14;

 

public class Blocked {

private int i = 1000;

 

public int getI() {

return i;

}

public void setI(int i) {

this. i = i;

}

public synchronized void doWait() {

try {

System. out. print("Не ");

this. wait(); /* остановка потока и

освобождение блокировки*/

System. out. print("сущностей "); // после возврата блокировки

Thread. sleep (50);

} catch (InterruptedException e) {

e.printStackTrace();

}

for (int j = 0; j < 5; j++) i/=5;

System. out. print("сверх ");

}

}

package chapt14;

 

public class Runner {

public static void main(String[] args) {

Blocked lock = new Blocked();

new Thread() {

public void run() {

lock.doWait();

}}.start();

try {

Thread. sleep (500);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (lock) { // 1

lock.setI(lock.getI() + 2);

System. out. print("преумножай ");

lock.notify(); // возврат блокировки

}

synchronized (lock) { // 2

lock.setI(lock.getI() + 3);

//блокировка после doWait()

System. out. print("необходимого. ");

try {

lock.wait(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System. out. print("=" + lock.getI());

}

}

В результате компиляции и запуска будет выведено следующее сообщение:

Не преумножай сущностей сверх необходимого. =3

Задержки потоков методом sleep() используются для точной демонстрации последовательности действий, выполняемых потоками. Если же в коде приложения убрать все блоки синхронизации, а также вызовы методов wait() и notify(), то вывод может быть следующим:

Не сущностей преумножай необходимого. =1005сверх

Состояния потока

В классе Thread объявлено внутреннее перечисление State, простейшее применение элементов которого призвано помочь в отслеживании состояний потока в процессе функционирования приложения и, как следствие, в улучшении управления им.

/* пример # 11: состояния NEW, RUNNABLE, TIMED_WAITING, TERMINATED: ThreadTimedWaitingStateTest.java */

package chapt14;

public class ThreadTimedWaitingStateTest extends Thread {

public void run() {

try {

Thread. sleep (50);

} catch (InterruptedException e) {

System. err. print("ошибка потока");

}

}

public static void main(String [] args){

try {

Thread thread = new ThreadTimedWaitingStateTest();

// NEW – поток создан, но ещё не запущен

System. out. println("1: " + thread.getState());

thread.start();

// RUNNABLE – поток запущен

System. out. println("2: " + thread.getState());

Thread.sleep(10);

// TIMED_WAITING

// поток ждет некоторое время окончания работы другого потока

System.out.println("3: " + thread.getState());

thread.join();

// TERMINATED – поток завершил выполнение

System.out.println("4: " + thread.getState());

} catch (InterruptedException e) {

System.err.print("ошибка потока");

}

}

}

В результате компиляции и запуска будет выведено:

NEW

RUNNABLE

TIMED_WAITING

TERMINATED

/* пример # 12: состояния BLOCKED, WAITING: ThreadWaitingStateTest.java */

package chapt14;

public class ThreadWaitingStateTest extends Thread {

public void run() {

try {

synchronized (this) {

wait();

}

} catch (InterruptedException e) {

System.err.print("ошибка потока");

}

}

 

public static void main(String[] args) {

try {

Thread thread = new ThreadWaitingStateTest();

thread.start();

synchronized (thread) {

Thread.sleep(10);

// BLOCKED – because thread attempting to acquire a lock

System.out.println("1: " + thread.getState());

}

Thread.sleep(10);

// WAITING – метод wait() внутри synchronized

// останавил поток и освободил блокировку

System.out.println("2: " + thread.getState());

thread.interrupt();

} catch (InterruptedException e) {

System.err.print("ошибка потока");

}

}

}

В результате компиляции и запуска будет выведено:

BLOCKED

WAITING

Потоки в J2SE 5

Java всегда предлагала широкие возможности для мультипрограммирования: потоки – это основа языка. Однако очень часто использование таких возможностей становилось ловушкой: правильно написать и отладить многопоточную программу достаточно сложно.

В версии 1.5 языка добавлены пакеты классов java.util.concurrent.locks, java.util.concurrent.atomic, java.util.concurrent, возможности которых обеспечивают более высокую производительность, масштабируемость, построение потокобезопасных блоков параллельных (concurrent) классов, вызов утилит синхронизации, использование семафоров, ключей и atomic-переменных.

Возможности синхронизации существовали и ранее. Практически это означало, что синхронизированные объекты блокировались, хотя необходимо это было далеко не всегда. Например, поток, изменяющий одну часть объекта Hashtable, блокировал работу других потоков, которые хотели прочесть (даже не изменить) совсем другую часть этого объекта. Поэтому введение дополнительных возможностей, связанных с синхронизаций потоков и блокировкой ресурсов довольно логично.

Ограниченно потокобезопасные (thread safe) коллекции и вспомогательные классы управления потоками сосредоточены в пакете java.util.concurrent. Среди них можно отметить:

· параллельные классы очередей ArrayBlockingQueue (FIFO очередь с фиксированой длиной), PriorityBlockingQueue (очередь с приоритетом) и ConcurrentLinkedQueue (FIFO очередь с нефиксированой длиной);

· параллельные аналоги существующих синхронизированых классов-коллекций ConcurrentHashMap (аналог Hashtable) и
CopyOnWriteArrayList (реализация List, оптимизированная для случая, когда количество итераций во много раз превосходит количество вставок и удалений);

· механизм управления заданиями, основанный на возможностях класса Executor, включающий пул потоков и службу их планирования;

· высокопроизводительный класс Lock, поддерживающий ограниченные ожидания снятия блокировки, прерываемые попытки блокировки, очереди блокировки и установку ожидания снятия нескольких блокиро­вок посредством класса Condition;

· классы атомарных переменных (AtomicInteger, AtomicLong,
AtomicReference), а также их высокопроизводительные аналоги SyncronizedInt и др.;

· классы синхронизации общего назначения, такие как Semaphore, CountDownLatch (позволяет потоку ожидать завершения нескольких операций в других потоках), CyclicBarrier (позволяет нескольким потокам ожидать момента, когда они все достигнут какой-либо точки) и Exchanger (позволяет потокам синхронизироваться и обмениваться информацией);

· обработка неотловленных прерываний: класс Thread теперь поддерживает установку обработчика на неотловленные прерывания (подобное ранее было доступно только в ThreadGroup).

Хорошим примером новых возможностей является синхронизация коллекции типа Hashtable. Объект Hashtable синхронизируется целиком, и если один поток занял кэш остальные потоки вынуждены ожидать освобождения объекта. В случае же использования нового класса ConcurrentHashMap практически все операции чтения могут проходить одновременно, операции чтения и записи практически не задерживают друг друга, и только одновременные попытки записи могут блокироваться. В случае же использования данного класса как кэша (спецификой которого является большое количество операций чтения и малое – записи), блокироваться многопоточный код практически не будет.

В таблице приведено время выполнения (в миллисекундах) программы, использовавшей в качестве кэша ConcurrentHashMap и Hashtable. Тесты проводились на двухпроцессорном сервере под управлением Linux. Количество данных для большего количества потоков увеличивалось.

Количество потоков ConcurrentHashMap Hashtable
  1.00 1.03
  2.59 32.40
  5.58 78.23
  13.21 163.48
  27.58 341.21
  57.27 778.41

// пример # 13: применение семафора: Sort.java: ArraySort.java

package chapt14;

import java.util.concurrent.*;

 

public class Sort {

public static final int ITEMS_COUNT = 15;

public static double items [];

// семафор, контролирующий разрешение на доступ к элементам массива

public static Semaphore sortSemaphore =

new Semaphore(0, true);

 

public static void main(String[] args) {

items = new double [ ITEMS_COUNT ];

for (int i = 0; i < items.length; ++i)

items [i] = Math. random ();

new Thread(new ArraySort(items)).start();

for (int i = 0; i < items. length; ++i) {

/*

* при проверке доступности элемента сортируемого

* массива происходит блокировка главного потока

* до освобождения семафора

*/

sortSemaphore. acquireUninterruptibly();

System. out. println(items [i]);

}

}

}

class ArraySort implements Runnable {

private double items[];

 

public ArraySort(double items[]) {

this. items = items;

}

public void run(){

for (int i = 0; i < items.length - 1; ++i) {

for (int j = i + 1; j < items.length; ++j) {

if (items[i] < items[j]) {

double tmp = items[i];

items[i] = items[j];

items[j] = tmp;

}

}

// освобождение семафора

Sort. sortSemaphore. release();

try {

Thread. sleep (71);

} catch (InterruptedException e) {

System. err. print(e);

}

}

Sort. sortSemaphore. release();

}

}

Задания к главе 14

Вариант А

1. Cоздать апплет с использованием потоков: строка движется горизонтально, отражаясь от границ апплета и меняя при этом случайным образом свой цвет.

2. Cоздать апплет с использованием потоков: строка движется по диагонали. При достижении границ апплета все символы строки случайным образом меняют регистр.

3. Организовать сортировку массива методами Шелла, Хоара, пузырька, на основе бинарного дерева в разных потоках.

4. Реализовать сортировку графических объектов, используя алгоритмы из задания 3.

5. Создать апплет с точкой, движущейся по окружности с постоянной угловой скоростью. Сворачивание браузера должно приводить к изменению угловой скорости движения точки для следующего запуска потока.

6. Изобразить точку, пересекающую с постоянной скоростью окно слева направо (справа налево) параллельно горизонтальной оси. Как только точка доходит до границы окна, в этот момент от левого (правого) края с вертикальной координатной y, выбранной с помощью датчика случайных чисел, начинает свое движение другая точка и т.д. Цвет точки также можно выбирать с помощью датчика случайных чисел. Для каждой точки создается собственный поток.

7. Изобразить в приложении правильные треугольники, вращающиеся
в плоскости экрана вокруг своего центра. Каждому объекту соответствует поток с заданным приоритетом.

8. Условие предыдущей задачи изменяется таким образом, что центр вращения перемещается от одного края окна до другого с постоянной скоростью параллельно горизонтальной оси.

9. Cоздать фрейм с тремя шариками, одновременно летающими в окне.
С каждым шариком связан свой поток со своим приоритетом.

10. Два изображения выводятся в окно. Затем они постепенно исчезают
с различной скоростью в различных потоках (случайным образом выбираются точки изображения, и их цвет устанавливается в цвет фона).

11. Условие предыдущей задачи изменить на применение эффекта постепенного “проявления” двух изображений.

Вариант B

Для заданий варианта В главы 4 организовать синхронизированный доступ к ресурсам (файлам). Для каждого процесса создать отдельный поток выполнения.

Тестовые задания к главе 14

Вопрос 14.1.

Дан код:

class Q implements Runnable{

int i = 0;

public int run(){

System.out.println("i = "+ ++i);

return i;

}}

public class Quest1 {

public static void main(String[] args) {

Q ob = new Q();

ob.run();

}}

При попытке компиляции и запуска будет выведено:

1) i = 0;

2) i = 1;

3) ошибка компиляции: создать объект потока можно только с помощью инициализации объекта класса Thread или его наследников;

4) ошибка компиляции: неправильно реализован метод run();

5) ошибка времени выполнения: поток должен запускаться методом start().

Вопрос 14.2.

Дан код:

Thread t1= new Thread();

t1.setPriority(7);

ThreadGroup tg= new ThreadGroup("TG");

tg.setMaxPriority(8);

Thread t2= new Thread(tg,"t2");

System.out.print("приоритет t1="

+ t1.getPriority());

System.out.print(", приоритет t2="

+ t2.getPriority());

В результате компиляции и выполнения будет выведено:

1) приоритет t1 = 7, приоритет t2 = 5;

2) приоритет t1 = 7, приоритет t2 = 8;

3) приоритет t1 = 10, приоритет t2 = 8;

4) нет правильного.

Вопрос 14.3.

Дан код:

class T1 implements Runnable{

public void run(){

System.out.print("t1 ");

} }

class T2 extends Thread{

public void run(){

System.out.print("t2 ");

} }

public class Quest3 {

public static void main(String[] args) {

T1 t1 = new T1();

T2 t2 = new T2(t1);

t1.start();

t2.start();

} }

В результате компиляции и запуска будет выведено:

1) t1 t2;

2) t2 t1;

3) ошибка компиляции: метод start() не определен в классе T1;

4) ошибка компиляции: в классе T2 не определен конструктор, принимающий в качестве параметра объект Thread;

5) ничего из перечисленного.

Вопрос 14.4.

Какое из указанных действий приведет к тому, что поток переходит в состояние “пассивный”? (выберите два)

1) вызов метода sleep() без параметра;

2) вызов метода stop();

3) окончание выполнения метода run();

4) вызов метода notifyAll();

5) вызов метода wait() с параметром null.

Вопрос 14.5.

Дан код:

class Quest5 extends Thread {Quest5 () { }Quest5 (Runnable r) { super(r); } public void run() { System.out.print("thread "); } public static void main(String[] args) { Runnable r = new Quest5(); //1Quest5 t = new Quest5(r); //2 t.start(); } }

При попытке компиляции и запуска будет выведено:

1) ошибка компиляции в строке //1;2) ошибка компиляции в строке //2;3) thread;4) thread thread; 5) код будет откомпилирован, но ничего выведено не будет.

 
Глава 15

СЕТЕВЫЕ ПРОГРАММЫ

Поддержка Интернет

Язык Java делает сетевое программирование простым благодаря наличию специальных средств и классов. Большинство этих классов находится в пакете java.net. Сетевые классы имеют методы для установки сетевых соединений передачи запросов и сообщений. Многопоточность позволяет обрабатывать несколько соединений. Сетевые приложения используют Internet-приложения, к которым относятся Web-браузер, e-mail, сетевые новости, передача файлов. Для создания таких приложений используются сокеты, порты, протоколы TCP/IP, UDP.

Приложения клиент/сервер используют компьютер, выполняющий специальную программу-сервер, которая обычно устанавливается на удаленном компьютере и предоставляет услуги другим программам-клиентам. Клиент - это программа, получающая услуги от сервера. Клиент устанавливает соединение с сервером и пересылает серверу запрос. Сервер осуществляет прослушивание клиентов, получает и выполняет запрос после установки соединения. Результат выполнения запроса может быть возвращен сервером клиенту. Запросы и сообщения представляют собой записи, структура которых определяется используемыми протоколами.

В стеке протоколов TCP/IP используются следующие прикладные протоколы:

HTTP - Hypertext Transfer Protocol (WWW);

NNTP - Network News Transfer Protocol (группы новостей);

SMTP - Simple Mail Transfer Protocol (посылка почты);

POP3 - Post Office Protocol (чтение почты с сервера);

FTP - File Transfer Protocol (протокол передачи файлов).

Каждый компьютер из подключенных к сети по протоколу TCP/IP имеет уникальный IP-адрес, используемый для идентификации и установки соединения. Это 32-битовое число, обычно записываемое как четыре числа, разделенные точками, каждое из которых изменяется от 0 до 255. IP-адрес может быть временным и выделяться динамически для каждого подключения или быть постоянным, как для сервера. IP-адреса используются во внутренних сетевых системах. Обычно при подключении к Internet вместо числового IP-адреса используются символьные имена (например: www.bsu.by), называемые именами домена. Специальная программа DNS (Domain Name Server), располагаемая на отдельном сервере, проверяет адрес и преобразует имя домена в числовой IP-адрес. Если в качестве сервера используется этот же компьютер без сетевого подключения, в качестве IP-адреса указывается 127.0.0.1 или localhost. Для явной идентификации услуг к IP-адресу присоединяется номер порта через двоеточие, например 217.21.43.10:443. Здесь указан номер порта 443. Номера портов от 1 до 1024 могут быть заняты для внутреннего использования, например, если порт явно не указан, браузер воспользуется значением по умолчанию: 20FTP -данные, 21FTP -управление, 53DNS, 80HTTP, 25SMTP, 110POP3, 119NNTP. К серверу можно подключиться с помощью различных портов. Каждый порт указывает конкретное место соединения на указанном компьютере и предоставляет определенную услугу.

Для доступа к сети Internet в браузере указывается адрес URL. Адрес URL (Universal Resource Locator) состоит из двух частей – префикса протокола (http, https, ftp и т.д.) и URI (Universal Resource Identifier). URI содержит Internet-адрес, необязательный номер порта и путь к каталогу, содержащему файл, например:

http://www.bsu.by

URI не может содержать такие специальные символы, как пробелы, табуляции, возврат каретки. Их можно задавать через шестнадцатеричные коды. Например: %20 обозначает пробел. Другие зарезервированные символы: символ & –разделитель аргументов, символ? –следует перед аргументами запросов, символ + – пробел, символ # – ссылки внутри страницы (имя_страницы#имя_ссылки).

Определить IP-адрес в приложеннии можно с помощью объекта класса InetAddress из пакета java.net.

Класс InetAddress не имеет public -конструкторов. Создать объект класса можно с помощью статических методов. Метод getLocalHost() возвращает объект класса InetAddress, содержащий IP-адрес и имя компьютера, на котором выполняется программа. Метод getByName(String host) возвращает объект класса InetAddress, содержащий IP-адрес по имени компьютера, используя пространство имен DNS. IP-адрес может быть временным, различным для каждого соединения, однако он остается постоянным, если соединение установлено. Метод getByAddress(byte[] addr) создает объект класса InetAddress, содержащий имя компьютера, по IP-адресу, представленному в виде массива байт. Если компьютер имеет несколько IP, то получить их можно методом getAllByName(String host),возвращающим массив объектов класса InetAddress. Если IP для данной машины один, то массив будет содержать один элемент. Метод getByAddress(String host, byte[] addr) создает объект класса InetAddress с заданным именем и IP-адресом, не проверяя существование такого компьютера. Все эти методы являются потенциальными генераторами исключительной ситуации UnknownHostException, и поэтому их вызов должен быть обработан с помощью throws для метода или блока try-catch. Проверить доступ к компьютеру в данный момент можно с помощью метода boolean isReachable(int timeout), который возвращает true, если компьютер доступен, где timeout – время ожидания ответа от компьютера в миллисекундах.

Следующая программа демонстрирует при наличии Internet-соединения, как получить IP-адрес текущего компьютера и IP-адрес из имени домена с помощью сервера имен доменов (DNS), к которому обращается метод getByName() класса InetAddress.

/* пример # 1: вывод IP-адреса компьютера и интернет-ресурса: InetLogic.java*/

package chapt15;

import java.net.*;

 

public class InetLogic {

public static void main(String[] args) {

InetAddress myIP = null;

InetAddress bsuIP = null;

try {

myIP = InetAddress.getLocalHost();

// вывод IP-адреса локального компьютера

System.out.println("Мой IP -> "

+ myIP.getHostAddress());

bsuIP = InetAddress.getByName(

"www.bsu.by");

// вывод IP-адреса БГУ www.bsu.by

System.out.println("BSU -> "

+ ibaIP.getHostAddress());

} catch (UnknownHostException e) {

// если не удается найти IP

e.printStackTrace();

}

}

}

В результате будет выведено, например:

Мой IP -> 172.17.16.14

BSU -> 217.21.43.10

Метод getLocalHost() класса InetAddress создает объект myIP и возвращает IP-адрес компьютера.

/* пример # 2: присваивание фиктивного имени реальному ресурсу, заданному через IP: UnCheckedHost.java */

package chapt15;

import java.io.IOException;

import java.net.InetAddress;

import java.net.UnknownHostException;

 

public class UnCheckedHost {

public static void main(String[] args) {

// задание IP-адреса лаборатории bsu.iba.by в виде массива

byte ip[] ={(byte)217, (byte)21, (byte)43, (byte)10};

try {

InetAddress addr =

InetAddress.getByAddress("University", ip);

System. out. println(addr.getHostName()

+ "-> cоединение:" + addr.isReachable(1000));

} catch (UnknownHostException e) {

System. out. println("адрес недоступен");

e.printStackTrace();

} catch (IOException e) {

System. out. println("ошибка потока");

e.printStackTrace();

}

}

}

В результате будет выведено в случае подключения к сети Интернет:

University-> cоединение:true

Для доступа к ресурсам можно создать объект класса URL, указывающий на ресурсы в Internet. В следующем примере объект URL используется для доступа к HTML-файлу, на который он указывает, и отображает его в окне браузера с помощью метода showDocument().

/* пример # 3: запуск страницы из апплета: MyShowDocument.java */

package chapt15;

import java.awt.Graphics;

import java.net.MalformedURLException;

import java.net.URL;

import javax.swing.JApplet;

 

public class MyShowDocument extends JApplet {

private URL bsu = null;

 

public String getMyURL() {

return "http://www.bsu.by";

}

public void paint(Graphics g) {

int timer = 0;

g.drawString("Загрузка страницы", 10, 10);

try {

for (; timer < 30; timer++) {

g.drawString(".", 10 + timer * 5, 25);

Thread. sleep (100);

}

bsu = new URL(getMyURL());

getAppletContext().showDocument(bsu, "_blank");

} catch (InterruptedException e) {

e.printStackTrace();

} catch (MalformedURLException e) {

// некорректно задан протокол, доменное имя или путь к файлу

e.printStackTrace();

}

}

}

Метод showDocument() может содержать параметры для отображения страницы различными способами: “ _self ” – выводит документ в текущий фрейм, “ _blank ” – в новое окно, “ _top ” – на все окно, “ _parent ” – в родительском окне, “ имя_окна ” – в окне с указанным именем. Для корректной работы данного примера апплет следует запускать из браузера, используя следующий HTML-документ:

<html>

<body align=center>

<applet code=chapt15.MyShowDocument.class></applet>

</body></html>

В следующей программе читается содержимое HTML-файла по указанному адресу и выводится в окно консоли.

/* пример # 4: чтение документа из интернета: ReadDocument.java */

package chapt15;

import java.net.*;

import java.io.*;

 

public class ReadDocument {

public static void main(String[] args) {

try {

URL lab = new URL("http://www.bsu.by");

InputStreamReader isr =

new InputStreamReader(lab.openStream());

BufferedReader d = new BufferedReader(isr);

String line = "";

while ((line = d.readLine())!= null) {

System.out.println(line);

}

} catch (MalformedURLException e) {

// некорректно заданы протокол, доменное имя или путь к файлу

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}

Сокетные соединения по протоколу TCP/IP

Сокеты (сетевые разъёмы) - это логическое понятие, соответствующее разъёмам, к которым подключены сетевые компьютеры и через которые осуществляяется двунаправленная поточная передача данных между компьютерами. Сокет определяется номером порта и IP-адресом. При этом IP-адрес используется для идентификации компьютера, номер порта – для идентификации процесса, работающего на компьютере. Когда одно приложение знает сокет другого, создается сокетное протоколо-ориентированное соединение по протоколу TCP/IP. Клиент пытается соединиться с сервером, инициализируя сокетное соединение. Сервер прослушивает сообщение и ждет, пока клиент не свяжется с ним. Первое сообщение, посылаемое клиентом на сервер, содержит сокет клиента. Сервер, в свою очередь, создает сокет, который будет использоваться для связи с клиентом, и посылает его клиенту с первым сообщением. После этого устанавливается коммуникационное соединение.

Сокетное соединение с сервером создается клиентом с помощью объекта класса Socket. При этом указывается IP-адрес сервера и номер порта. Если указано символьное имя домена, то Java преобразует его с помощью DNS-сервера к IP-адресу. Например, если сервер установлен на этом же компьютере, соединение с сервером можно установить из приложения клиента с помощью инструкции:

Socket socket = new Socket(" ИМЯ_СЕРВЕРА ", 8030);

Сервер ожидает сообщения клиента и должен быть заранее запущен с указанием определенного порта. Объект класса ServerSocket создается с указанием конструктору номера порта и ожидает сообщения клиента с помощью метода accept() класса ServerSocket, который возвращает сокет клиента:

ServerSocket server = new ServerSocket(8030);

Socket socket = server.accept();

Таким образом, для установки необходимо установить IP-адрес и номер порта сервера, IP-адрес и номер порта клиента. Обычно порт клиента и сервера устанавливаются одинаковыми. Клиент и сервер после установления сокетного соединения могут получать данные из потока ввода и записывать данные в поток вывода с помощью методов getInputStrеam() и getOutputStrеam() или к PrintStream для того, чтобы программа могла трактовать поток как выходные файлы.

В следующем примере для отправки клиенту строки "привет!" сервер вызывает метод getOutputStream() класса Socket. Клиент получает данные от сервера с помощью метода getInputStream(). Для разъединения клиента и сервера после завершения работы сокет закрывается с помощью метода close() класса Socket. В данном примере сервер отправляет клиенту строку "привет!", после чего разрывает связь.

/* пример # 5: передача клиенту строки: MyServerSocket.java */

package chapt15;

import java.io.*;

import java.net.*;

public class MyServerSocket {

public static void main(String[] args) {

Socket s = null;

try { // отправка строки клиенту

//создание объекта и назначение номера порта

ServerSocket server = new ServerSocket(8030);

s = server.accept(); //ожидание соединения

PrintStream ps =

new PrintStream(s.getOutputStream());

// помещение строки "привет!" в буфер

ps.println("привет!");

// отправка содержимого буфера клиенту и его очищение

ps.flush();

ps.close();

} catch (IOException e) {

System. err. println("Ошибка: " + e);

} finally {

if (s!= null)

s.close(); // разрыв соединения

}

}

}

/* пример # 6: получение клиентом строки: MyClientSocket.java */

package chapt15;

import java.io.*;

import java.net.*;

public class MyClientSocket {

public static void main(String[] args) {

Socket socket = null;

try { // получение строки клиентом

socket = new Socket(" ИМЯ_СЕРВЕРА ", 8030);

/* здесь "ИМЯ_СЕРВЕРА" компьютер, на котором стоит сервер-сокет"*/

BufferedReader br =

new BufferedReader(

new InputStreamReader(

socket.getInputStream()));

String msg = br.readLine();

System. out. println(msg);

socket.close();

} catch (IOException e) {

System. err. println("ошибка: " + e);

}

}

}

Аналогично клиент может послать данные серверу через поток вывода, полученный с помощью метода getOutputStream(), а сервер может получать данные через поток ввода, полученный с помощью метода getInputStream().

Если необходимо протестировать подобный пример на одном компьютере, можно выступать одновременно в роли клиента и сервера, используя статические методы getLocalHost() класса InetAddress для получения динамического IP-адреса компьютера, который выделяется при входе в сеть интернет.

Многопоточность

Сервер должен поддерживать многопоточность, иначе он будет не в состоянии обрабатывать несколько соединений одновременно. В этом случае сервер содержит цикл, ожидающий нового клиентского соединения. Каждый раз, когда клиент просит соединения, сервер создает новый поток. В следующем примере создается класс ServerThread, расширяющий класс Thread, и используется затем для соединений с многими клиентами, каждый в своем потоке.

/* пример # 7: сервер для множества клиентов: NetServerThread.java */

package chapt15;

import java.io.*;

import java.net.*;

 

public class NetServerThread {

public static void main(String[] args) {

try {

ServerSocket serv = new ServerSocket(8071);

System. out. println("initialized");

while (true) {

//ожидание клиента

Socket sock = serv.accept();

System. out. println(

sock.getInetAddress().getHostName()

+ " connected");

/*создание отдельного потока для обмена

данными с соединившимся клиентом*/

ServerThread server = new ServerThread(sock);

server.start(); //запуск потока

}

} catch (IOException e) {

System. err. println(e);

}

}

}

class ServerThread extends Thread {

private PrintStream os; //передача

private BufferedReader is; //чтение

private InetAddress addr; //адрес клиента

 

public ServerThread(Socket s) throws IOException {

os = new PrintStream(s.getOutputStream());

is = new BufferedReader(

new InputStreamReader(

s.getInputStream()));

addr = s.getInetAddress();

}

public void run() {

int i = 0;

String str;

try {

while ((str = is.readLine())!= null) {

if ("PING".equals(str))

os.println("PONG "+ ++i);

System. out. println("PING-PONG" + i

+ " with " + addr.getHostName());

}

} catch (IOException e) {

//если клиент не отвечает, соединение с ним разрывается

System. out. println("Disconnect");

} finally {

disconnect(); //уничтожение потока

}

}

public void disconnect() {

try {

System. out. println(addr.getHostName()

+ " disconnected");

os.close();

is.close();

} catch (IOException e) {

e.printStackTrace();

} finally {

this. interrupt();

}

}

}

Сервер передает сообщение, посылаемое клиенту. Для клиентских приложений поддержка многопоточности также необходима. Например, один поток ожидает выполнения операции ввода/вывода, а другие потоки выполняют свои функции.

/* пример # 8: получение и отправка сообщения клиентом в потоке: NetClientThread.java */

package chapt15;

import java.io.*;

import java.net.*;

 

public class NetClientThread {

public static void main(String[] args) {

try {

// установка соединения с сервером

Socket s = new Socket(InetAddress. getLocalHost (), 8071);

//или Socket s = new Socket("ИМЯ_СЕРВЕРА", 8071);

PrintStream ps = new PrintStream(s.getOutputStream());

BufferedReader br = new BufferedReader(

new InputStreamReader(s.getInputStream()));

for (int i = 1; i <= 10; i++) {

ps.println("PING");

System. out. println(br.readLine());

Thread. sleep (1000);

}

s.close();

} catch (UnknownHostException e) {

// если не удалось соединиться с сервером

System. out. println("адрес недоступен");

e.printStackTrace();

} catch (IOException e) {

System. out. println("ошибка I/О потока");

e.printStackTrace();

} catch (InterruptedException e) {

System. out. println(

"ошибка потока выполнения");

e.printStackTrace();

}

}

}

Сервер должен быть инициализирован до того, как клиент попытается осуществить сокетное соединение. При этом может быть использован IP-адрес локального компьютера.

Датаграммы и протокол UDP

UDP (User Datagram Protocol) не устанавливает виртуального соединения и не гарантирует доставку данных. Отправитель просто посылает пакеты по указанному адресу; если отосланная информация была повреждена или вообще не дошла, отправитель об этом даже не узнает. Однако достоинством UDP является высокая скорость передачи данных. Данный протокол часто используется при трансляции аудио- и видеосигналов, где потеря небольшого количества данных не может привести к серьезным искажениям всей информации.

По протоколу UDP данные передаются пакетами. Пакетом в этом случае UDP является объект класса DatagramPacket. Этот класс содержит в себе передаваемые данные, представленные в виде массива байт. Конструкторы класса:

DatagramPacket(byte[] buf, int length)

DatagramPacket(byte[] buf, int length,






Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных