Z każdym z gniazd związana jest inna funkcja przeznaczona do konsumpcji odebranych danych. Niech zadaniem każdej z tych funkcji będzie przesłanie odebranych danych za pośrednictwem innego kanału szeregowego. Ideę pokazuje rysunek 1.
Dane wysłane z komputera (IP=192.168.0.68) do modułu (IP=192.168.0.55) zostaną retransmitowane poprzez odpowiedni kanał szeregowy mikrokontrolera (w zależności od numeru portu, na który były adresowane dane wysłane z komputera). Z punktu widzenia funkcjonalnego, powstał swoisty koncentrator komunikacyjny, który potrafi powiązać komputer z różnym miejscem docelowym. Uzyskanie takiej funkcjonalności wymaga pewnych zabiegów. W programie muszą zostać utworzone zmienne identyfikujące gniazdka UDP (UDPSocket1…UDPSocket4), jak pokazuje listing 1.
Listing 1.
typedef struct {
EMACInstanceRecT * EMACLayer ;
IPLayerInstanceRecT * IPLayer ;
PtrUDPLayerInstanceRecT UDPLayer ;
PtrUDPSocketDescriptionRecT UDPSocket1 ;
PtrUDPSocketDescriptionRecT UDPSocket2 ;
PtrUDPSocketDescriptionRecT UDPSocket3 ;
PtrUDPSocketDescriptionRecT UDPSocket4 ;
} UDPExampleInstanceRecT ;
typedef UDPExampleInstanceRecT * PtrUDPExampleInstanceRecT ;
static UDPExampleInstanceRecT UDPDemoInstance ;
W funkcji głównej programu, po standardowym zainicjowaniu sieci, należy utworzyć cztery gniazdka, nadać im numery portów i oznaczyć jako gniazdka nasłuchowe. Nic nowego, to już robiliśmy, tylko tym razem wszystko jest wykonane cztery razy. Przy nadawaniu gniazdkom numeru portu trzeba pamiętać, że w każdym przypadku musi być on unikalny – wtedy oprogramowanie bezbłędnie przekieruje dane do miejsca docelowego. Pokazuje to listing 2. Tu warto zauważyć pewien szczegół: gdy wywołuje się funkcję do określenia gniazdka jako nasłuchowego (wywołanie SetUDPListen), w każdym przypadku z gniazdkiem zostaje skojarzona inna funkcja przewidziana do „konsumpcji”.
Listing 2.
int main ( void )
{
USHORT BindStatus ;
/*-------------------------------------------------------------------------*/
SysInit ( ) ;
InitSysTime ( ) ;
HardwareInit ( ) ;
SoftwareInit ( ) ;
UART0HardwInit( FOSC , N81Mode , 9600 ) ;
HelloMessage ( ) ;
StartNet ( & UDPDemoInstance ) ;
UDPDemoInstance . UDPSocket1 = CreateUDPSocket ( UDPDemoInstance . UDPLayer ) ;
if ( ! UDPDemoInstance . UDPSocket1 )
Halt ( Error1 ) ;
UDPDemoInstance . UDPSocket2 = CreateUDPSocket ( UDPDemoInstance . UDPLayer ) ;
if ( ! UDPDemoInstance . UDPSocket2 )
Halt ( Error1 ) ;
UDPDemoInstance . UDPSocket3 = CreateUDPSocket ( UDPDemoInstance . UDPLayer ) ;
if ( ! UDPDemoInstance . UDPSocket3 )
Halt ( Error1 ) ;
UDPDemoInstance . UDPSocket4 = CreateUDPSocket ( UDPDemoInstance . UDPLayer ) ;
if ( ! UDPDemoInstance . UDPSocket4 )
Halt ( Error1 ) ;
BindStatus = BindUDPSocket ( UDPDemoInstance . UDPSocket1 , UDPPortNumber ) ;
if ( BindStatus != UDP_BindPortOK )
{
Halt ( Error2 ) ;
} /* if */ ;
BindStatus = BindUDPSocket ( UDPDemoInstance . UDPSocket2 , UDPPortNumber + 1 ) ;
if ( BindStatus != UDP_BindPortOK )
{
Halt ( Error2 ) ;
} /* if */ ;
BindStatus = BindUDPSocket ( UDPDemoInstance . UDPSocket3 , UDPPortNumber + 2 ) ;
if ( BindStatus != UDP_BindPortOK )
{
Halt ( Error2 ) ;
} /* if */ ;
BindStatus = BindUDPSocket ( UDPDemoInstance . UDPSocket4 , UDPPortNumber + 3 ) ;
if ( BindStatus != UDP_BindPortOK )
{
Halt ( Error2 ) ;
} /* if */ ;
SetUDPListen ( UDPDemoInstance . UDPSocket1 , UDPStream1Service ) ;
SetUDPListen ( UDPDemoInstance . UDPSocket2 , UDPStream2Service ) ;
SetUDPListen ( UDPDemoInstance . UDPSocket3 , UDPStream3Service ) ;
SetUDPListen ( UDPDemoInstance . UDPSocket4 , UDPStream4Service ) ;
for ( ; ; )
{
SysTimerPool ( ) ;
UDPDemoInstance . IPLayer -> IPLayerPoolService ( UDPDemoInstance . IPLayer ) ;
} /* loop */ ;
} /* main */
Rysunek 1 pokazuje, że do kanałów szeregowych przyłączone są komputery. Wyobraźmy sobie, że w rzeczywistości są to jakieś inne urządzenia mające kanał szeregowy. Mogą to być elementy automatyki domowej. Przesłanie czegokolwiek z komputera bezbłędnie trafi do miejsca docelowego (należy jedynie nie pomylić adresu IP i numeru portu, gdyż te dwa elementy precyzyjnie identyfikują odbiorcę). W prezentowanym „ćwiczebnym” programie różne kanały szeregowe są „symulowane”, wykorzystując jeden UART (wszystko zostaje wysłane do UART0). By jednak przekonać się o właściwym działaniu, każde dane są poprzedzone napisem identyfikującym kanał szeregowy. Przykładowe rozwiązanie pokazuje listing 3.
Listing 3.
static void SendDataBuffer ( void * UDPDataBuffer ,
USHORT UDPDataSize )
{
UCHAR * ChPtr ;
USHORT Loop ;
/*-------------------------------------------------------------------------*/
if ( UDPDataSize == 0 )
{
UART0SendString ( NoUDPData ) ;
} /* if ... */
else
{
ChPtr = ( UCHAR * ) UDPDataBuffer ;
for ( Loop = 0 ; Loop < UDPDataSize ; Loop ++ )
{
if ( ( ( * ChPtr ) >= 0x20 ) && ( ( * ChPtr ) < 0x80 ) )
UART0Send ( * ChPtr ) ;
else
UART0Send ( '.' ) ;
ChPtr ++ ;
} /* for */ ;
} /* if ... else */ ;
UART0Send ( '\r' ) ;
UART0Send ( '\n' ) ;
} /* SendDataBuffer */
static void UDPStream1Service ( PtrUDPSocketDescriptionRecT UDPSocket ,
ULONG SenderIPAddress ,
USHORT SenderPortNo ,
ULONG DestIPAddress ,
USHORT DestPortNo ,
void * UDPDataBuffer ,
USHORT UDPDataSize )
{
/*-------------------------------------------------------------------------*/
UART0SendString ( ( UCHAR * ) "UART0: " ) ;
SendDataBuffer ( UDPDataBuffer , UDPDataSize ) ;
} /* UDPStream1Service */
static void UDPStream2Service ( PtrUDPSocketDescriptionRecT UDPSocket ,
ULONG SenderIPAddress ,
USHORT SenderPortNo ,
ULONG DestIPAddress ,
USHORT DestPortNo ,
void * UDPDataBuffer ,
USHORT UDPDataSize )
{
/*-------------------------------------------------------------------------*/
UART0SendString ( ( UCHAR * ) "UART1: " ) ;
SendDataBuffer ( UDPDataBuffer , UDPDataSize ) ;
} /* UDPStream2Service */
static void UDPStream3Service ( PtrUDPSocketDescriptionRecT UDPSocket ,
ULONG SenderIPAddress ,
USHORT SenderPortNo ,
ULONG DestIPAddress ,
USHORT DestPortNo ,
void * UDPDataBuffer ,
USHORT UDPDataSize )
{
/*-------------------------------------------------------------------------*/
UART0SendString ( ( UCHAR * ) "UART2: " ) ;
SendDataBuffer ( UDPDataBuffer , UDPDataSize ) ;
} /* UDPStream3Service */
static void UDPStream4Service ( PtrUDPSocketDescriptionRecT UDPSocket ,
ULONG SenderIPAddress ,
USHORT SenderPortNo ,
ULONG DestIPAddress ,
USHORT DestPortNo ,
void * UDPDataBuffer ,
USHORT UDPDataSize )
{
/*-------------------------------------------------------------------------*/
UART0SendString ( ( UCHAR * ) "UART3: " ) ;
SendDataBuffer ( UDPDataBuffer , UDPDataSize ) ;
} /* UDPStream4Service */
Jak można się przekonać, powstało proste urządzenie komunikacyjne, które umożliwia przesłanie danych do określonego UART jakiegoś podłączonego urządzenia. Jednak to rozwiązanie ma niewielką wadę: otóż wymaga opracowania pewnego programu dla komputera. W przypadku rozwiązania użytego w systemie Infinity taką funkcję pełniła przeglądarka internetowa. Rozumiem, że stworzenie takiego programu może okazać się dla wielu Czytelników barierą nie do pokonania, toteż śpieszę z pomocą. W materiałach dodatkowych w Elportalu dostępny jest źródłowo przykładowy program wraz z komentarzem, który może być bazą do dalszych modyfikacji. Uruchomienie tego programu umożliwia wysłanie komunikatów do czterech urządzeń identyfikowanych przez cztery numery portów. Przykładowe użycie, jak pokazuje rysunek 2, wysyła różne komunikaty do stacji identyfikowanej przez numer IP=192.168.0.55.
Program obsługujący komunikację poprzez kanał transmisji szeregowej pokazuje efekt użycia na rysunku 3. Jest on zgodny z realizacją programu pokazanego na listingu 3.
Podróż danych w jedną stronę (z punktu widzenia LPC2378) nie stanowi problemu, trochę gorzej sprawa się przedstawia z powrotem, czyli z transmisją w drugą stronę. Tu drogi Czytelniku
nie należy popadać w panikę, gdyż problemy są w gruncie rzeczy pozorne (leżą w zakresie zagadnień organizacyjnych). Najprostsza koncepcja sprowadza się do tego, że odebranie znaku z kanału szeregowego generuje pakiet UDP niosący w sobie jeden znak. To swoista rozrzutność, toteż proponuję gromadzenie danych w odpowiednim buforze i wysłanie danych jako pakietu UDP w dwóch sytuacjach: gdy zostanie zapełniony bufor lub upłynie określony czas od odebrania ostatniego znaku. Wyzwalaczem akcji wysłania będzie umowna przerwa w transmisji danych poprzez kanał szeregowy. Oczywiście można zaproponować całą gamę innych rozwiązań, choćby takie, że wyzwalaczem wysłania będzie odebranie znaku Enter (w zbiorze ASCII to znak CR). Tę koncepcję prezentuje kolejny przykład oprogramowania. Wymagane środowisko jest pokazane na rysunku 4.
Program mikrokontrolera realizuje standardową procedurę inicjacji obsługi sieci. W zakresie protokołu UDP otwiera jedno gniazdko do nasłuchu o ustalonym numerze. Te operacje pokazane są na listingu 4.
Listing 4.
int main ( void )
{
USHORT BindStatus ;
/*-------------------------------------------------------------------------*/
SysInit ( ) ;
InitSysTime ( ) ;
HardwareInit ( ) ;
SoftwareInit ( ) ;
UART0HardwInit( FOSC , N81Mode , 9600 ) ;
HelloMessage ( ) ;
StartNet ( & UDPDemoInstance ) ;
UDPDemoInstance . UDPSocket = CreateUDPSocket ( UDPDemoInstance . UDPLayer ) ;
if ( ! UDPDemoInstance . UDPSocket )
Halt ( Error1 ) ;
BindStatus = BindUDPSocket ( UDPDemoInstance . UDPSocket , UDPPortNumber ) ;
if ( BindStatus != UDP_BindPortOK )
{
Halt ( Error2 ) ;
} /* if */ ;
SetUDPListen ( UDPDemoInstance . UDPSocket , UDPStreamService ) ;
for ( ; ; )
{
SysTimerPool ( ) ;
UDPDemoInstance . IPLayer -> IPLayerPoolService ( UDPDemoInstance . IPLayer ) ;
SerialPool ( ) ;
} /* loop */ ;
} /* main */
Tu w pętli głównej dochodzi wywołanie nowej funkcji związanej z obsługą kanału szeregowego (obsługą w sensie odbioru danych). Jej postać pokazuje listing 5, gdzie znajduje się sprawdzenie, czy w kolejkach cyklicznych odbiornika UART0 znajdują się dane (wywołanie funkcji UART0DataPresent) i ewentualne pobranie stamtąd kolejnego znaku (wywołanie funkcji UART0GetData). Wczytany znak zostaje dołączony do bufora sieciowego. Jeżeli bufor został wypełniony znakami do końca, następuje natychmiastowe wysłanie danych jako pakietu UDP (wywołanie funkcji SendBuffer). W przeciwnym wypadku, po wcześniejszej deaktywacji (wywołanie funkcji DetachTimer), zostaje aktywowane wykonanie procedury, która ma się wykonać za określony czas (wywołanie funkcji AttachTimer). Ta funkcja zostanie autonomicznie wywołania po upłynięciu specyfikowanego czasu (chyba, że zostanie deaktywowana).
Listing 5.
static void SerialPool ( void )
{
UCHAR Ch ;
/*---------------------------------------------------------------------*/
if ( UART0DataPresent ( ) )
{
Ch = UART0GetData ( ) ;
UDPDemoInstance . SerialBuffer [ UDPDemoInstance . SerialBufferIndex ] = Ch ;
UDPDemoInstance . SerialBufferIndex ++ ;
DetachTimer ( UDPDemoInstance . TimerAction ) ;
if ( UDPDemoInstance . SerialBufferIndex >= SerialBufferSize )
{
SendBuffer ( NIL ) ;
} /* if ... */
else
{
UDPDemoInstance . TimerAction = AttachTimer ( 100 , SendBuffer , NIL ) ;
} /* if ... else */ ;
} /* if */ ;
} /* SerialPool */
Wywołanie odłożonej w czasie operacji pochodzi z realizacji czynności przewidzianych w funkcji SysTimerPool. Jest to ten sam mechanizm, jaki wykorzystuje wewnętrznie cała obsługa sieci, gdyż tam również znajdują się czynności, które muszą zostać wykonane po określonym czasie.
Realizacja operacji związanych z wysłaniem pakietu UDP właściwie nie jest problematyczna, jest tu tylko jeden problem do rozwiązania. Jeżeli zostanie skompletowany bufor danych do wysłania jako pakiet UDP, to powstaje jedno pytanie: dokąd dane należy wysłać. Można postąpić w ten sposób, że dane są wysłane a priori do ustalonego miejsca (adres IP=192.168.0.68, port=1000). Innym rozwiązaniem jest wariant zaproponowany w przykładowym programie. Są tam dwie zmienne (LastClientIPAddress i LastClientPortNo) do przechowywania adresu IP i numeru portu ostatnio odebranych danych UDP (wstępnie są one zainicjowane na zero). Jeżeli w chwili wysłania danych te zmienne zawierają jakiekolwiek dane o charakterze adresowym (jako wartości niezerowe), to pakiet UDP zostanie wysłany do tak określonego miejsca (rozwiązanie pokazuje listing 6).
Listing 6.
static void SendBuffer ( void * CallParam )
{
USHORT SendReply ;
/*--------------------------------------------------------------------*/
if ( ! UDPDemoInstance . LastClientIPAddress )
{
UART0SendString ( NoDestMsg ) ;
} /* if ... */
else
{
SendReply = UDPSendData ( UDPDemoInstance . UDPSocket ,
UDPDemoInstance . LastClientIPAddress ,
UDPDemoInstance . LastClientPortNo ,
( void * ) UDPDemoInstance . SerialBuffer ,
UDPDemoInstance . SerialBufferIndex ) ;
if ( SendReply != UDP_SendOK )
UART0SendString ( SendError ) ;
} /* if ... else */ ;
UDPDemoInstance . SerialBufferIndex = 0 ;
} /* SendBuffer */
W każdym wywołaniu funkcji dotyczącej przetwarzania danych odebranych z sieci (funkcja zgłoszona przez SetUDPListen, pokazanej na listingu 7), w parametrach wywołania wniesione są dane adresowe (między innymi skąd, jako adres IP i numer portu, pochodzi pakiet UDP). Wystarczy te dane zachować w zmiennych globalnych. W ten sposób powstaje urządzenie o charakterze komunikacyjnym, które dane odebrane z sieci retransmituje w kanał szeregowy oraz dane odebrane z kanału szeregowego retransmituje do sieci, taki pośrednik pomiędzy dwoma stacjami.
Listing 7.
static void UDPStreamService ( PtrUDPSocketDescriptionRecT UDPSocket ,
ULONG SenderIPAddress ,
USHORT SenderPortNo ,
ULONG DestIPAddress ,
USHORT DestPortNo ,
void * UDPDataBuffer ,
USHORT UDPDataSize )
{
UCHAR * ChPtr ;
USHORT Loop ;
/*----------------------------------------------------------------------*/
UDPDemoInstance . LastClientIPAddress = SenderIPAddress ;
UDPDemoInstance . LastClientPortNo = SenderPortNo ;
if ( UDPDataSize == 0 )
{
UART0SendString ( NoUDPData ) ;
} /* if ... */
else
{
ChPtr = ( UCHAR * ) UDPDataBuffer ;
for ( Loop = 0 ; Loop < UDPDataSize ; Loop ++ )
{
if ( ( ( * ChPtr ) >= 0x20 ) && ( ( * ChPtr ) < 0x80 ) )
UART0Send ( * ChPtr ) ;
else
UART0Send ( '.' ) ;
ChPtr ++ ;
} /* for */ ;
} /* if ... else */ ;
UART0Send ( '\r' ) ;
UART0Send ( '\n' ) ;
} /* UDPStreamService */
Ilustracją tych rozważań jest uruchomienie programu, jak pokazuje rysunek 5. Po jego uruchomieniu należy wpisać w odpowiednie pola docelowy adres IP jako 192.168.0.55 i numer portu 20001. Te informacje pozwolą na wskazanie miejsca docelowego wysyłanych danych. Po wpisaniu w polu Dane jakiegokolwiek tekstu należy kliknąć na przycisk Wyślij. To spowoduje przesłanie wpisanych danych do systemu z mikrokontrolerem LPC2378, w którym program dokona retransmisji odebranych danych do kanału szeregowego. Odebrane dane są wyświetlone w okienku PC-towego programu obsługującego kanał szeregowy. Jednocześnie odebranie czegokolwiek z sieci przez program mikrokontrolera spowoduje zachowanie danych adresowych nadawcy. Teraz wysłanie czegokolwiek w kanał szeregowy uformuje pakiet UDP, który zostaje wysłany do zapamiętanego nadawcy.
Przykładowe użycie pokazuje rysunek 6, co można skorelować z rysunkiem 5.
Znacząco prostsza jest realizacja funkcjonalności, gdzie element sieciowy, jak choćby mikrokontroler LPC2378, stanowi końcowy element w torze komunikacyjnym. Rozpatrzmy hipotetyczny układ mierzący przykładowo temperaturę, który co jakiś czas jest odpytywany o aktualne wartości pomiarowe. Z punktu widzenia realizacji jest to serwer, czyli ta strona w komunikacji, która tworzy gniazdko do nasłuchu (i oczywiście realizuje pomiary, których wartości przechowuje sobie w zmiennych w pamięci). Odebranie pakietu UDP (może on zawierać jakieś polecenie, parametry lub inne ważne dane, które w obecnych rozważaniach związanych z komunikacją sieciową są nieistotne) w funkcji „konsumpcji” odebranych danych może wygenerować natychmiast odpowiedź. W zgłoszonej (w UDPListen) funkcji do „konsumpcji” odebranych danych zawarta jest jednocześnie odpowiedź. Realizację algorytmu można żartobliwie określić „dziś pytanie, dziś odpowiedź”. Implementację omawianej funkcji pokazuje listing 8.
Listing 8.
static void UDPStreamService ( PtrUDPSocketDescriptionRecT UDPSocket ,
ULONG SenderIPAddress ,
USHORT SenderPortNo ,
ULONG DestIPAddress ,
USHORT DestPortNo ,
void * UDPDataBuffer ,
USHORT UDPDataSize )
{
UCHAR Buffer [ 16 ] ;
/*---------------------------------------------------------------------*/
Buffer [ 0 ] = 'T' ;
Buffer [ 1 ] = 'E' ;
Buffer [ 2 ] = 'M' ;
Buffer [ 3 ] = 'P' ;
Buffer [ 4 ] = '=' ;
Buffer [ 5 ] = '1' ;
Buffer [ 6 ] = '2' ;
Buffer [ 7 ] = '3' ;
Buffer [ 8 ] = '.' ;
Buffer [ 9 ] = '4' ;
UDPSendData ( UDPSocket , SenderIPAddress , SenderPortNo ,
( void * ) Buffer , 10 ) ;
} /* UDPStreamService */
Tu warto zwrócić uwagę na kilka szczegółów, w parametrach wywołania funkcji umieszczone jest wskazanie na gniazdko, poprzez które zostały odebrane dane i to samo gniazdko zostaje użyte do wysłania odpowiedzi. Dodatkowo w parametrach zawarte są szczegółowe dane adresowe nadawcy (parametr SenderIPAdres jako adres IP i SenderPortNo jako numer portu) i te dane są użyte w funkcji wysłania odpowiedzi. Umożliwia to odesłanie danych dokładnie do nadawcy zapytania.
Do zapytania można użyć dosyć uniwersalnego, specjalnie przygotowanego do tematyki programu (UDPTester), jak pokazuje rysunek 7. W okienku tego programu wyświetlona jest zawartość wychodzącego pakietu UDP oraz zawartość odebranego pakietu UDP. Korzystając z tego serwera danych pomiarowych, można tu opisać jeszcze jedną dosyć ciekawą i istotną cechę protokołu UDP → wysyłanie danych w trybie „do wszystkich”. Oczywiście to nie dotyczy wszystkich w skali całego świata, a jedynie „wszystkich” w obrębie lokalnej sieci domowej.
Jeżeli w programie UDPTester zostaną wpisane dane miejsca docelowego jako IP=192.168.0.255, jak na rysunku 8, to taki zapis oznacza „do wszystkich w sieci lokalnej” na port UDP o określonym numerze (w tym wypadku jest to 20001). W każdej sieci lokalnej jest przeznaczony jeden adres o znaczeniu „do wszystkich”. Nie jest to stały adres (jak przykładowo IP=127.0.0.1 oznaczający, że odbiorcą jestem sam), ale jest prosta formuła pozwalająca ustalić taki adres.
Tu należy przypomnieć sobie znaczenie maski podsieci. Dzieli ona adres IP na dwie części: tam, gdzie w masce podsieci są bity jedynek, ta część określa numer sieci oraz tam, gdzie w masce są zera, ta część określa numer stacji w określonej sieci. Tu na chwilę należy zapomnieć o powszechnej notacji zapisu adresu IP jako czterech liczb rozdzielonych kropkami i należy spojrzeć na powstałe dane z innej perspektywy, w sensie liczb 32-bitowych. Podział adresu na dwie części pokazuje rysunek 9.
Jeżeli w adresie IP jako numer stacji zostanie wpisana wartość zawierająca same jedynki (binarnie), to tak utworzony adres jest adresem oznaczającym: do wszystkich. Jeżeli maska podsieci ma wartość 255.255.255.0, to trzy pierwsze liczby z adresu wydzielają adres sieci (i ten musi być zgodny z numerem sieci, czyli być identyczny jak w każdym adresie IP każdej stacji przyłączonej do sieci lokalnej), ostatnia liczba ma zawierać wszystkie bity ustawione, co daje wartość 255. Reasumując, adres 192.168.0.255 jest takim adresem w naszej sieci lokalnej. Jeżeli drogi Czytelniku pomyślałeś, że jakikolwiek wysłany pakiet został by odebrany przez wszystkich, to musi on zostać zaadresowany w sensie adresu MAC na FF-FF-FF-FF-FF-FF. Jeżeli taka koncepcja przyszła Ci do głowy, to muszę powiedzieć, że jesteś na dobrej drodze do zrozumienia tej filozofii, gdyż dokładnie tak to się odbywa. Jednak w nagłówkach IP są wpisane „prawdziwe” adresy, toteż stacja odbiorcza jest w stanie wygenerować właściwą odpowiedź. To tak, jakby ten adres nie musiał przechodzić fazy związanej z protokołem ARP, a jego adres MAC jest z góry znany (zawierający same jedynki).
Podanie docelowego adresu IP=192.168.0.255 (jak na rysunku 8) spowodowało wysłanie pakietu (informacja podkreślona na czerwono), na który przyszła odpowiedź (podkreślone na zielono). To bardzo interesująca cecha, która pozwala na realizację poszukiwania w sieci lokalnej innych stacji, co do których nie jest znana adresacja IP. Ta własność stała się podstawą do realizacji protokołu DHCP, o którym będzie w kolejnej części.