В основе межсетевых взаимодействий по сетевых протоколам TCP и UDP лежат сокеты. Сокет предоставляет интерфейс доступа к определенному порту определенного хоста. То есть через сокет один хост может обращаться
к приложению на другом хосте. В .NET сокеты представлены классом Socket из пространства имен System.NET.Sockets
, который предоставляет низкоуровневый интерфейс для приема и отправки сообщений по сети.
Для создания объекта сокета можно использовать один из его конструкторов.
Socket(AddressFamily, SocketType, ProtocolType)
: создает сокет, используя указанные семейство адресов, тип сокета и протокол.
Socket(SafeSocketHandle)
: создает сокет с помощью дескриптора сокета - объекта SafeSocketHandle.
Socket(SocketInformation)
: создает сокет, используя структуру SocketInformation.
Socket(SocketType, ProtocolType)
: создает сокет, используя указанные тип сокета и протокол.
Возьмем первый вариант конструктора с тремя параметра:
Первый параметр конструктора представляет перечисление AddressFamily и задает схему адресации, которую может использовать сокет. Данное перечисление содержит 33 константы. Наиболее используемые:
InterNetwork
: адрес по протоколу IPv4
InterNetworkV6
: адрес по протоколу IPv6
Ipx
: адрес IPX или SPX
NetBios
: адрес NetBios
Второй параметр представляет перечисление SocketType, которое устанавливает тип сокета. Может принимать следующие значения:
Dgram
: сокет будет получать и отправлять дейтаграммы по протоколу Udp. Данный тип сокета работает в связке с типом протокола - Udp и значением
AddressFamily.InterNetwork
Raw
: сокет имеет доступ к нижележащему протоколу транспортного уровня и может использовать для передачи сообщений такие протоколы,
как ICMP и IGMP
Rdm
: сокет может взаимодействовать с удаленными хостами без установки постоянного подключения. В случае, если отправленные сокетом
сообщения невозможно доставить, то сокет получит об этом уведомление
Seqpacket
: обеспечивает надежную двустороннюю передачу данных с установкой постоянного подключения
Stream
: обеспечивает надежную двустороннюю передачу данных с установкой постоянного подключения. Для связи используется протокол TCP,
поэтому этот тип сокета используется в паре с типом протокола Tcp и значением AddressFamily.InterNetwork
Unknown
: адрес NetBios
Третий параметр представляет перечисление ProtocolType, которое устанавливает тип используемого протокола. Может принимать следующие значения:
Ggp
Icmp
IcmpV6
Idp
Igmp
IP
IPSecAuthenticationHeader
(Заголовок IPv6 AH)
IPSecEncapsulatingSecurityPayload
(Заголовок IPv6 ESP)
IPv4
IPv6
IPv6DestinationOptions
(Заголовок IPv6 Destination Options)
IPv6FragmentHeader
(Заголовок IPv6 Fragment)
IPv6HopByHopOptions
(Заголовок IPv6 Hop by Hop Options)
IPv6NoNextHeader
(Заголовок IPv6 No next)
IPv6RoutingHeader
(Заголовок IPv6 Routing)
Ipx
ND
Pup
Raw
Spx
SpxII
Tcp
Udp
Unknown
(неизвестный протокол)
Unspecified
(неуказанный протокол)
Каждое значение представляет соответствующий протокол, но наиболее используемыми являются Tcp и Udp.
Например, определим сокет, который использует протокол Tcp:
using System.Net.Sockets; Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Или сокет, использующий протокол Udp:
using System.Net.Sockets; 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 все относительно просто.
свойства класса позволяют получить информацию о сокете. Основные свойства:
AddressFamily
: представляет схему адресации, используемую сокетом, в виде перечисления AddressFamily
Available
: возвращает объем данных, полученных от подключенного хоста и доступных для чтения
Connected
: возвращает true, если сокет подключен к удаленному хосту
LocalEndPoint
: возвращает локальную точку (объект типа EndPoint), по которой запущен сокет и по которой он принимает данные
ProtocolType
: возвращает тип протокола в виде значения перечисления ProtocolType
RemoteEndPoint
: возвращает адрес удаленного хоста, к которому подключен сокет (объект типа EndPoint)
SocketType
: возвращает тип сокета в виде значения перечисления SocketType
При работе с сокетами вне зависимости от выбранных протоколов мы будем опираться на методы класса Socket:
Accept() / AcceptAsync()
: создает новый объект Socket для обработки входящего подключения
Bind()
: связывает объект Socket с локальной конечной точкой
Close()
: закрывает сокет
Connect() / ConnectAsync
: устанавливает соединение с удаленным хостом
Listen()
: начинает прослушивание входящих запросов
Poll()
: определяет состояние сокета
Receive() / ReceiveAsync
: получает данные
ReceiveFrom() / ReceiveFromAsync()
: получает данные и сохраняет конечную точку, от которой получены данные
Send() / SendAsync()
: отправляет данные
SendTo() / SendToAsync()
: отправляет данные на определенную конечную точку
Shutdown()
: блокирует на сокете прием и/или отправку данных
В зависимости от применяемого протокола (TCP, UDP и т.д.) общий принцип работы с сокетами будет немного различаться.
При применении протокола, который требует установление соединения, например, TCP, сервер должен вызвать метод Bind для установки точки для прослушивания входящих подключений и затем запустить прослушивание подключений с помощью метода Listen. Далее с помощью метода Accept можно получить входящие запросы на подключение в виде объекта Socket, который используется для взаимодействия с удаленным узла. У полученного объекта Socket вызываются методы Send и Receive соответственно для отправки и получения данных. Если необходимо подключиться к серверу, то вызывается метод Connect. Для обмена данными с сервером также применяются методы Send или Receive.
Если применяется протокол, для которого не требуется установление соединения, например, UDP, то после вызова метода Bind не надо вызывать метод Listen.
И в этом случае для приема данных используется метод ReceiveFrom() / ReceiveFromAsync()
, а для отправки данных - метод SendTo() / SendToAsync()
.
После завершения работы с сокетом рекомендуется его закрыть. Для этого можно использовать метод Close(). Он закрывает подключение с удаленным хостом и освобождает
все управляемые и неуправляемые ресурсы, связанные с сокетом. После этого свойство Connected
будет равно false
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // работа с сокетом socket // ............... socket.Close();
Также можно применять конструкцию using:
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { // работа с сокетом socket // ............... }
При работе с протоколами, ориентированными на установку соединения, например, TCP, Microsoft рекомендует перед методом Close вызывать метод Shutdown
, который блокирует примем и отправку данных для сокета и гарантирует, что
все данные будут получены и отправлены перед закрытием сокета.
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { socket.Shutdown(SocketShutdown.Both); } catch(Exception ex) { Console.WriteLine(ex.Message); } finally { socket.Close(); }
В качестве параметра в метод Shutdown передается значение из перечисления SocketShutdown:
Send
: блокируется отправка данных
Receive
: блокируется получение данных
Both
: блокируются отправка и получение данных