Передача данных через сокет по имени хоста. Что такое сокет? Обмен данными с помощью сокетов

Вы хотите разработать сетевую программу на Джаве – игрушку, чат, или то и другое вместе… Вы нашли правильную статью – здесь вы сможете ознакомиться с захватывающим миром сокетов в Java. Прочитав эту статью, вам будет виден свет в конце туннеля – станет очевидным предназначение сокетов и то, как разработать простую программу с использованием сокетов на языке программирования Джава.

Что такое сокет?

На сегодняшний день использование клиентов служб мгновенного обмена сообщениями (instant messanger) стало незаменимым средством для всех пользователей Интернета. Существует множество различных протоколов и клиентов (MSN, ICQ, Skype и т. д.), о которых каждый слышал и которые мы все ежедневно используем. Но не все знают, что положено в основу их роботы – собственно для этого статья и написана. Предположим, вы установили один из клиентов служб мгновенного обмена сообщениями на ваш компьютер. После его запуска и введения имени и пароля вашего пользователя, программа пытается подключиться к серверу. Что именно означает слово "подключиться"?

Каждый компьютер в сети имеет IP-адрес. Этот адрес похож на ваш домашний адрес – он однозначно идентифицирует ваш компьютер и позволяет другим общаться с вашим компьютером. Не будем вдаваться в подробности IP-адреса, так как эта статья не о них, хочу только отметить что IP-адрес это набор номеров разделенных точками (например, 64.104.137.158). Хотя существует и другой способ идентификации компьютеров в сети – доменное имя, которое более удобное и нагляднее идентифицирует компьютер, чем простой набор чисел (например, www.сайт). В Интернете существуют специальные компьютеры, которые осуществляют преобразование доменного имени в IP-адрес и наоборот.

На одном компьютере может параллельно исполняется несколько программ. Предположим, что вы запустили 10 программ на своем компьютере, и все они ожидают, чтоб другие компьютеры связались с ними. Можете представить себе это так: вас 10 человек в большом офисе с 1 телефоном и все ждут звонков от их собственных клиентов. Как вы это решите? Можно конечно назначить ответственного работника, и он будет приносить телефон соответственному человеку, которому звонят, но тогда другие клиенты не смогут дозвониться к другим людям. Кроме того, это очень трудно и нелепо иметь ответственного работника за маршрутизацию звонков к нужным людям. Вы должно быть, уже догадались, к чему я веду – если все эти программы, исполняющиеся на одном компьютере, с гордостью просят своих клиентов связаться с ними по определенному IP-адресу, то их клиенты не будут довольны. Идея состоит в следующем … иметь отдельный IP-адрес для каждой программы, верно? НЕ ВЕРНО! Суть вопроса не правильная – это также как спрашивать об отдельном офисе для каждого из вас. Ну, тогда … может отдельных телефонных номеров будет достаточно? ДА! На сетевом жаргоне "отдельные телефонные номера" имеют название порты. Порт – это просто число и каждая из программ, которая исполняется на определенном компьютере, может выбрать уникальное число порта, чтоб определить себя для внешнего мира. ЗАПОМНИТЕ – эти порты вы не сможете найти среди аппаратных средств компьютера (даже не старайтесь их искать). Эти числа – логические. Теперь все прояснилось: существует IP-адрес, с помощью которого другие компьютеры могут распознавать определенный компьютер в сети, и порт-число, которое определяет некую программу, работающую на компьютере. Также становиться понятным и то, что две программы на разных компьютерах могут использовать один и тот же порт (два дома на разных улицах тоже могут иметь один и тот самый номер, или нет?). Ну что же, мы практически у цели, только чтоб немного вас попугать, давайте выведем формулу:

IP-адрес = уникально определяет компьютер в сети.
Порт-число = уникально определяет программу, которая исполняется на соответствующем компьютере.

Если сложить вместе выше описанные уравнения, то получим:

IP-адрес + порт-число = _____

Другими словами:

