Мы наконец-то добрались до изучения того, ради чего сокеты и создавались: как передавать и получать с их помощью данные. По традиции начнём рассмотрение с более простого протокола UDP. Функции, которые рассматриваются в этом разделе, могут быть использованы и с другими протоколами, и от этого их поведение может меняться. Мы здесь описываем только их поведение при использовании UDP.
Многочисленные вопросы на Круглом столе, посвящённые передаче данных по сети с помощью сокетов, показывают, что эта тема достаточно актуальна, но вызывает некоторые сложности у начинающих программистов. Данная статья является первой в цикле из трёх статей, призванных дать ответы на подобные вопросы. Она посвящена стандартным сокетам. Вторая статья будет посвящена сокетам Windows, а третья - внутреннему устройству классов VCL, предназначенных для передачи данных с помощью сокетов. Статьи не претендуют на исчерпывающее освещение проблемы (в частности, будут обсуждаться только протоколы TCP и UDP), однако они должны дать сведения, достаточные для понимания основных механизмов работы сокетов и дальнейшего самостоятельного их изучения. В статьях много дополнительной информации, которая не является необходимой непосредственно для написания программы, но расширяет кругозор в данной области знаний. Строго говоря, если писать только то, что необходимо для простейшей организации связи, каждая из трёх статей цикла уместилась бы на странице. Но в таком виде это было бы полезно только ламерам, которым лишь бы содрать откуда-нибудь готовое решение и абы как вставить его в свою "программу". А моя цель - написать что-то полезное для тех, кто пока ещё не знаком близко с сокетами, но хочет в первую очередь понять, как они устроены, а не получить что-то готовенькое. Таким людям, на мой взгляд, будет полезно знать то, что находится вокруг, потому что это знание помогает искать решения в нестандартных ситуациях.
В языке Delphi процедуры и функции рассматриваются не только как части програм- мы, которые можно выполнять с помощью вызовов, а трактуются гораздо шире: здесь до- пускается интерпретация процедур и функций как объектов, которые можно присваивать переменным и передавать в качестве параметров. Такие действия можно выполнять с помощью процедурных типов. В описании процедурного типа задаются парамет- ры, а для функции еще и возвращаемое значение. По существу синтаксис записи процедурного типа в точности совпадает с записью заголовка процедуры или функции, только опускается идентификатор после ключе- вого слова procedure или function. Приведем несколько примеров объявлений процедурного типа.
При описании переменной следует указать ее тип. Знание типа переменной необ- ходимо для определения набора значений, которые она может принимать, и действий, которые над ней можно выполнять. Для обозначения типа используют идентификато- ры. Ранее мы уже познакомились с такими типами, как натуральные и целые числа, обозначаемые идентификаторами Cardinal и Integer. Все типы можно разделить на шесть основных подразделов: • простой тип; • строковый тип; • структурный (структурированный) тип; • указательный тип; • процедурный тип; • вариантный тип. Простые типы Простые типы определяют упорядоченные множества значений. Это может быть или множество натуральных чисел (тип Cardinal), или множество целых чисел (тип integer), оптимизированных для работы с 32-разрядными приложениями, а также базовые целочисленные типы, перечисленные в табл. 3.2. Таблица 3.2. Целочисленные типы Тип Диапазон Формат Integer от -2147483648 до 2147483647 Знаковый 32 бита Cardinal от 0 до 4294967295 Беззнаковый 32 бита s h o r t i n t от-128 до 127 Знаковый 8 бит Smallint от-32768 до 32767 Знаковый 16 бит Longint от -2147483648 до 2147483647 Знаковый 32 бита Int64 от-26 3 до 263-1 Знаковый 64 бита Byte от 0 до 255 Беззнаковый 8 бит word от 0 до 65535 Беззнаковый 16 бит Longword от 0 до 4294967295 Беззнаковый 32 бита Целочисленные типы можно отнести и к порядковым типам. Порядковые типы представляют собой подмножество простых типов. Все простые типы, отличные от ве- щественных типов, являются порядковыми и имеют следующие четыре характеристики. 1. Все возможные значения данного порядкового типа представляют собой упоря- доченное множество, и каждое возможное значение связано с порядковым но- мером, который представляет собой целочисленное значение. За исключением значений целочисленного типа, первое значение любого порядкового типа име- ет порядковый номер 0, следующее значение имеет порядковый номер 1 и т.д. Порядковым номером значения целочисленного типа является само это значе- ние. В любом порядковом типе каждому значению, кроме первого, предшеству- ет другое значение, и после каждого значения, кроме последнего, следует дру- гое значение в соответствии с упорядоченностью типа.
Прежде чем перейдем к дальнейшему описанию языка Delphi, формально опреде- лим несколько терминов. Во-первых, это слово "идентификатор". Идентификатор — это строка символов, используемая для именования некоторого элемента программы. Это может быть переменная, запись, функция, процедура или конструкция более вы- сокого уровня, например сама программа. Идентификатор может иметь любую длину, однако в языке Delphi только первые его 255 символов являются значимыми (что более чем достаточно!). Идентификатор должен начинаться с буквы или символа подчеркивания (_) и не может содержать пробелов. После первого символа идентификатора можно использовать буквы, цифры и символы подчеркивания. Как и в зарезервированных словах, в идентификаторах можно использовать как строчные, так и прописные буквы (компилятор их не разли- чает). Приведенные ниже идентификаторы означают одно и то же.
В первой главе, при ознакомлении с графическим интерфейсом пользователя, для демонстрации примеров использовались приложения типа VCL Form Application. Одна- ко для изучения языка Delphi значительно удобнее использовать приложения типа Console Application (консольное приложение). Здесь не надо думать о графическом ин- терфейсе, а ввод и вывод на экран информации происходит значительно проще. По- этому не надо отвлекаться на посторонние вещи, думайте только об особенностях языка. К графическому интерфейсу и приложениям типа VCL Form Application мы обя- зательно вернемся в следующих главах, так как они используются в подавляющем большинстве программ, написанных на Delphi.
Ключевые, или зарезервированные слова — это как команды для компилятора, на основе которых он готовится к выполнению определенных действий. Например, клю- чевые слова unit (модуль) или programm (программа), расположенные в начале каж- дого модуля, предупреждают компилятор о начале программы. За словом unit или program обязательно должно располагаться имя модуля. Действие ключевого слова распространяется до тех пор, пока не встретится другое ключевое слово. Рассмотрим ключевые слова для консольного приложения, создаваемого по умолчанию при выбо- ре проекта типа Console Application, с которым мы будем работать в этой главе. program Projectl; {$APPTYPE CONSOLE} uses SysUtils; begin { TODO -oUser -cConsole Main: Insert code here } end. Здесь четыре ключевых слова выделены жирным шрифтом. Такое выделение клю- чевых слов обязательно в редакторе кодов Delphi, хотя для других редакторов это не требуется, и если вы будете писать программу в другом редакторе, то такого выделе- ния, вероятнее всего, не будет.
В основе языка Delphi лежит язык программирования Pascal, который показа.! себя как наиболее универсальный и легко изучаемый язык. При этом его удоб- но использовать для создания сложных приложений, включая работу с Internet, база- ми данных, приложения масштаба предприятия. За время своего развития язык Pascal постоянно совершенствовался, и на его основе создан Object Pascal — это наиболее радикальное и удачное расширение возможностей языка. Начиная с версии Delphi 7 язык Object Pascal называется язык Delphi. В этой главе познакомимся с основами языка программирования Delphi, его алфавитом и синтаксисом.
В модуле могут объявляться (подключаться) другие модули, от которых этот модуль зависит. Как и для платформы Win32, компилятор Delphi 8 для .NET должен иденти- фицировать эти модули. В случае с явным объявлением имен путь всегда известен, но если это групповой модуль, то компилятор должен определить область поиска подклю- ченных модулей. Рассмотрим следующий пример модуля с явным объявлением про- странства имен и подключенными модулями (для чего используется директива uses). unit MyCompany.Programs.Units.MyUnitl; uses MyCompany.Libs.Unit2, Unit3, Unit4; Здесь модуль MyUnitl является членом пространства имен MyCompany. Programs .Units. Модуль MyUnitl зависит от трех других модулей: модуля Unit2 в пространстве имен MyCompany.Libs и двух групповых модулей Unit3 и Unit4.
В Delphi 8 файлы проекта (программы, библиотеки или пакеты) неявно входят в про- странство имен, называемое пространством имен проекта по умолчанию. Модуль может быть или членом пространства имен по умолчанию, или для него можно явно объявить пространство имен. Во втором случае пространство имен объявляется в заголовке модуля. uni t MyCompany.MyWidgets.MyUni t; Обратите внимание, что отдельные пространства имен разделяются точками. Но в данном случае точки не являются разделителями между именами, а являют- ся частью имени модуля. Имя исходного файла для данного модуля должно быть MyCompany .MyWidgets .MyUnit .pas, а имя выходного файла, созданного компилято- ром, будет MyCompany.MyWidgets.MyUnit.dcuil. Точки в данном случае показыва- ют вложенность пространств имен, а модуль MyUnit является членом пространства имен MyWidgets, которое само включено в пространство имен MyCompany. По умолчанию всегда создаются пространства имен для всех модулей проекта. Рас- смотрим следующие объявления.