Gniazdo Unix - Szybki przewodnik
Gniazda umożliwiają komunikację między dwoma różnymi procesami na tej samej lub różnych maszynach. Mówiąc dokładniej, jest to sposób na rozmowę z innymi komputerami przy użyciu standardowych deskryptorów plików Uniksa. W systemie Unix każda akcja we / wy jest wykonywana poprzez zapisanie lub odczytanie deskryptora pliku. Deskryptor pliku to po prostu liczba całkowita powiązana z otwartym plikiem i może to być połączenie sieciowe, plik tekstowy, terminal lub coś innego.
Dla programisty gniazdo wygląda i zachowuje się podobnie jak deskryptor pliku niskiego poziomu. Dzieje się tak, ponieważ polecenia takie jak read () i write () działają z gniazdami w taki sam sposób, jak z plikami i potokami.
Gniazda zostały po raz pierwszy wprowadzone w 2.1BSD, a następnie poprawione do ich obecnej postaci w 4.2BSD. Funkcja gniazd jest teraz dostępna w większości aktualnych wersji systemu UNIX.
Gdzie jest używane gniazdo?
Gniazdo Unix jest używane w strukturze aplikacji klient-serwer. Serwer to proces, który wykonuje pewne funkcje na żądanie klienta. Większość protokołów na poziomie aplikacji, takich jak FTP, SMTP i POP3, wykorzystuje gniazda do ustanawiania połączenia między klientem a serwerem, a następnie do wymiany danych.
Rodzaje gniazd
Użytkownicy mają do dyspozycji cztery rodzaje gniazd. Pierwsze dwa są najczęściej używane, a ostatnie dwa rzadko.
Zakłada się, że procesy komunikują się tylko między gniazdami tego samego typu, ale nie ma ograniczeń uniemożliwiających komunikację między gniazdami różnych typów.
Stream Sockets- Gwarantowana dostawa w środowisku sieciowym. Jeśli wyślesz przez gniazdo strumieniowe trzy elementy „A, B, C”, dotrą one w tej samej kolejności - „A, B, C”. Te gniazda używają protokołu TCP (Transmission Control Protocol) do transmisji danych. Jeśli doręczenie nie jest możliwe, nadawca otrzymuje informację o błędzie. Rekordy danych nie mają żadnych granic.
Datagram Sockets- Dostawa w środowisku sieciowym nie jest gwarantowana. Są bezpołączeniowe, ponieważ nie musisz mieć otwartego połączenia, jak w przypadku Stream Sockets - tworzysz pakiet z informacjami o miejscu docelowym i wysyłasz go. Używają UDP (User Datagram Protocol).
Raw Sockets- Zapewniają one użytkownikom dostęp do podstawowych protokołów komunikacyjnych, które obsługują abstrakcje gniazd. Gniazda te są zwykle zorientowane na datagram, chociaż ich dokładna charakterystyka zależy od interfejsu zapewnianego przez protokół. Surowe gniazda nie są przeznaczone dla zwykłego użytkownika; zostały one przeznaczone głównie dla osób zainteresowanych opracowaniem nowych protokołów komunikacyjnych lub uzyskaniem dostępu do niektórych z bardziej tajemniczych funkcji istniejącego protokołu.
Sequenced Packet Sockets- Są podobne do gniazda strumieniowego, z wyjątkiem tego, że granice rekordów są zachowywane. Ten interfejs jest dostarczany tylko jako część abstrakcji gniazd Network Systems (NS) i jest bardzo ważny w większości poważnych aplikacji NS. Gniazda pakietów sekwencyjnych pozwalają użytkownikowi manipulować nagłówkami Sequence Packet Protocol (SPP) lub Internet Datagram Protocol (IDP) w pakiecie lub grupie pakietów, albo przez napisanie nagłówka prototypu wraz z danymi, które mają być wysłane, lub przez określenie domyślnego nagłówka, który ma być używany ze wszystkimi wychodzącymi danymi i umożliwia użytkownikowi odbieranie nagłówków w przychodzących pakietach.
Co jest następne?
Następnych kilka rozdziałów ma na celu wzmocnienie twoich podstaw i przygotowanie podstaw przed pisaniem programów serwera i klienta przy użyciu gniazda . Jeśli chcesz bezpośrednio przejść, aby zobaczyć, jak napisać program klienta i serwera, możesz to zrobić, ale nie jest to zalecane. Zdecydowanie zaleca się, aby przejść krok po kroku i ukończyć te kilka początkowych rozdziałów, aby stworzyć bazę, zanim przejdziesz do programowania.
Zanim przejdziemy do faktycznych rzeczy, omówmy trochę o adresach sieciowych - adresie IP.
Adres hosta IP lub częściej po prostu adres IP służy do identyfikacji hostów podłączonych do Internetu. IP oznacza protokół internetowy i odnosi się do warstwy internetowej ogólnej architektury sieciowej Internetu.
Adres IP to 32-bitowa liczba interpretowana jako cztery 8-bitowe liczby lub oktety. Każdy adres IP jednoznacznie identyfikuje uczestniczącą sieć użytkownika, hosta w sieci i klasę sieci użytkownika.
Adres IP jest zwykle zapisywany w notacji dziesiętnej z kropkami w postaci N1.N2.N3.N4, gdzie każdy Ni jest liczbą dziesiętną z przedziału od 0 do 255 (szesnastkowo od 00 do FF).
Klasy adresowe
Adresami IP zarządza i tworzy Internet Assigned Numbers Authority (IANA). Istnieje pięć różnych klas adresów. Możesz określić, do której klasy należy adres IP, sprawdzając pierwsze cztery bity adresu IP.
Class A adresy zaczynają się od 0xxxlub 1 to 126 dziesiętny.
Class B adresy zaczynają się od 10xxlub 128 to 191 dziesiętny.
Class C adresy zaczynają się od 110xlub 192 to 223 dziesiętny.
Class D adresy zaczynają się od 1110lub 224 to 239 dziesiętny.
Class E adresy zaczynają się od 1111lub 240 to 254 dziesiętny.
Adresy zaczynające się od 01111111lub 127 dziesiętne, są zarezerwowane dla sprzężenia zwrotnego i wewnętrznego testowania na komputerze lokalnym [Możesz to sprawdzić: zawsze powinieneś być w stanie pingować 127.0.0.1, co wskazuje na ciebie]; Adresy klasy D są zarezerwowane dla multiemisji; Adresy klasy E są zarezerwowane do użytku w przyszłości. Nie powinny być używane jako adresy hostów.
Przykład
Class | Leftmost bits | Start address | Finish address |
ZA | 0xxx | 0.0.0.0 | 127.255.255.255 |
b | 10xx | 128.0.0.0 | 191.255.255.255 |
do | 110x | 192.0.0.0 | 223.255.255.255 |
re | 1110 | 224.0.0.0 | 239.255.255.255 |
mi | 1111 | 240.0.0.0 | 255.255.255.255 |
Podsieci
Podsieci lub podsieci zasadniczo oznaczają rozgałęzienie sieci. Można to zrobić z różnych powodów, takich jak sieć w organizacji, użycie różnych fizycznych nośników (takich jak Ethernet, FDDI, WAN itp.), Zachowanie przestrzeni adresowej i bezpieczeństwo. Najczęstszym powodem jest kontrolowanie ruchu sieciowego.
Podstawową ideą tworzenia podsieci jest podzielenie części adresu IP zawierającej identyfikator hosta na dwie części -
- Adres podsieci w samym adresie sieciowym; i
- Adres hosta w podsieci.
Na przykład powszechnym formatem adresu klasy B jest N1.N2.SH, gdzie N1.N2 oznacza sieć klasy B, 8-bitowe pole S określa podsieć, a 8-bitowe pole H identyfikuje hosta w podsieci.
Nazwy hostów w postaci liczb są trudne do zapamiętania, dlatego nazywane są zwykłymi nazwami, takimi jak Takshila lub Nalanda. Piszemy aplikacje, aby znaleźć kropkowany adres IP odpowiadający danej nazwie.
Proces znajdowania kropkowanego adresu IP na podstawie podanej alfanumerycznej nazwy hosta jest znany jako hostname resolution.
Rozpoznawanie nazwy hosta odbywa się za pomocą specjalnego oprogramowania znajdującego się w systemach o dużej pojemności. Systemy te nazywane są systemami nazw domen (DNS), które przechowują mapowanie adresów IP i odpowiadające im zwykłe nazwy.
Plik / etc / hosts
Korespondencja między nazwami hostów i adresami IP jest przechowywana w pliku o nazwie hosts . W większości systemów ten plik znajduje się w/etc informator.
Wpisy w tym pliku wyglądają następująco -
# This represents a comments in /etc/hosts file.
127.0.0.1 localhost
192.217.44.207 nalanda metro
153.110.31.18 netserve
153.110.31.19 mainserver centeral
153.110.31.20 samsonite
64.202.167.10 ns3.secureserver.net
64.202.167.97 ns4.secureserver.net
66.249.89.104 www.google.com
68.178.157.132 services.amrood.com
Należy pamiętać, że z danym adresem IP może być skojarzona więcej niż jedna nazwa. Ten plik jest używany podczas konwersji adresu IP na nazwę hosta i odwrotnie.
Nie miałbyś dostępu do edycji tego pliku, więc jeśli chcesz umieścić dowolną nazwę hosta wraz z adresem IP, musisz mieć uprawnienia roota.
Większość aplikacji sieciowych wykorzystuje architekturę klient-serwer, która odnosi się do dwóch procesów lub dwóch aplikacji, które komunikują się ze sobą w celu wymiany informacji. Jeden z dwóch procesów działa jako proces klienta, a inny proces działa jako serwer.
Proces klienta
Jest to proces, który zazwyczaj polega na żądaniu informacji. Po otrzymaniu odpowiedzi proces ten może się zakończyć lub wykonać inne przetwarzanie.
Example, Przeglądarka internetowa działa jako aplikacja kliencka, która wysyła żądanie do serwera sieci Web w celu uzyskania jednej strony internetowej w formacie HTML.
Proces serwera
Jest to proces, który przyjmuje żądanie od klientów. Po otrzymaniu żądania od klienta proces ten wykona wymagane przetwarzanie, zgromadzi żądane informacje i wyśle je do klienta żądającego. Po zakończeniu staje się gotowy do obsługi innego klienta. Procesy serwera są zawsze czujne i gotowe do obsługi przychodzących żądań.
Example - Serwer sieciowy czeka na żądania z przeglądarek internetowych i gdy tylko otrzyma żądanie z przeglądarki, odbiera żądaną stronę HTML i wysyła ją z powrotem do tej przeglądarki.
Należy pamiętać, że klient musi znać adres serwera, ale serwer nie musi znać adresu ani nawet istnienia klienta przed ustanowieniem połączenia. Po ustanowieniu połączenia obie strony mogą wysyłać i odbierać informacje.
Architektury dwu- i trójwarstwowe
Istnieją dwa typy architektur klient-serwer -
2-tier architecture- W tej architekturze klient współpracuje bezpośrednio z serwerem. Ten typ architektury może mieć pewne luki w zabezpieczeniach i problemy z wydajnością. Internet Explorer i Web Server działają w architekturze dwuwarstwowej. Tutaj problemy z bezpieczeństwem są rozwiązywane przy użyciu protokołu Secure Socket Layer (SSL).
3-tier architectures- W tej architekturze jeszcze jedno oprogramowanie znajduje się pomiędzy klientem a serwerem. To oprogramowanie pośrednie nazywa się „oprogramowaniem pośrednim”. Oprogramowanie pośredniczące służy do wykonywania wszystkich kontroli bezpieczeństwa i równoważenia obciążenia w przypadku dużego obciążenia. Oprogramowanie pośredniczące pobiera wszystkie żądania od klienta i po wykonaniu wymaganego uwierzytelnienia przekazuje je do serwera. Następnie serwer wykonuje wymagane przetwarzanie i wysyła odpowiedź z powrotem do oprogramowania pośredniego, a na końcu oprogramowanie pośredniczące przekazuje tę odpowiedź z powrotem do klienta. Jeśli chcesz zaimplementować architekturę trójwarstwową, możesz zachować wszelkie oprogramowanie pośrednie, takie jak oprogramowanie Web Logic lub WebSphere, pomiędzy serwerem WWW a przeglądarką WWW.
Rodzaje serwerów
Istnieją dwa typy serwerów, które możesz mieć -
Iterative Server- Jest to najprostsza forma serwera, w której proces serwera obsługuje jednego klienta i po wykonaniu pierwszego żądania przyjmuje żądanie od innego klienta. Tymczasem kolejny klient czeka.
Concurrent Servers- Ten typ serwera uruchamia wiele współbieżnych procesów, aby obsłużyć wiele żądań jednocześnie, ponieważ jeden proces może trwać dłużej, a inny klient nie może czekać tak długo. Najprostszym sposobem napisania współbieżnego serwera pod Uniksem jest rozwidlenie procesu potomnego do obsługi każdego klienta osobno.
Jak zrobić klienta
System wywołań ustanowienia połączenia jest nieco inny dla klienta i serwera, ale oba obejmują podstawową konstrukcję gniazda. Oba procesy tworzą własne gniazda.
Kroki związane z ustanowieniem gniazda po stronie klienta są następujące -
Utwórz gniazdo z socket() wywołanie systemowe.
Podłącz gniazdo do adresu serwera za pomocą rozszerzenia connect() wywołanie systemowe.
Wysyłaj i odbieraj dane. Można to zrobić na wiele sposobów, ale najprostszym jest użycie rozszerzeniaread() i write() wywołania systemowe.
Jak stworzyć serwer
Kroki związane z ustanowieniem gniazda po stronie serwera są następujące -
Utwórz gniazdo z socket() wywołanie systemowe.
Powiąż gniazdo z adresem przy użyciu rozszerzenia bind()wywołanie systemowe. W przypadku gniazda serwera w Internecie adres składa się z numeru portu na komputerze hosta.
Nasłuchuj połączeń z listen() wywołanie systemowe.
Zaakceptuj połączenie z accept()wywołanie systemowe. To wywołanie zazwyczaj blokuje połączenie, dopóki klient nie połączy się z serwerem.
Wysyłaj i odbieraj dane za pomocą read() i write() wywołania systemowe.
Interakcja klienta i serwera
Poniżej znajduje się diagram przedstawiający pełną interakcję klienta i serwera -
Różne struktury są używane w programowaniu gniazd Unix do przechowywania informacji o adresie i porcie oraz innych informacji. Większość funkcji gniazd wymaga jako argumentu wskaźnika do struktury adresu gniazda. Struktury zdefiniowane w tym rozdziale odnoszą się do rodziny protokołów internetowych.
sockaddr
Pierwsza struktura to sockaddr, która przechowuje informacje o gnieździe -
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
Jest to ogólna struktura adresu gniazda, która będzie przekazywana w większości wywołań funkcji gniazda. Poniższa tabela zawiera opis pól członków -
Atrybut | Wartości | Opis |
---|---|---|
sa_family | AF_INET AF_UNIX AF_NS AF_IMPLINK |
Reprezentuje rodzinę adresów. W większości aplikacji internetowych używamy AF_INET. |
sa_data | Adres zależny od protokołu | Zawartość 14 bajtów adresu specyficznego dla protokołu jest interpretowana zgodnie z typem adresu. W przypadku rodziny internetowej użyjemy adresu IP numeru portu, który jest reprezentowany przez strukturę sockaddr_in zdefiniowaną poniżej. |
sockaddr in
Druga struktura, która pomaga w odwołaniu się do elementów gniazda, jest następująca -
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
Oto opis pól członkowskich -
Atrybut | Wartości | Opis |
---|---|---|
sa_family | AF_INET AF_UNIX AF_NS AF_IMPLINK |
Reprezentuje rodzinę adresów. W większości aplikacji internetowych używamy AF_INET. |
sin_port | Port serwisowy | 16-bitowy numer portu w sieciowej kolejności bajtów. |
sin_addr | Adres IP | 32-bitowy adres IP w sieciowej kolejności bajtów. |
sin_zero | Nieużywany | Po prostu ustaw tę wartość na NULL, ponieważ nie jest ona używana. |
in addr
Ta struktura jest używana tylko w powyższej strukturze jako pole struktury i zawiera 32-bitowy identyfikator netid / hostid.
struct in_addr {
unsigned long s_addr;
};
Oto opis pól członkowskich -
Atrybut | Wartości | Opis |
---|---|---|
s_addr | port serwisowy | 32-bitowy adres IP w sieciowej kolejności bajtów. |
hostent
Ta struktura służy do przechowywania informacji związanych z hostem.
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list
#define h_addr h_addr_list[0]
};
Oto opis pól członkowskich -
Atrybut | Wartości | Opis |
---|---|---|
h_name | ti.com itp. | Jest to oficjalna nazwa gospodarza. Na przykład tutorialspoint.com, google.com itp. |
h_aliases | TI | Zawiera listę aliasów nazw hostów. |
h_addrtype | AF_INET | Zawiera rodzinę adresów, aw przypadku aplikacji internetowych zawsze będzie to AF_INET. |
h_length | 4 | Przechowuje długość adresu IP, która wynosi 4 dla adresu internetowego. |
h_addr_list | in_addr | W przypadku adresów internetowych tablica wskaźników h_addr_list [0], h_addr_list [1] itd. Wskazuje na strukturę in_addr. |
NOTE - h_addr jest zdefiniowany jako h_addr_list [0], aby zachować wsteczną kompatybilność.
servent
Ta szczególna struktura służy do przechowywania informacji związanych z usługą i powiązanymi portami.
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
Oto opis pól członkowskich -
Atrybut | Wartości | Opis |
---|---|---|
s_name | http | To jest oficjalna nazwa serwisu. Na przykład SMTP, FTP POP3 itp. |
s_aliases | ALIAS | Zawiera listę aliasów usług. W większości przypadków będzie to NULL. |
sport | 80 | Będzie miał powiązany numer portu. Na przykład dla HTTP będzie to 80. |
s_proto | TCP UDP |
Jest ustawiony na używany protokół. Usługi internetowe są świadczone przy użyciu protokołu TCP lub UDP. |
Wskazówki dotyczące struktur gniazd
Struktury adresów gniazd są integralną częścią każdego programu sieciowego. Przydzielamy je, wypełniamy i przekazujemy do nich wskaźniki do różnych funkcji gniazd. Czasami przekazujemy wskaźnik do jednej z tych struktur do funkcji gniazda i wypełnia ona zawartość.
Zawsze przekazujemy te struktury przez odniesienie (tj. Przekazujemy wskaźnik do struktury, a nie samą strukturę) i zawsze przekazujemy rozmiar struktury jako kolejny argument.
Gdy funkcja gniazda wypełnia strukturę, długość jest również przekazywana przez odwołanie, dzięki czemu jej wartość może być aktualizowana przez funkcję. Nazywamy te argumenty wartość-wynik.
Zawsze ustawiaj zmienne struktury na NULL (tj. '\ 0') za pomocą memset () dla funkcji bzero (), w przeciwnym razie może dojść do nieoczekiwanych wartości śmieci w Twojej strukturze.
Gdy proces klienta chce połączyć się z serwerem, klient musi mieć możliwość zidentyfikowania serwera, z którym chce się połączyć. Jeśli klient zna 32-bitowy adres internetowy hosta, na którym znajduje się serwer, może skontaktować się z tym hostem. Ale w jaki sposób klient identyfikuje konkretny proces serwera działający na tym hoście?
Aby rozwiązać problem z identyfikacją konkretnego procesu serwera działającego na hoście, w protokołach TCP i UDP zdefiniowano grupę dobrze znanych portów.
W naszym celu port zostanie zdefiniowany jako liczba całkowita z zakresu od 1024 do 65535. Dzieje się tak, ponieważ wszystkie numery portów mniejsze niż 1024 są uważane za dobrze znane - na przykład telnet używa portu 23, http używa 80, ftp używa 21, i tak dalej.
Przypisania portów do usług sieciowych można znaleźć w pliku / etc / services. Jeśli piszesz na własnym serwerze, musisz zadbać o przypisanie portu do swojego serwera. Powinieneś upewnić się, że ten port nie powinien być przypisany do żadnego innego serwera.
Zwykle praktyką jest przypisywanie dowolnego portu o numerze większym niż 5000. Ale jest wiele organizacji, które napisały serwery o numerach portów powyżej 5000. Na przykład Yahoo Messenger działa na 5050, SIP Server na 5060 itd.
Przykładowe porty i usługi
Oto mała lista usług i powiązanych portów. Najbardziej aktualną listę portów internetowych i powiązanych usług można znaleźć na stronie IANA - TCP / IP Port Assignments .
Service | Port Number | Service Description |
Echo | 7 | UDP / TCP odsyła to, co otrzymuje. |
odrzucać | 9 | UDP / TCP odrzuca dane wejściowe. |
dzień | 13 | UDP / TCP zwraca czas ASCII. |
ładować | 19 | UDP / TCP zwraca znaki. |
ftp | 21 | Transfer plików TCP. |
telnet | 23 | Zdalne logowanie TCP. |
smtp | 25 | E-mail TCP. |
dzień | 37 | UDP / TCP zwraca czas binarny. |
tftp | 69 | Prosty transfer plików UDP. |
palec | 79 | Informacje TCP o użytkownikach. |
http | 80 | Sieć TCP w sieci WWW. |
Zaloguj sie | 513 | Zdalne logowanie TCP. |
WHO | 513 | UDP różne informacje o użytkownikach. |
Xserver | 6000 | Okna TCP X (NB> 1023). |
Funkcje portów i usług
Unix udostępnia następujące funkcje do pobierania nazwy usługi z pliku / etc / services.
struct servent *getservbyname(char *name, char *proto) - To wywołanie przyjmuje nazwę usługi i nazwę protokołu oraz zwraca odpowiedni numer portu dla tej usługi.
struct servent *getservbyport(int port, char *proto) - To wywołanie przyjmuje numer portu i nazwę protokołu oraz zwraca odpowiednią nazwę usługi.
Wartość zwracana dla każdej funkcji jest wskaźnikiem do struktury o następującej postaci -
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
Oto opis pól członkowskich -
Atrybut | Wartości | Opis |
---|---|---|
s_name | http | To jest oficjalna nazwa serwisu. Na przykład SMTP, FTP POP3 itp. |
s_aliases | ALIAS | Zawiera listę aliasów usług. W większości przypadków będzie ustawiony na NULL. |
sport | 80 | Będzie miał powiązany numer portu. Na przykład dla HTTP będzie to 80. |
s_proto | TCP UDP |
Jest ustawiony na używany protokół. Usługi internetowe są świadczone przy użyciu protokołu TCP lub UDP. |
Niestety nie wszystkie komputery przechowują bajty zawierające wartość wielobajtową w tej samej kolejności. Rozważmy 16-bitowy internet, który składa się z 2 bajtów. Istnieją dwa sposoby przechowywania tej wartości.
Little Endian - W tym schemacie młodszy bajt jest przechowywany pod adresem początkowym (A), a najstarszy bajt jest przechowywany pod następnym adresem (A + 1).
Big Endian - W tym schemacie najstarszy bajt jest przechowywany pod adresem początkowym (A), a najmniejszy bajt jest przechowywany pod następnym adresem (A + 1).
Aby umożliwić komunikację między komputerami o różnych konwencjach kolejności bajtów, protokoły internetowe określają kanoniczną konwencję kolejności bajtów dla danych przesyłanych w sieci. Jest to znane jako kolejność bajtów sieci.
Podczas ustanawiania połączenia internetowego przez gniazdo należy upewnić się, że dane w elementach sin_port i sin_addr struktury sockaddr_in są reprezentowane w sieciowej kolejności bajtów.
Funkcje porządkowania bajtów
Procedury konwersji danych między wewnętrzną reprezentacją hosta a kolejnością bajtów sieci są następujące -
Funkcjonować | Opis |
---|---|
htons () | Host do sieci Short |
htonl () | Host do sieci długo |
ntohl () | Sieć do hostowania długo |
ntohs () | Sieć do hostowania Short |
Poniżej znajduje się więcej szczegółów na temat tych funkcji -
unsigned short htons(unsigned short hostshort) - Ta funkcja konwertuje wartości 16-bitowe (2-bajtowe) z kolejności bajtów hosta na kolejność bajtów sieci.
unsigned long htonl(unsigned long hostlong) - Ta funkcja konwertuje wielkości 32-bitowe (4-bajtowe) z kolejności bajtów hosta na kolejność bajtów sieci.
unsigned short ntohs(unsigned short netshort) - Ta funkcja konwertuje wartości 16-bitowe (2-bajtowe) z sieciowej kolejności bajtów na kolejność bajtów hosta.
unsigned long ntohl(unsigned long netlong) - Ta funkcja konwertuje ilości 32-bitowe z kolejności bajtów sieci na kolejność bajtów hosta.
Te funkcje są makrami i powodują wstawienie kodu źródłowego konwersji do programu wywołującego. Na maszynach little-endian kod zmieni wartości na sieciową kolejność bajtów. Na maszynach typu big-endian żaden kod nie jest wstawiany, ponieważ nie jest potrzebny; funkcje są zdefiniowane jako null.
Program do określania kolejności bajtów hosta
Zachowaj następujący kod w pliku byteorder.c, a następnie skompiluj go i uruchom na swoim komputerze.
W tym przykładzie przechowujemy dwubajtową wartość 0x0102 w krótkiej liczbie całkowitej, a następnie patrzymy na dwa kolejne bajty, c [0] (adres A) ic [1] (adres A + 1), aby określić bajt zamówienie.
#include <stdio.h>
int main(int argc, char **argv) {
union {
short s;
char c[sizeof(short)];
}un;
un.s = 0x0102;
if (sizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
else
printf("unknown\n");
}
else {
printf("sizeof(short) = %d\n", sizeof(short));
}
exit(0);
}
Dane wyjściowe generowane przez ten program na komputerze z procesorem Pentium są następujące -
$> gcc byteorder.c $> ./a.out
little-endian
$>
Unix udostępnia różne wywołania funkcji, które pomagają w manipulowaniu adresami IP. Funkcje te konwertują adresy internetowe między łańcuchami ASCII (preferowanymi przez ludzi) a wartościami binarnymi uporządkowanymi według bajtów sieci (wartościami przechowywanymi w strukturach adresów gniazda).
Następujące trzy wywołania funkcji są używane do adresowania IPv4 -
- int inet_aton (const char * strptr, struct in_addr * addrptr)
- in_addr_t inet_addr (const char * strptr)
- char * inet_ntoa (struct in_addr inaddr)
int inet_aton (const char * strptr, struct in_addr * addrptr)
To wywołanie funkcji konwertuje określony ciąg w standardowej notacji kropkowej w Internecie na adres sieciowy i przechowuje adres w podanej strukturze. Przekonwertowany adres będzie w sieciowej kolejności bajtów (bajty uporządkowane od lewej do prawej). Zwraca 1, jeśli ciąg był prawidłowy i 0 w przypadku błędu.
Poniżej znajduje się przykład użycia -
#include <arpa/inet.h>
(...)
int retval;
struct in_addr addrptr
memset(&addrptr, '\0', sizeof(addrptr));
retval = inet_aton("68.178.157.132", &addrptr);
(...)
in_addr_t inet_addr (const char * strptr)
To wywołanie funkcji konwertuje określony ciąg w standardowej notacji z kropkami w Internecie na liczbę całkowitą odpowiednią do użycia jako adres internetowy. Przekonwertowany adres będzie w sieciowej kolejności bajtów (bajty uporządkowane od lewej do prawej). Zwraca 32-bitowy binarny adres sieciowy uporządkowany w bajtach IPv4 i INADDR_NONE w przypadku błędu.
Poniżej znajduje się przykład użycia -
#include <arpa/inet.h>
(...)
struct sockaddr_in dest;
memset(&dest, '\0', sizeof(dest));
dest.sin_addr.s_addr = inet_addr("68.178.157.132");
(...)
char * inet_ntoa (struct in_addr inaddr)
To wywołanie funkcji konwertuje określony adres hosta internetowego na ciąg znaków w standardowej notacji kropkowej.
Poniżej znajduje się przykład użycia -
#include <arpa/inet.h>
(...)
char *ip;
ip = inet_ntoa(dest.sin_addr);
printf("IP Address is: %s\n",ip);
(...)
W tym rozdziale opisano podstawowe funkcje gniazda wymagane do napisania kompletnego klienta i serwera TCP.
Poniższy diagram przedstawia pełną interakcję klienta i serwera -
Funkcja gniazda
Aby wykonać operacje we / wy sieci, pierwszą rzeczą, którą musi wykonać proces, jest wywołanie funkcji gniazda, określenie typu żądanego protokołu komunikacyjnego i rodziny protokołów itp.
#include <sys/types.h>
#include <sys/socket.h>
int socket (int family, int type, int protocol);
To wywołanie zwraca deskryptor gniazda, którego można użyć w późniejszych wywołaniach systemowych lub -1 w przypadku błędu.
Parametry
family - Określa rodzinę protokołów i jest jedną ze stałych pokazanych poniżej -
Rodzina | Opis |
---|---|
AF_INET | Protokoły IPv4 |
AF_INET6 | Protokoły IPv6 |
AF_LOCAL | Protokoły domeny Unix |
AF_ROUTE | Gniazda routingu |
AF_KEY | Gniazdo Ket |
W tym rozdziale nie omówiono innych protokołów z wyjątkiem IPv4.
type- Określa rodzaj gniazda, które chcesz. Może przyjąć jedną z następujących wartości -
Rodzaj | Opis |
---|---|
SOCK_STREAM | Gniazdo strumieniowe |
SOCK_DGRAM | Gniazdo datagramowe |
SOCK_SEQPACKET | Sekwencyjne gniazdo pakietów |
SOCK_RAW | Gniazdo surowe |
protocol - Argument powinien być ustawiony na konkretny typ protokołu podany poniżej lub 0, aby wybrać domyślny system dla danej kombinacji rodziny i typu -
Protokół | Opis |
---|---|
IPPROTO_TCP | Protokół transportowy TCP |
IPPROTO_UDP | Protokół transportowy UDP |
IPPROTO_SCTP | Protokół transportowy SCTP |
Connect Function
Funkcja connect jest używana przez klienta TCP do nawiązywania połączenia z serwerem TCP.
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
To wywołanie zwraca 0, jeśli pomyślnie łączy się z serwerem, w przeciwnym razie zwraca -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
serv_addr - Jest to wskaźnik do struct sockaddr, który zawiera docelowy adres IP i port.
addrlen - Ustaw na sizeof (struct sockaddr).
Wiążą Function
Funkcja bind przypisuje adres protokołu lokalnego do gniazda. W przypadku protokołów internetowych adres protokołu to kombinacja 32-bitowego adresu IPv4 lub 128-bitowego adresu IPv6 wraz z 16-bitowym numerem portu TCP lub UDP. Ta funkcja jest wywoływana tylko przez serwer TCP.
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr,int addrlen);
To wywołanie zwraca 0, jeśli pomyślnie połączy się z adresem, w przeciwnym razie zwraca -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
my_addr - Jest to wskaźnik do struct sockaddr, który zawiera lokalny adres IP i port.
addrlen - Ustaw na sizeof (struct sockaddr).
Możesz automatycznie umieścić swój adres IP i port
Wartość 0 dla numeru portu oznacza, że system wybierze losowy port, a wartość INADDR_ANY adresu IP oznacza, że adres IP serwera zostanie przypisany automatycznie.
server.sin_port = 0;
server.sin_addr.s_addr = INADDR_ANY;
NOTE- Wszystkie porty poniżej 1024 są zarezerwowane. Możesz ustawić port powyżej 1024 i poniżej 65535, chyba że są one używane przez inne programy.
Słuchać funkcji
Funkcja nasłuchiwania jest wywoływana tylko przez serwer TCP i wykonuje dwie czynności -
Funkcja Listen przekształca niepołączone gniazdo w gniazdo pasywne, wskazując, że jądro powinno akceptować przychodzące żądania połączeń kierowane do tego gniazda.
Drugi argument tej funkcji określa maksymalną liczbę połączeń, jakie jądro powinno kolejkować dla tego gniazda.
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd,int backlog);
To wywołanie zwraca 0 w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
backlog - Jest to liczba dozwolonych połączeń.
Przyjąć funkcji
Funkcja accept jest wywoływana przez serwer TCP w celu zwrócenia następnego ukończonego połączenia z początku kolejki zakończonych połączeń. Podpis wezwania jest następujący -
#include <sys/types.h>
#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
To wywołanie zwraca nieujemny deskryptor w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu. Przyjmuje się, że zwrócony deskryptor jest deskryptorem gniazda klienta i wszystkie operacje odczytu i zapisu będą wykonywane na tym deskryptorze w celu komunikacji z klientem.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
cliaddr - Jest to wskaźnik do struct sockaddr, który zawiera adres IP i port klienta.
addrlen - Ustaw na sizeof (struct sockaddr).
Wyślij Function
Funkcja wysyłania służy do wysyłania danych przez gniazda strumieniowe lub gniazda datagramowe CONNECTED. Jeśli chcesz wysyłać dane przez NIEPODŁĄCZONE gniazda datagramowe, musisz użyć funkcji sendto ().
Możesz użyć wywołania systemowego write () do wysyłania danych. Jego podpis jest następujący -
int send(int sockfd, const void *msg, int len, int flags);
To wywołanie zwraca liczbę wysłanych bajtów, w przeciwnym razie zwróci -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
msg - Jest to wskaźnik do danych, które chcesz wysłać.
len - Jest to długość danych, które chcesz wysłać (w bajtach).
flags - Jest ustawiony na 0.
Recv Function
Funkcja recv służy do odbierania danych przez gniazda strumieniowe lub gniazda datagramowe CONNECTED. Jeśli chcesz otrzymywać dane przez NIEPODŁĄCZONE gniazda datagramowe, musisz użyć funkcji recvfrom ().
Do odczytania danych można użyć wywołania systemowego read () . To wywołanie jest wyjaśnione w rozdziale o funkcjach pomocniczych.
int recv(int sockfd, void *buf, int len, unsigned int flags);
To wywołanie zwraca liczbę bajtów odczytanych do bufora, w przeciwnym razie zwróci -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
buf - Jest to bufor do wczytywania informacji.
len - Jest to maksymalna długość bufora.
flags - Jest ustawiony na 0.
Sendto Function
Funkcja sendto służy do wysyłania danych przez NIEPODŁĄCZONE gniazda datagramowe. Jego podpis jest następujący -
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
To wywołanie zwraca liczbę wysłanych bajtów, w przeciwnym razie zwraca -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
msg - Jest to wskaźnik do danych, które chcesz wysłać.
len - Jest to długość danych, które chcesz wysłać (w bajtach).
flags - Jest ustawiony na 0.
to - Jest to wskaźnik struct sockaddr dla hosta, do którego mają zostać wysłane dane.
tolen - Jest ustawiony na sizeof (struct sockaddr).
Recvfrom Function
Funkcja recvfrom służy do odbierania danych z gniazd datagramowych UNCONNECTED.
int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);
To wywołanie zwraca liczbę bajtów wczytanych do bufora, w przeciwnym razie zwraca -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
buf - Jest to bufor do wczytywania informacji.
len - Jest to maksymalna długość bufora.
flags - Jest ustawiony na 0.
from - Jest to wskaźnik struct sockaddr dla hosta, z którego mają zostać odczytane dane.
fromlen - Jest ustawiony na sizeof (struct sockaddr).
Blisko Function
Funkcja close służy do zamykania komunikacji między klientem a serwerem. Jego składnia jest następująca -
int close( int sockfd );
To wywołanie zwraca 0 w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
Wyłączanie funkcji
Funkcja zamykania służy do bezpiecznego zamykania komunikacji między klientem a serwerem. Ta funkcja zapewnia większą kontrolę w porównaniu z funkcją zamykania . Poniżej podano składnię shutdown -
int shutdown(int sockfd, int how);
To wywołanie zwraca 0 w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu.
Parametry
sockfd - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
how - Wpisz jedną z liczb -
0 - wskazuje, że odbieranie jest niedozwolone,
1 - wskazuje, że wysyłanie jest niedozwolone i
2- wskazuje, że zarówno wysyłanie, jak i odbieranie są niedozwolone. Gdy jak jest ustawione na 2, jest to to samo, co close ().
Select Function
Funkcja select wskazuje, który z określonych deskryptorów plików jest gotowy do odczytu, gotowy do zapisu lub ma oczekujący stan błędu.
Gdy aplikacja wywołuje recv lub recvfrom , jest blokowana do momentu nadejścia danych do tego gniazda. Aplikacja może wykonywać inne przydatne operacje, gdy strumień danych przychodzących jest pusty. Inna sytuacja ma miejsce, gdy aplikacja otrzymuje dane z wielu gniazd.
Wywołanie recv lub recvfrom w gnieździe, które nie ma danych w swojej kolejce wejściowej, zapobiega natychmiastowemu otrzymywaniu danych z innych gniazd. Wywołanie funkcji select rozwiązuje ten problem, umożliwiając programowi odpytywanie wszystkich uchwytów gniazd w celu sprawdzenia, czy są one dostępne dla nieblokujących operacji odczytu i zapisu.
Poniżej podano składnię select -
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
To wywołanie zwraca 0 w przypadku sukcesu, w przeciwnym razie zwraca -1 w przypadku błędu.
Parametry
nfds- Określa zakres deskryptorów plików do przetestowania. Funkcja select () testuje deskryptory plików w zakresie od 0 do nfds-1
readfds- Wskazuje na obiekt typu fd_set, który na wejściu określa deskryptory plików, które mają być sprawdzane pod kątem gotowości do odczytu, a na wyjściu wskazuje, które deskryptory plików są gotowe do odczytu. Może mieć wartość NULL, aby wskazać pusty zestaw.
writefds- Wskazuje na obiekt typu fd_set, który na wejściu określa deskryptory plików do sprawdzenia pod kątem gotowości do zapisu, a na wyjściu wskazuje, które deskryptory plików są gotowe do zapisu. Może mieć wartość NULL, aby wskazać pusty zestaw.
exceptfds- Wskazuje na obiekt typu fd_set, który na wejściu określa deskryptory plików, które mają być sprawdzane pod kątem oczekujących warunków błędów, a na wyjściu wskazuje, które deskryptory plików mają oczekujące warunki błędu. Może mieć wartość NULL, aby wskazać pusty zestaw.
timeout- Wskazuje na strukturę timeval, która określa, jak długo wywołanie select powinno odpytywać deskryptory pod kątem dostępnej operacji we / wy. Jeśli wartość limitu czasu wynosi 0, funkcja select natychmiast zwróci. Jeśli argument timeout ma wartość NULL, select będzie blokować, dopóki co najmniej jeden uchwyt pliku / gniazda nie będzie gotowy na dostępną operację we / wy. W przeciwnym razie opcja select zwróci wartość po upływie określonego czasu LUB gdy co najmniej jeden deskryptor pliku / gniazda jest gotowy do operacji we / wy.
Wartość zwracana z funkcji select to liczba uchwytów określonych w zestawach deskryptorów plików, które są gotowe do operacji we / wy. Jeśli zostanie osiągnięty limit czasu określony przez pole limitu czasu, wybierz opcję powrotu 0. Istnieją następujące makra służące do manipulowania zestawem deskryptorów plików -
FD_CLR(fd, &fdset)- Czyści bit dla deskryptora pliku fd w zestawie deskryptorów pliku fdset.
FD_ISSET(fd, &fdset)- Zwraca niezerową wartość, jeśli bit deskryptora pliku fd jest ustawiony w deskryptorze pliku wskazywanym przez fdset , a 0 w przeciwnym razie.
FD_SET(fd, &fdset) - Ustawia bit dla deskryptora pliku fd w zestawie deskryptorów pliku fdset.
FD_ZERO(&fdset) - Inicjuje zestaw deskryptorów plików fdset tak, aby miał zero bitów dla wszystkich deskryptorów plików.
Zachowanie tych makr jest niezdefiniowane, jeśli argument fd jest mniejszy niż 0 lub większy lub równy FD_SETSIZE.
Przykład
fd_set fds;
struct timeval tv;
/* do socket initialization etc.
tv.tv_sec = 1;
tv.tv_usec = 500000;
/* tv now represents 1.5 seconds */
FD_ZERO(&fds);
/* adds sock to the file descriptor set */
FD_SET(sock, &fds);
/* wait 1.5 seconds for any data to be read from any single socket */
select(sock+1, &fds, NULL, NULL, &tv);
if (FD_ISSET(sock, &fds)) {
recvfrom(s, buffer, buffer_len, 0, &sa, &sa_len);
/* do something */
}
else {
/* do something else */
}
Ten rozdział opisuje wszystkie funkcje pomocnicze, które są używane podczas programowania gniazd. Inne funkcje pomocnicze opisano w rozdziałach -Ports and Servicesi sieć Byte Orders.
Zapisu Funkcja
Funkcja write próbuje zapisać n-bajtów bajtów z bufora wskazanego przez buf do pliku skojarzonego z deskryptorem otwartego pliku, fildes .
Możesz także użyć funkcji send (), aby wysłać dane do innego procesu.
#include <unistd.h>
int write(int fildes, const void *buf, int nbyte);
Po pomyślnym zakończeniu write () zwraca liczbę bajtów faktycznie zapisanych w pliku powiązanym z fildami. Ta liczba nigdy nie jest większa niż nbajtów. W przeciwnym razie zwracane jest -1.
Parametry
fildes - Jest to deskryptor gniazda zwracany przez funkcję gniazda.
buf - Jest to wskaźnik do danych, które chcesz wysłać.
nbyte- Jest to liczba bajtów do zapisania. Jeśli nbyte jest równe 0, write () zwróci 0 i nie da żadnych innych wyników, jeśli plik jest zwykłym plikiem; w przeciwnym razie wyniki są nieokreślone.
Odczytu Funkcja
The read function attempts to read nbyte bytes from the file associated with the buffer, fildes, into the buffer pointed to by buf.
You can also use recv() function to read data to another process.
#include <unistd.h>
int read(int fildes, const void *buf, int nbyte);
Upon successful completion, write() returns the number of bytes actually written to the file associated with fildes. This number is never greater than nbyte. Otherwise, -1 is returned.
Parameters
fildes − It is a socket descriptor returned by the socket function.
buf − It is the buffer to read the information into.
nbyte − It is the number of bytes to read.
The fork Function
The fork function creates a new process. The new process called the child process will be an exact copy of the calling process (parent process). The child process inherits many attributes from the parent process.
#include <sys/types.h>
#include <unistd.h>
int fork(void);
Upon successful completion, fork() returns 0 to the child process and the process ID of the child process to the parent process. Otherwise -1 is returned to the parent process, no child process is created and errno is set to indicate the error.
Parameters
void − It means no parameter is required.
The bzero Function
The bzero function places nbyte null bytes in the string s. This function is used to set all the socket structures with null values.
void bzero(void *s, int nbyte);
This function does not return anything.
Parameters
s − It specifies the string which has to be filled with null bytes. This will be a point to socket structure variable.
nbyte − It specifies the number of bytes to be filled with null values. This will be the size of the socket structure.
The bcmp Function
The bcmp function compares byte string s1 against byte string s2. Both strings are assumed to be nbyte bytes long.
int bcmp(const void *s1, const void *s2, int nbyte);
This function returns 0 if both strings are identical, 1 otherwise. The bcmp() function always returns 0 when nbyte is 0.
Parameters
s1 − It specifies the first string to be compared.
s2 − It specifies the second string to be compared.
nbyte − It specifies the number of bytes to be compared.
The bcopy Function
The bcopy function copies nbyte bytes from string s1 to the string s2. Overlapping strings are handled correctly.
void bcopy(const void *s1, void *s2, int nbyte);
Ta funkcja nic nie zwraca.
Parametry
s1 - Określa ciąg źródłowy.
s2v - Określa ciąg docelowy.
nbyte - Określa liczbę bajtów do skopiowania.
Memset Function
Funkcja memset służy również do ustawiania zmiennych strukturalnych w taki sam sposób, jakbzero. Spójrz na jego składnię, podaną poniżej.
void *memset(void *s, int c, int nbyte);
Ta funkcja zwraca wskaźnik do void; w rzeczywistości jest to wskaźnik do pamięci zestawu i musisz go odpowiednio skasować.
Parametry
s - Określa źródło do ustawienia.
c - Określa znak do ustawienia w miejscach n-bajtowych.
nbyte - Określa liczbę bajtów do ustawienia.
Aby proces stał się serwerem TCP, musisz wykonać kroki podane poniżej -
Utwórz gniazdo za pomocą funkcji systemowej socket () .
Powiąż gniazdo z adresem za pomocą wywołania systemowego bind () . W przypadku gniazda serwera w Internecie adres składa się z numeru portu na komputerze hosta.
Nasłuchuj połączeń przy użyciu wywołania systemowego Listen () .
Zaakceptuj połączenie za pomocą wywołania systemowego accept () . To wywołanie jest zwykle blokowane, dopóki klient nie połączy się z serwerem.
Wysyłaj i odbieraj dane za pomocą wywołań systemowych read () i write () .
Teraz umieśćmy te kroki w postaci kodu źródłowego. Umieść ten kod w pliku server.c i skompiluj go za pomocą kompilatora gcc .
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
int main( int argc, char *argv[] ) {
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
/* First call to socket() function */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
/* Initialize socket structure */
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 5001;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR on binding");
exit(1);
}
/* Now start listening for the clients, here process will
* go in sleep mode and will wait for the incoming connection
*/
listen(sockfd,5);
clilen = sizeof(cli_addr);
/* Accept actual connection from the client */
newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
if (newsockfd < 0) {
perror("ERROR on accept");
exit(1);
}
/* If connection is established then start communicating */
bzero(buffer,256);
n = read( newsockfd,buffer,255 );
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
printf("Here is the message: %s\n",buffer);
/* Write a response to the client */
n = write(newsockfd,"I got your message",18);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
return 0;
}
Obsługa wielu połączeń
Aby umożliwić serwerowi obsługę wielu jednoczesnych połączeń, wprowadzamy następujące zmiany w powyższym kodzie -
Umieść instrukcję accept i następujący kod w nieskończonej pętli.
Po nawiązaniu połączenia wywołaj fork (), aby utworzyć nowy proces.
Proces potomny zamknie sockfd i wywoła funkcję doprocessingu , przekazując jako argument nowy deskryptor pliku gniazda. Gdy dwa procesy zakończą konwersację, na co wskazuje zwracanie funkcji doprocessing () , proces ten po prostu kończy się.
Proces nadrzędny zamyka newsockfd . Ponieważ cały ten kod jest w nieskończonej pętli, powróci do instrukcji accept, aby czekać na następne połączenie.
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
void doprocessing (int sock);
int main( int argc, char *argv[] ) {
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n, pid;
/* First call to socket() function */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
/* Initialize socket structure */
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 5001;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR on binding");
exit(1);
}
/* Now start listening for the clients, here
* process will go in sleep mode and will wait
* for the incoming connection
*/
listen(sockfd,5);
clilen = sizeof(cli_addr);
while (1) {
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0) {
perror("ERROR on accept");
exit(1);
}
/* Create child process */
pid = fork();
if (pid < 0) {
perror("ERROR on fork");
exit(1);
}
if (pid == 0) {
/* This is the client process */
close(sockfd);
doprocessing(newsockfd);
exit(0);
}
else {
close(newsockfd);
}
} /* end of while */
}
Poniższy fragment kodu przedstawia prostą implementację funkcji doprocesowania .
void doprocessing (int sock) {
int n;
char buffer[256];
bzero(buffer,256);
n = read(sock,buffer,255);
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
printf("Here is the message: %s\n",buffer);
n = write(sock,"I got your message",18);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
}
Aby uczynić proces klientem TCP, musisz wykonać kroki podane poniżej & minus;
Utwórz gniazdo za pomocą funkcji systemowej socket () .
Połącz gniazdo z adresem serwera za pomocą wywołania systemowego connect () .
Wysyłaj i odbieraj dane. Można to zrobić na wiele sposobów, ale najprostszym sposobem jest użycie wywołań systemowych read () i write () .
Teraz umieśćmy te kroki w postaci kodu źródłowego. Umieść ten kod w plikuclient.c i skompiluj go z gcc kompilator.
Uruchom ten program i podaj nazwę hosta i numer portu serwera, aby połączyć się z serwerem, który musisz już uruchomić w innym oknie Uniksa.
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char *argv[]) {
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];
if (argc < 3) {
fprintf(stderr,"usage %s hostname port\n", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
/* Create a socket point */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(portno);
/* Now connect to the server */
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR connecting");
exit(1);
}
/* Now ask for a message from the user, this message
* will be read by server
*/
printf("Please enter the message: ");
bzero(buffer,256);
fgets(buffer,255,stdin);
/* Send message to the server */
n = write(sockfd, buffer, strlen(buffer));
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
/* Now read server response */
bzero(buffer,256);
n = read(sockfd, buffer, 255);
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
printf("%s\n",buffer);
return 0;
}
Oto lista wszystkich funkcji związanych z programowaniem gniazd.
Funkcje portów i usług
Unix udostępnia następujące funkcje do pobierania nazwy usługi z pliku / etc / services.
struct servent *getservbyname(char *name, char *proto) - To wywołanie przyjmuje nazwę usługi i nazwę protokołu oraz zwraca odpowiedni numer portu dla tej usługi.
struct servent *getservbyport(int port, char *proto) - To wywołanie pobiera numer portu i nazwę protokołu oraz zwraca odpowiednią nazwę usługi.
Funkcje porządkowania bajtów
unsigned short htons (unsigned short hostshort) - Ta funkcja konwertuje wartości 16-bitowe (2-bajtowe) z kolejności bajtów hosta na kolejność bajtów sieci.
unsigned long htonl (unsigned long hostlong) - Ta funkcja konwertuje wielkości 32-bitowe (4-bajtowe) z kolejności bajtów hosta na kolejność bajtów sieci.
unsigned short ntohs (unsigned short netshort) - Ta funkcja konwertuje wartości 16-bitowe (2-bajtowe) z sieciowej kolejności bajtów na kolejność bajtów hosta.
unsigned long ntohl (unsigned long netlong) - Ta funkcja konwertuje ilości 32-bitowe z kolejności bajtów sieci na kolejność bajtów hosta.
Funkcje adresu IP
int inet_aton (const char *strptr, struct in_addr *addrptr)- To wywołanie funkcji konwertuje określony ciąg w standardowej notacji internetowej z kropką na adres sieciowy i zapisuje adres w podanej strukturze. Przekonwertowany adres będzie w sieciowej kolejności bajtów (bajty uporządkowane od lewej do prawej). Zwraca 1, jeśli ciąg jest prawidłowy i 0 w przypadku błędu.
in_addr_t inet_addr (const char *strptr)- To wywołanie funkcji konwertuje określony ciąg w standardowej notacji z kropkami w Internecie na wartość całkowitą, która może być używana jako adres internetowy. Przekonwertowany adres będzie w sieciowej kolejności bajtów (bajty uporządkowane od lewej do prawej). Zwraca 32-bitowy binarny adres sieciowy uporządkowany w bajtach IPv4 i INADDR_NONE w przypadku błędu.
char *inet_ntoa (struct in_addr inaddr) - To wywołanie funkcji konwertuje określony adres hosta internetowego na ciąg znaków w standardowej notacji kropkowej.
Funkcje rdzenia gniazda
int socket (int family, int type, int protocol) - To wywołanie zwraca deskryptor gniazda, którego możesz użyć w późniejszych wywołaniach systemowych lub daje -1 w przypadku błędu.
int connect (int sockfd, struct sockaddr *serv_addr, int addrlen)- Funkcja connect jest używana przez klienta TCP do nawiązywania połączenia z serwerem TCP. To wywołanie zwraca 0, jeśli pomyślnie łączy się z serwerem, w przeciwnym razie zwraca -1.
int bind(int sockfd, struct sockaddr *my_addr,int addrlen)- Funkcja bind przypisuje adres protokołu lokalnego do gniazda. To wywołanie zwraca 0, jeśli pomyślnie połączy się z adresem, w przeciwnym razie zwraca -1.
int listen(int sockfd, int backlog)- Funkcja nasłuchiwania jest wywoływana tylko przez serwer TCP w celu nasłuchiwania żądania klienta. To wywołanie zwraca 0 po pomyślnym zakończeniu, w przeciwnym razie zwraca -1.
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)- Funkcja akceptacji jest wywoływana przez serwer TCP w celu akceptowania żądań klientów i ustanowienia rzeczywistego połączenia. To wywołanie po pomyślnym zakończeniu zwraca nieujemny deskryptor, w przeciwnym razie zwraca -1.
int send(int sockfd, const void *msg, int len, int flags)- Funkcja wysyłania służy do wysyłania danych przez gniazda strumieniowe lub gniazda datagramowe CONNECTED. To wywołanie zwraca liczbę wysłanych bajtów, w przeciwnym razie zwraca -1.
int recv (int sockfd, void *buf, int len, unsigned int flags)- Funkcja recv służy do odbierania danych przez gniazda strumieniowe lub gniazda datagramowe CONNECTED. To wywołanie zwraca liczbę bajtów wczytanych do bufora, w przeciwnym razie zwraca -1 w przypadku błędu.
int sendto (int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen)- Funkcja sendto służy do wysyłania danych przez NIEPODŁĄCZONE gniazda datagramowe. To wywołanie zwraca liczbę wysłanych bajtów, w przeciwnym razie zwraca -1 w przypadku błędu.
int recvfrom (int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen)- Funkcja recvfrom służy do odbierania danych z gniazd datagramowych UNCONNECTED. To wywołanie zwraca liczbę bajtów wczytanych do bufora, w przeciwnym razie zwraca -1 w przypadku błędu.
int close (int sockfd)- Funkcja close służy do zamykania komunikacji między klientem a serwerem. To wywołanie zwraca 0 po pomyślnym zakończeniu, w przeciwnym razie zwraca -1.
int shutdown (int sockfd, int how)- Funkcja zamykania służy do bezpiecznego zamykania komunikacji między klientem a serwerem. Ta funkcja zapewnia większą kontrolę w porównaniu z funkcją zamykania. Po pomyślnym zakończeniu zwraca 0, w przeciwnym razie -1.
int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) - Ta funkcja służy do odczytu lub zapisu wielu gniazd.
Funkcje pomocnicze gniazda
int write (int fildes, const void *buf, int nbyte)- Funkcja zapisu próbuje zapisać n-bajtów bajtów z bufora wskazywanego przez buf do pliku skojarzonego z deskryptorem otwartego pliku, fildes. Po pomyślnym zakończeniu write () zwraca liczbę bajtów faktycznie zapisanych w pliku powiązanym z fildami. Ta liczba nigdy nie jest większa niż nbajtów. W przeciwnym razie zwracane jest -1.
int read (int fildes, const void *buf, int nbyte)- Funkcja odczytu próbuje odczytać n-bajtowych bajtów z pliku skojarzonego z deskryptorem otwartego pliku, fildes, do bufora wskazywanego przez buf. Po pomyślnym zakończeniu write () zwraca liczbę bajtów faktycznie zapisanych w pliku powiązanym z fildami. Ta liczba nigdy nie jest większa niż nbajtów. W przeciwnym razie zwracane jest -1.
int fork (void)- Funkcja widelca tworzy nowy proces. Nowy proces, zwany procesem potomnym, będzie dokładną kopią procesu wywołującego (proces nadrzędny).
void bzero (void *s, int nbyte)- Funkcja bzero umieszcza nbyte bajtów null w łańcuchu s. Ta funkcja zostanie użyta do ustawienia wszystkich struktur gniazd na wartości null.
int bcmp (const void *s1, const void *s2, int nbyte)- Funkcja bcmp porównuje łańcuch bajtów s1 z łańcuchem bajtów s2. Zakłada się, że oba łańcuchy mają długość n-bajtów.
void bcopy (const void *s1, void *s2, int nbyte)- Funkcja bcopy kopiuje n-bajtów bajtów z łańcucha s1 do łańcucha s2. Nakładające się ciągi są obsługiwane poprawnie.
void *memset(void *s, int c, int nbyte) - Funkcja memset jest również używana do ustawiania zmiennych strukturalnych w taki sam sposób jak bzero.