Уникально определяет программу в сети. Если вы догадались до этого сами – значит мои усилия не пропали зря. Если нет, тогда прочитайте еще раз все сначала или воспользуйтесь Google для поиска лучшей статьи. _____ – это и есть … СОКЕТ!

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

Как программировать с использованием сокетов?

Достаточно о теории, давайте перейдем к действиям. Разработаем очень простой и наглядный код на Java, который продемонстрирует возможности использования сокетов. Попробуем реализовать следующий список действий:

1) Одна Джава программа будет пытаться связаться с другой Java программой (которая отчаянно ждет кого-то, чтоб с ней связался). Назовем первую программу Клиентом, а вторую Сервером.

2) После успешного связывания с сервером, клиент ждет ввода данных от вас и отсылает текст серверу.

3) Серверная программа отсылает клиенту назад тот;t текст (для того чтоб показать, что она умеет делать даже такое полезное действие).

4) Полученный от сервера текст, клиент показывает вам, чтоб показать вам мнение сервера о вас. Приготовились приступить к разработке? Начнем. Отмечу только, что я не буду учить вас программированию на Java с чистого листа, а только объясню код, который относится к сокетам. Создайте 2 новых Джава программы и назовите их Server.java и Client.java. Я привел код ниже, только не пугайтесь, я все объясню.


import java.net.*;
import java.io.*;
public class Server {
int port = 6666; // случайный порт (может быть любое число от 1025 до 65535)
try {
ServerSocket ss = new ServerSocket(port); // создаем сокет сервера и привязываем его к вышеуказанному порту
System.out.println("Waiting for a client...");

Socket socket = ss.accept(); // заставляем сервер ждать подключений и выводим сообщение когда кто-то связался с сервером
System.out.println("Got a client:) ... Finally, someone saw me through all the cover!");
System.out.println();

// Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиенту.


String line = null;
while(true) {
line = in.readUTF(); // ожидаем пока клиент пришлет строку текста.
System.out.println("The dumb client just sent me this line: " + line);
System.out.println("I"m sending it back...");
out.writeUTF(line); // отсылаем клиенту обратно ту самую строку текста.
System.out.println("Waiting for the next line...");
System.out.println();
}
} catch(Exception x) { x.printStackTrace(); }
}
}

Import java.net.*;
import java.io.*;

public class Client {
public static void main(String ar) {
int serverPort = 6666; // здесь обязательно нужно указать порт к которому привязывается сервер.
String address = "127.0.0.1"; // это IP-адрес компьютера, где исполняется наша серверная программа.
// Здесь указан адрес того самого компьютера где будет исполняться и клиент.

Try {
InetAddress ipAddress = InetAddress.getByName(address); // создаем объект который отображает вышеописанный IP-адрес.
System.out.println("Any of you heard of a socket with IP address " + address + " and port " + serverPort + "?");
Socket socket = new Socket(ipAddress, serverPort); // создаем сокет используя IP-адрес и порт сервера.
System.out.println("Yes! I just got hold of the program.");

// Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиентом.
InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();

// Конвертируем потоки в другой тип, чтоб легче обрабатывать текстовые сообщения.
DataInputStream in = new DataInputStream(sin);
DataOutputStream out = new DataOutputStream(sout);

// Создаем поток для чтения с клавиатуры.
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
String line = null;
System.out.println("Type in something and press enter. Will send it to the server and tell ya what it thinks.");
System.out.println();

While (true) {
line = keyboard.readLine(); // ждем пока пользователь введет что-то и нажмет кнопку Enter.
System.out.println("Sending this line to the server...");
out.writeUTF(line); // отсылаем введенную строку текста серверу.
out.flush(); // заставляем поток закончить передачу данных.
line = in.readUTF(); // ждем пока сервер отошлет строку текста.
System.out.println("The server was very polite. It sent me this: " + line);
System.out.println("Looks like the server is pleased with us. Go ahead and enter more lines.");
System.out.println();
}
} catch (Exception x) {
x.printStackTrace();
}
}
}

Теперь скомпилируем код:

Javac Server.java Client.java

Откроем два командных окна (DOS). В одном окне введем:

Java Server

а в другом:

Java Client

Обязательно в таком порядке.

Теперь введите строку текста в окне, где запущен клиент, и нажмите кнопку Enter. Наблюдайте за двумя окнами и увидите что случиься. В конце, нажмите Ctrl-C для того чтоб остановить программы.

Объяснение кода работы с сокетами

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

Рассмотрим следующую часть кода сервера:

ServerSocket ss = new ServerSocket(port);
Socket socket = ss.accept();

Класс ServerSocket немного отличается от класса Socket. Класс Socket – это и есть сокет. Главное отличие ServerSocket заключается в том, что он умеет заставлять программу ждать подключений от клиентов. Когда вы его создаете, нужно указывать порт, с которым он будет работать, и вызвать его метод accept(). Этот метод заставляет программу ждать подключений по указанному порту. Исполнение программы зависает в этом месте, пока клиент не подключится. После успешного подключения клиентом, создается нормальный Socket объект, который вы можете использовать для выполнения все существующий операций с сокетом. Заметим также, что этот Socket объект отображает другой конец соединения. Если вы хотите отослать данные клиенту, то вы не можете использовать для этого ваш собственный сокет.

Следующим рассмотрим Socket класс. Вы можете создать Socket объект, указав IP-адрес и порт. Вы можете использовать InetAddress класс для отображения IP-адреса (этот способ более предпочтительный). Для создания InetAddress объекта используйте следующий метод:

InetAddress ipAddress = InetAddress.getByName(address);

Заметим, что в нашей программе мы использовали адрес 127.0.0.1. Это специальный адрес называется адрес замыкания – он просто отображает локальный компьютер. Если вы намерены запустить клиент и сервер на разных компьютерах, тогда нужно использовать соответственный IP-адрес сервера.

После того как мы создали InetAddress, то можно создать Socket:

Socket socket = new Socket(ipAddress, serverPort);

После создания Socket объекта, можно взять входной и выходной потоки сокета. Входной поток позволит вам читать с сокета, а выходной поток дает возможность писать в сокет.

InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();

Следующие строки просто конвертируют потоки в другие типы потоков. После этого нам легче будет работать с String объектами. Этот код ничего не делает с сетью.

DataInputStream in = new DataInputStream(sin);
DataOutputStream out = new DataOutputStream(sout);

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

Последнее обновление: 31.10.2015

В основе межсетевых взаимодействий по протоколам TCP и UDP лежат сокеты. В.NET сокеты представлены классом System.NET.Sockets.Socket , который предоставляет низкоуровневый интерфейс для приема и отправки сообщений по сети.

Рассмотрим основные свойства данного класса:

    AddressFamily: возвращает все адреса, используемые сокетом. Данное свойство представляет одно из значений, определенных в одноименном перечислении AddressFamily . Перечисление содержит 18 различных значений, наиболее используемые:

    • InterNetwork: адрес по протоколу IPv4

      InterNetworkV6: адрес по протоколу IPv6

      Ipx: адрес IPX или SPX

      NetBios: адрес NetBios

    Available: возвращает объем данных, которые доступны для чтения

    Connected: возвращает true, если сокет подключен к удаленному хосту

    LocalEndPoint: возвращает локальную точку, по которой запущен сокет и по которой он принимает данные

    ProtocolType: возвращает одно из значений перечисления ProtocolType , представляющее используемый сокетом протокол. Есть следующие возможные значения:

    • IPSecAuthenticationHeader (Заголовок IPv6 AH)

      IPSecEncapsulatingSecurityPayload (Заголовок IPv6 ESP)

      IPv6DestinationOptions (Заголовок IPv6 Destination Options)

      IPv6FragmentHeader (Заголовок IPv6 Fragment)

      IPv6HopByHopOptions (Заголовок IPv6 Hop by Hop Options)

      IPv6NoNextHeader (Заголовок IPv6 No next)

      IPv6RoutingHeader (Заголовок IPv6 Routing)

      Unknown (неизвестный протокол)

      Unspecified (неуказанный протокол)

    Каждое значение представляет соответствующий протокол, но наиболее используемыми являются Tcp и Udp.

    RemoteEndPoint: возвращает адрес удаленного хоста, к которому подключен сокет

    SocketType: возвращает тип сокета. Представляет одно из значений из перечисления SocketType :

    • Dgram: сокет будет получать и отправлять дейтаграммы по протоколу Udp. Данный тип сокета работает в связке с типом протокола - Udp и значением AddressFamily.InterNetwork

      Raw: сокет имеет доступ к нижележащему протоколу транспортного уровня и может использовать для передачи сообщений такие протоколы, как ICMP и IGMP

      Rdm: сокет может взаимодействовать с удаленными хостами без установки постоянного подключения. В случае, если отправленные сокетом сообщения невозможно доставить, то сокет получит об этом уведомление

      Seqpacket: обеспечивает надежную двустороннюю передачу данных с установкой постоянного подключения

      Stream: обеспечивает надежную двустороннюю передачу данных с установкой постоянного подключения. Для связи используется протокол TCP, поэтому этот тип сокета используется в паре с типом протокола Tcp и значением AddressFamily.InterNetwork

      Unknown: адрес NetBios

Для создания объекта сокета можно использовать один из его конструкторов. Например, сокет, использующий протокол Tcp:

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Или сокет, использующий протокол Udp:

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

Таким образом, при создании сокета мы можем указывать разные комбинации протоколов, типов сокета, значений из перечисления AddressFamily. Однако в то же время не все комбинации являются корректными. Так, для работы через протокол Tcp, нам надо обязательно указать параметры: AddressFamily.InterNetwork, SocketType.Stream и ProtocolType.Tcp. Для Udp набор параметров будет другим: AddressFamily.InterNetwork, SocketType.Dgram и ProtocolType.Udp. Для других протоколов набор значений будет отличаться. Поэтому использование сокетов может потребовать некоторого знания принципов работы отдельных протоколов. Хотя в отношении Tcp и Udp все относительно просто.

Общий принцип работы сокетов

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

    Accept() : создает новый объект Socket для обработки входящего подключения

    Bind() : связывает объект Socket с локальной конечной точкой

    Close() : закрывает сокет

    Connect() : устанавливает соединение с удаленным хостом

    Listen() : начинает прослушивание входящих запросов

    Poll() : определяет состояние сокета

    Receive() : получает данные

    Send() : отправляет данные

    Shutdown() : блокирует на сокете прием и отправку данных

В зависимости от применяемого протокола (TCP, UDP и т.д.) общий принцип работы с сокетами будет немного различаться.

При применении протокола, который требует установление соединения, например, TCP, сервер должен вызвать метод Bind для установки точки для прослушивания входящих подключений и затем запустить прослушивание подключений с помощью метода Listen. Далее с помощью метода Accept можно получить входящие запросы на подключение в виде объекта Socket, который используется для взаимодействия с удаленным узла. У полученного объекта Socket вызываются методы Send и Receive соответственно для отправки и получения данных. Если необходимо подключиться к серверу, то вызывается метод Connect. Для обмена данными с сервером также применяются методы Send или Receive.

Если применяется протокол, для которого не требуется установление соединения, например, UDP, то после вызова метода Bind не надо вызывать метод Listen. И в этом случае для приема данных используется метод ReceiveFrom, а для отправки данных - метод SendTo.

Сокеты

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

Первоначально сокеты были разработаны для UNIX в Калифорнийском университете в Беркли. В UNIX обеспечивающий связь метод ввода-вывода следует алгоритму open/read/write/close. Прежде чем ресурс использовать, его нужно открыть, задав соответствующие разрешения и другие параметры. Как только ресурс открыт, из него можно считывать или в него записывать данные. После использования ресурса пользователь должен вызывать метод Close(), чтобы подать сигнал операционной системе о завершении его работы с этим ресурсом.

Когда в операционную систему UNIX были добавлены средства межпроцессного взаимодействия (Inter-Process Communication, IPC) и сетевого обмена, был заимствован привычный шаблон ввода-вывода. Все ресурсы, открытые для связи, в UNIX и Windows идентифицируются дескрипторами. Эти дескрипторы, или описатели (handles) , могут указывать на файл, память или какой-либо другой канал связи, а фактически указывают на внутреннюю структуру данных, используемую операционной системой. Сокет, будучи таким же ресурсом, тоже представляется дескриптором. Следовательно, для сокетов жизнь дескриптора можно разделить на три фазы: открыть (создать) сокет, получить из сокета или отправить сокету и в конце концов закрыть сокет.

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

Типы сокетов

Существуют два основных типа сокетов - потоковые сокеты и дейтаграммные.

Потоковые сокеты (stream socket)

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

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

Для этого типа сокетов путь формируется до начала передачи сообщений. Тем самым гарантируется, что обе участвующие во взаимодействии стороны принимают и отвечают. Если приложение отправляет получателю два сообщения, то гарантируется, что эти сообщения будут получены в той же последовательности.

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

Потоки базируются на явных соединениях: сокет А запрашивает соединение с сокетом В, а сокет В либо соглашается с запросом на установление соединения, либо отвергает его.

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

Сервер электронной почты представляет пример приложения, которое должно доставлять содержание в правильном порядке, без дублирования и пропусков. Потоковый сокет рассчитывает, что TCP обеспечит доставку сообщений по их назначениям.

Дейтаграммные сокеты (datagram socket)

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

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

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

Кроме двух рассмотренных типов существует также обобщенная форма сокетов, которую называют необрабатываемыми или сырыми.

Сырые сокеты (raw socket)

Главная цель использования сырых сокетов состоит в обходе механизма, с помощью которого компьютер обрабатывает TCP/IP. Это достигается обеспечением специальной реализации стека TCP/IP, замещающей механизм, предоставленный стеком TCP/IP в ядре - пакет непосредственно передается приложению и, следовательно, обрабатывается гораздо эффективнее, чем при проходе через главный стек протоколов клиента.

По определению, сырой сокет - это сокет, который принимает пакеты, обходит уровни TCP и UDP в стеке TCP/IP и отправляет их непосредственно приложению.

При использовании таких сокетов пакет не проходит через фильтр TCP/IP, т.е. никак не обрабатывается, и предстает в своей сырой форме. В таком случае обязанность правильно обработать все данные и выполнить такие действия, как удаление заголовков и разбор полей, ложится на получающее приложение - все равно, что включить в приложение небольшой стек TCP/IP.

Однако нечасто может потребоваться программа, работающая с сырыми сокетами. Если вы не пишете системное программное обеспечение или программу, аналогичную анализатору пакетов, вникать в такие детали не придется. Сырые сокеты главным образом используются при разработке специализированных низкоуровневых протокольных приложений. Например, такие разнообразные утилиты TCP/IP, как trace route, ping или arp, используют сырые сокеты.

Работа с сырыми сокетами требует солидного знания базовых протоколов TCP/UDP/IP.

Порты

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

Сокет состоит из IP-адреса машины и номера порта, используемого приложением TCP. Поскольку IP-адрес уникален в Интернете, а номера портов уникальны на отдельной машине, номера сокетов также уникальны во всем Интернете. Эта характеристика позволяет процессу общаться через сеть с другим процессом исключительно на основании номера сокета.

За определенными службами номера портов зарезервированы - это широко известные номера портов, например порт 21, использующийся в FTP. Ваше приложение может пользоваться любым номером порта, который не был зарезервирован и пока не занят. Агентство Internet Assigned Numbers Authority (IANA) ведет перечень широко известных номеров портов.

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

Например, на стороне клиента, приложение должно знать адрес цели и номер порта. Отправляя запрос на соединение, клиент пытается установить соединение с сервером:

Если события развиваются удачно, при условии что сервер запущен прежде, чем клиент попытался с ним соединиться, сервер соглашается на соединение. Дав согласие, серверное приложение создает новый сокет для взаимодействия именно с установившим соединение клиентом:

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

Работа с сокетами в.NET

Поддержку сокетов в.NET обеспечивают классы в пространстве имен System.Net.Sockets - начнем с их краткого описания.

Классы для работы с сокетами
Класс Описание
MulticastOption Класс MulticastOption устанавливает значение IP-адреса для присоединения к IP-группе или для выхода из нее.
NetworkStream Класс NetworkStream реализует базовый класс потока, из которого данные отправляются и в котором они получаются. Это абстракция высокого уровня, представляющая соединение с каналом связи TCP/IP.
TcpClient Класс TcpClient строится на классе Socket, чтобы обеспечить TCP-обслуживание на более высоком уровне. TcpClient предоставляет несколько методов для отправки и получения данных через сеть.
TcpListener Этот класс также построен на низкоуровневом классе Socket. Его основное назначение - серверные приложения. Он ожидает входящие запросы на соединения от клиентов и уведомляет приложение о любых соединениях.
UdpClient UDP - это протокол, не организующий соединение, следовательно, для реализации UDP-обслуживания в.NET требуется другая функциональность.
SocketException Это исключение порождается, когда в сокете возникает ошибка.
Socket Последний класс в пространстве имен System.Net.Sockets - это сам класс Socket. Он обеспечивает базовую функциональность приложения сокета.

Класс Socket

Класс Socket играет важную роль в сетевом программировании, обеспечивая функционирование как клиента, так и сервера. Главным образом, вызовы методов этого класса выполняют необходимые проверки, связанные с безопасностью, в том числе проверяют разрешения системы безопасности, после чего они переправляются к аналогам этих методов в Windows Sockets API.

Прежде чем обращаться к примеру использования класса Socket, рассмотрим некоторые важные свойства и методы этого класса:

Свойства и методы класса Socket
Свойство или метод Описание
AddressFamily Дает семейство адресов сокета - значение из перечисления Socket.AddressFamily.
Available Возвращает объем доступных для чтения данных.
Blocking Дает или устанавливает значение, показывающее, находится ли сокет в блокирующем режиме.
Connected Возвращает значение, информирующее, соединен ли сокет с удаленным хостом.
LocalEndPoint Дает локальную конечную точку.
ProtocolType Дает тип протокола сокета.
RemoteEndPoint Дает удаленную конечную точку сокета.
SocketType Дает тип сокета.
Accept() Создает новый сокет для обработки входящего запроса на соединение.
Bind() Связывает сокет с локальной конечной точкой для ожидания входящих запросов на соединение.
Close() Заставляет сокет закрыться.
Connect() Устанавливает соединение с удаленным хостом.
GetSocketOption() Возвращает значение SocketOption.
IOControl() Устанавливает для сокета низкоуровневые режимы работы. Этот метод обеспечивает низкоуровневый доступ к лежащему в основе классу Socket.
Listen() Помещает сокет в режим прослушивания (ожидания). Этот метод предназначен только для серверных приложений.
Receive() Получает данные от соединенного сокета.
Poll() Определяет статус сокета.
Select() Проверяет статус одного или нескольких сокетов.
Send() Отправляет данные соединенному сокету.
SetSocketOption() Устанавливает опцию сокета.
Shutdown() Запрещает операции отправки и получения данных на сокете.
интерфейс сетевых системных вызовов ( socket() , bind() , recvfrom() , sendto() и т. д.) в операционной системе UNIX может применяться и для других стеков протоколов (и для протоколов, лежащих ниже транспортного уровня ).

При создании сокета необходимо точно специфицировать его тип. Эта спецификация производится с помощью трех параметров вызова socket() . Первый параметр указывает, к какому семейству протоколов относится создаваемый сокет , а второй и третий параметры определяют конкретный протокол внутри данного семейства.

Второй параметр служит для задания вида интерфейса работы с сокетом – будет это потоковый сокет , сокет для работы с датаграммами или какой-либо иной. Третий параметр указывает протокол для заданного типа интерфейса. В стеке протоколов TCP/IP существует только один протокол для потоковых сокетов – TCP и только один протокол для датаграммных сокетов – UDP , поэтому для транспортных протоколов TCP/IP третий параметр игнорируется.

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

Для транспортных протоколов TCP/IP мы всегда в качестве первого параметра будем указывать предопределенную константу AF_INET (Address family – Internet ) или ее синоним PF_INET ( Protocol family – Internet ).

Второй параметр будет принимать предопределенные значения SOCK_STREAM для потоковых сокетов и SOCK_DGRAM – для датаграммных.

Поскольку третий параметр в нашем случае не учитывается, в него мы будем подставлять значение 0 .

Ссылка на информацию о созданном сокете помещается в таблицу открытых файлов процесса подобно тому, как это делалось для pip ’ов и FIFO (см. семинар 5). Системный вызов возвращает пользователю файловый дескриптор , соответствующий заполненному элементу таблицы, который далее мы будем называть дескриптором сокета . Такой способ хранения информации о сокете позволяет, во-первых, процессам-детям наследовать ее от процессов-родителей, а, во-вторых, использовать для сокетов часть системных вызовов, которые уже знакомы нам по работе с pip ’ами и FIFO : close() , read() , write() .

Системный вызов для создания сокета

Прототип системного вызова

#include #include int socket(int domain, int type, int protocol);

Описание системного вызова

Системный вызов socket служит для создания виртуального коммуникационного узла в операционной системе. Данное описание не является полным описанием системного вызова, а предназначено только для использования в нашем курсе. За полной информацией обращайтесь к UNIX Manual.

Параметр domain определяет семейство протоколов, в рамках которого будет осуществляться передача информации. Мы рассмотрим только два таких семейства из нескольких существующих. Для них имеются предопределенные значения параметра:

  • PF_INET – для семейства протоколов TCP/IP ;
  • PF_UNIX – для семейства внутренних протоколов UNIX, иначе называемого еще UNIX domain.

Параметр type определяет семантику обмена информацией: будет ли осуществляться связь через сообщения (datagrams), с помощью установления виртуального соединения или еще каким-либо способом. Мы будем пользоваться только двумя способами обмена информацией с предопределенными значениями для параметра type :

  • SOCK_STREAM – для связи с помощью установления виртуального соединения ;
  • SOCK_DGRAM – для обмена информацией через сообщения.

Параметр protocol специфицирует конкретный протокол для выбранного семейства протоколов и способа обмена информацией. Он имеет значение только в том случае, когда таких протоколов существует несколько. В нашем случае семейство протоколов и тип обмена информацией определяют протокол однозначно. Поэтому данный параметр мы будем полагать равным 0 .

Возвращаемое значение

В случае успешного завершения системный вызов возвращает файловый дескриптор (значение большее или равное 0 ), который будет использоваться как ссылка на созданный коммуникационный узел при всех дальнейших сетевых вызовах. При возникновении какой-либо ошибки возвращается отрицательное значение.

Адреса сокетов. Настройка адреса сокета. Системный вызов bind()

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

Во втором параметре должен быть указатель на структуру struct sockaddr , содержащую удаленную и локальные части полного адреса.

Указатели типа struct sockaddr * встречаются во многих сетевых системных вызовах; они используются для передачи информации о том, к какому адресу привязан или должен быть привязан сокет . Рассмотрим этот тип данных подробнее. Структура struct sockaddr описана в файле следующим образом:

struct sockaddr { short sa_family; char sa_data; };

Такой состав структуры обусловлен тем, что сетевые системные вызовы могут применяться для различных семейств протоколов, которые по -разному определяют адресные пространства для удаленных и локальных адресов сокета . По сути дела, этот тип данных представляет собой лишь общий шаблон для передачи системным вызовам структур данных, специфических для каждого семейства протоколов. Общим элементом этих структур остается только поле short sa_family (которое в разных структурах, естественно, может иметь разные имена, важно лишь, чтобы все они были одного типа и были первыми элементами своих структур) для описания семейства протоколов. Содержимое этого поля системный вызов анализирует для точного определения состава поступившей информации.

Для работы с семейством протоколов TCP/IP мы будем использовать адрес сокета следующего вида, описанного в файле :

struct sockaddr _in{ short sin_family; /* Избранное семейство протоколов – всегда AF_INET */ unsigned short sin_port; /* 16-битовый номер порта в сетевом порядке байт */ struct in_addr sin_addr; /* Адрес сетевого интерфейса */ char sin_zero; /* Это поле не используется, но должно всегда быть заполнено нулями */ };

Первый элемент структуры – sin_family задает семейство протоколов . В него мы будем заносить уже известную нам предопределенную константу AF_INET (см. предыдущий раздел).

Удаленная часть полного адреса – IP-адрес – содержится в структуре типа struct in_addr , с которой мы встречались в разделе "Функции преобразования IP-адресов inet_ntoa() , inet_aton() " .

Для указания номера порта предназначен элемент структуры sin_port , в котором номер порта должен храниться в сетевом порядке байт . Существует два варианта задания номера порта : фиксированный порт по желанию пользователя и порт , который произвольно назначает операционная система . Первый вариант требует указания в качестве номера порта положительного заранее известного числа и для протокола UDP обычно используется при настройке адресов сокетов и при передаче информации с помощью системного вызова sendto() (см. следующий раздел). Второй вариант требует указания в качестве номера порта значения 0. В этом случае операционная система сама привязывает сокет к свободному номеру порта . Этот способ обычно используется при настройке сокетов программ клиентов, когда заранее точно знать номер порта программисту необязательно.

Какой номер порта может задействовать пользователь при фиксированной настройке? Номера портов с 1 по 1023 могут назначать сокетам только процессы, работающие с привилегиями системного администратора. Как правило, эти номера закреплены за системными сетевыми службами независимо от вида используемой операционной системы, для того чтобы пользовательские клиентские программы могли запрашивать обслуживание всегда по одним и тем же локальным адресам. Существует также ряд широко применяемых сетевых программ, которые запускают процессы с полномочиями обычных пользователей (например, X-Windows). Для таких программ корпорацией Internet по присвоению имен и номеров (

Создание сокета

Системный вызов socket создает сокет и возвращает дескриптор, который может применяться для доступа к сокету:

#include
#include
int socket(int domain, int type, int protocol);

Созданный сокет - это одна конечная точка линии передачи. Параметр domain задает семейство адресов, параметр type определяет тип используемого с этим сокетом обмена данными, a protocol - применяемый протокол.

В табл. 15.1 приведены имена доменов.

Таблица 15.1

К наиболее популярным доменам сокетов относятся AF_UNIX , применяемый для локальных сокетов, реализуемых средствами файловых систем UNIX и Linux, и AF_INET , используемый для сетевых сокетов UNIX. Сокеты домена AF_INET могут применяться программами, взаимодействующими в сетях на базе протоколов TCP/IP, включая Интернет. Интерфейс ОС Windows Winsock также предоставляет доступ к этому домену сокетов.

Параметр сокета type задает характеристики обмена данными, применяемые для нового сокета. Возможными значениями могут быть SOCK_STREAM и SOCK_DGRAM .

SOCK_STREAM - это упорядоченный, надежный, основанный на соединении, двунаправленный поток байтов. В случае домена сокетов AF_INET этот тип обмена данными по умолчанию обеспечивается TCP-соединением, которое устанавливается между двумя конечными точками потоковых сокетов при подключении. Данные могут передаваться в двух направлениях по линии связи сокетов. Протоколы TCP включают в себя средства фрагментации и последующей повторной сборки сообщений больших объемов и повторной передачи любых их частей, которые могли быть потеряны в сети.

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

Протокол, применяемый для обмена данными, обычно определяется типом сокета и доменом. Как правило, выбора нет. Параметр protocol применяется в тех случаях, когда выбор все же предоставляется. Задание 0 позволяет выбрать стандартный протокол, используемый во всех примерах данной главы.

Системный вызов socket возвращает дескриптор, во многом похожий на низкоуровневый файловый дескриптор. Когда сокет подключен к концевой точке другого сокета, для отправки и получения данных с помощью сокетов можно применять системные вызовы read и write с дескриптором сокета. Системный вызов close используется для удаления сокетного соединения.