Gniazda w MS Windows. Biblioteka Winsock.
Inicjalizacja Winsock.
Niezbędne pliki nagłówkowe to Winsock.h (starsza wersja biblioteki Winsock - 1.1) lub Winsock2.h (wersja Winsock 2.2). Należy dołączyć plik biblioteczny ws2_32.lib.
W Windows CE oraz Windows 95 dostępna jest wersja 1.1. W nowszych wersjach Windows 95 oraz w Windows 98, Windows Me, Windows NT 4.0, Windows 2000 dostępna jest wersja 2.2 biblioteki.
Przed wywołaniem dowolnej funkcji Winsock należy załadować poprawną wersję biblioteki Winsock. Funkcją, która to realizuje jest WSAStartup:
int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData)
Pierwszy parametr określa wersję biblioteki Winsock, którą chcemy załadować (obecnie najnowszą wersją jest 2.2). Dla wersji 2.2 należy podać wartość 0x0202 lub skorzystać z makra MAKEWORD(2,2). Starszy bajt określa numer podwersji, młodszy określa główny numer wersji.
Drugim parametrem (typ LPWSADATA) jest wskaźnik do struktury WSAData. Struktura ta zawiera informacje o załadowanej wersji Winsock. Wskaźnik do odpowiednio uzupełnionej struktury jest zwracany przez funkcję WSAStartup.
Opis struktury WSAData:
typedef struct WSAData {
WORD wVersion; // wersja Winsock, której chce używać funkcja wywołująca
WORD wHighVersion; // najwyższa wersja Winsock obsługiwana przez
// załadowaną bibliotekę
char szDescription[WSADESCRIPTION_LEN+1]; // opis tekstowy
// załadowanej biblioteki
char szSystemStatus[WSASYS_STATUS_LEN+1]; // opis stanu i
// konfiguracji
unsigned short iMaxSockets; // maks. liczba gniazd - pole ignorowane w Winsock
// powyżej wersji 2
unsigned short iMaxUdpDg; // maks. rozmiar datagramu UDP - pole ignorowane w
// Winsock powyżej wersji 2
char FAR * lpVendorInfo; // info. o sprzedawcy - pole ignorowane w Winsock
// powyżej wersji 2
} WSADATA, FAR * LPWSADATA;
W Win32 FAR jest ignorowane!
Po zakończeniu korzystania z biblioteki Winsock bależy wywołać funkcję WSACleanup(), która usunie bibliotekę z pamięci i zwolni zasoby.
Otwieranie gniazd.
W Win32 gniazdo nie jest identyfikowane przez deskryptor taki jak deskryptor pliku. Utworzony jest typ SOCKET (w Winsock2.h i Winsock.h) - jako UINT_PTR (zdefiniowane w basetsd.h jako unsigned int o rozmiarze takim jak wskaźnik).
Większość nazw rozbudowanych funkcji dotyczących gniazd w Winsock 2.2 rozpoczyna się od liter WSA. Tego przedrostka nie ma większość funkcji z Winsock 1.1 (za wyjątkiem np. WSAStartup i WSACleanup). Winsock 2.2 umożliwia stosowanie podstawowych funkcji z Winsock 1.1 (bez przedrostka WSA).
Gniazda można otworzyć za pomocą dwóch funkcji: WSASocket (Winsock 2.2) i socket (Winsock 1.1).
Podstawowa wersja:
SOCKET socket (
int sf,
int type,
int protocol
);
Rozbudowana wersja:
SOCKET WSASocket (
int af, //rodzina adresów - dla TCP i UDP jest to AF_INET
int type, // typ protokołu np. SOCK_STREAM dla TCP, SOCK_DGRAM dla UDP
int protocol, // IPPROTO_IP dla TCP, IPPROTO_UDP dla UDP
LPWSAPROTOCOL_INFO lpProtocolInfo, // zaawansowane użycie
GROUP g, // nie używane
DWORD dwFlags // zaawansowane użycie
);
Adresowanie gniazd.
struct sockaddr_in
{
short sin_family; // AF_INET
u_short sin_port; // numer portu w sieciowej kolejności bajtów
struct in_addr sin_addr; // adres IP 32 bity w sieciowej kolejności
char sin_zero[8]; // dla zgodności rozmiaru struktury z sockaddr.
}
Numery portów:
0-1023 zarezerwowane dla usług powszechnie znanych,
1024-49151 do wykorzystania przez programy
49152-65535 porty dynamiczne i do wykorzystania przez programy.
Konwersja wartości wielobajtowych z kolejności sieciowej na kolejność komputera i odwrotnie.
u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);
u_long ntohl (u_long netlong);
u_short ntohs(u_short netshort);
Są również odpowiedniki bardziej rozbudowane z przedrostkami WSA (patrz Pomoc).
Funkcja konwertująca „kropkowy” adres IP (np. 149.155.144.32) na adres zapisany na 4 bajtach w kolejności sieciowej:
unsigned long inet_addr(
const char FAR *cp
);
cp jest wskaźnikiem do ciągu znaków zakończonego `\0'.
W Winsock dostępne są funkcje gethostbyname oraz gethostbyaddr (analogiczne jak w systemie Unix).
struct hostent
{
char FAR * h_name;
char FAR * FAR * h_aliases;
short h_addrtype;
short h_length;
char FAR * FAR * h_addr_list;
}
struct hostent FAR * gethostbyname (
coonst char FAR * name
);
name wskazuje na tekstową nazwę komputera.
Podstawowe funkcje (analogiczne jak w systemie Unix).
int bind (
SOCKET s,
const struct sockaddr FAR * name,
int namelen
)
int listen (
SOCKET s,
int backlog
);
SOCKET accept(
SOCKET s,
struct sockaddr FAR * addr,
int FAR * addlen
);
(jest też funkcja WSAAccept)
int connect(
SOCKET s,
const struct sockaddr FAR * name,
int namelen
);
(jest też funkcja WSAConnect)
Transmisja danych.
Wysyłanie danych
int send(
SOCKET s, // gniazdo
const char FAR * buf, // bufor przechowujący wysyłane znaki
int len, // liczba znaków w buforze
int flags // na ogół 0
);
Przykład wysyłania danych:
char sendbuff[2048]
int nBytes = 2048,
nLeft, // ile bajtów zostało do wysłania
indx; // indeks wskazujący początek miejsca w buforze do wysłania nie przesłanych
jeszcze danych
// Tu powinno być wypełnienie bufora danymi
nLeft = nBytes;
indx = 0;
while (nLeft > 0)
{
ret = send (s, &sendbuff[indx], nLeft, 0);
if ( ret == SOCKET_ERROR )
{
// Błąd
}
nLeft -= ret;
indx += ret;
}
Odbiór danych (znaczenie parametrów podobne do send).
int recv(
SOCKET s,
char FAR * buf,
int len,
int flags
);
Zakończenie połączenia.
int shutdown(
SOCKET s,
int how
);
how może przyjąć wartości: SD_RECEIVE, SD_SEND, SD_BOTH. Np. dla SD_SEND nie będą możliwe dalsze wywołania funkcji wysyłających (ale wszystkie dane oczekujące na wysłanie zostaną wysłane, oraz po potwierdzeniu odbioru wygenerowany pakiet FIN).
int closesocket( SOCKET s );
Jest to zamknięcie gniazda i zwolnienie deskryptora. Usunięte będą też dane oczekujące w kolejce.
Przykład zastosowania - serwer i klient echa.
---------------------------------------------------------------------------------------------------------
SERVER
// Program Server.c
//
// Opis:
// Prosta aplikacja serwera TCP, akceptująca nachodzące
// połączenia klientów. Po ustanowieniu połączenia
// uruchamiany jest wątek odczytujący dane przesłane przez
// klienta i odsyłający je z powrotem (o ile opcja odsyłania
// nie jest wyłączona).
//
//
// Opcje wiersza poleceń:
// server [-p:x] [-i:IP] [-o]
// -p:x Numer nasłuchującego portu
// -i:str Nasłuchujący interfejs (nr IP)
// -o Tylko odbiór, bez odsyłania
//
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_PORT 5250
#define DEFAULT_BUFFER 4096
int iPort = DEFAULT_PORT; // Port nasłuchujący klientów
BOOL bInterface = FALSE, // Nasłuchujący interfejs
bRecvOnly = FALSE; // Tylko odbiór, bez odsyłania
char szAddress[128]; // Interfejs nasłuchujący klientów
//
// Funkcja: usage
//
// Opis:
// Wydruk informacji o działaniu programu
//
void usage()
{
printf("użycie: server [-p:x] [-i:IP] [-o]\n\n");
printf(" -p:x Numer nasluchujacego portu \n");
printf(" -i:str Nasluchujacy interfejs \n");
printf(" -o Tylko odbior, bez odsylania\n\n");
ExitProcess(1);
}
//
// Funkcja: ValidateArgs
//
// Opis:
// Analiza argumentów wiersza poleceń i ustalenie wartości
// niektórych zmiennych globalnych, decydujących o wykonaniu
// określonych czynności.
//
void ValidateArgs(int argc, char **argv)
{
int i;
for(i = 1; i < argc; i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (tolower(argv[i][1]))
{
case 'p':
iPort = atoi(&argv[i][3]);
break;
case 'i':
bInterface = TRUE;
if (strlen(argv[i]) > 3)
strcpy(szAddress, &argv[i][3]);
break;
case 'o':
bRecvOnly = TRUE;
break;
default:
usage();
break;
}
}
}
}
//
// Funkcja: ClientThread
//
// Opis:
// Ta funkcja jest wywoływana w postaci wątku obsługującego
// dane połączenie klienta. Przekazany parametr jest uchwytem
// gniazda zwróconym przez wywołanie funkcji accept(). Ta
// funkcja czyta dane nadesłane przez klienta i odsyła je
// z powrotem.
//
DWORD WINAPI ClientThread(LPVOID lpParam)
{
SOCKET sock=(SOCKET)lpParam;
char szBuff[DEFAULT_BUFFER];
int ret,
nLeft,
idx;
while(1)
{
// Wykonanie blokującej funkcji recv ()
//
ret = recv(sock, szBuff, DEFAULT_BUFFER, 0);
if (ret == 0) // Zamknięcie uzgodnione
break;
else if (ret == SOCKET_ERROR)
{
printf("Funkcja recv() zakonczona bledem: %d\n",
WSAGetLastError());
break;
}
szBuff[ret] = '\0';
printf("ODBIOR: '%s'\n", szBuff);
//
// Odesłanie danych (jeżeli wybrano tę opcję).
//
if (!bRecvOnly)
{
nLeft = ret;
idx = 0;
//
// Sprawdzenie, czy zapisaliśmy wszystkie dane
//
while(nLeft > 0)
{
ret = send(sock, &szBuff[idx], nLeft, 0);
if (ret == 0)
break;
else if (ret == SOCKET_ERROR)
{
printf("Funkcja send() zakonczona"
" bledem: %d\n",
WSAGetLastError());
break;
}
nLeft -= ret;
idx += ret;
}
}
}
return 0;
}
//
// Funkcja: main
//
// Opis:
// Wątek główny. Inicjacja interfejsu Winsock, analiza
// argumentów wiersza poleceń, utworzenie nasłuchującego
// gniazda, związanie go z adresem lokalnym i oczekiwanie
// na połączenia klientów.
//
int main(int argc, char **argv)
{
WSADATA wsd;
SOCKET sListen,
sClient;
int iAddrSize;
HANDLE hThread;
DWORD dwThreadId;
struct sockaddr_in local,
client;
ValidateArgs(argc, argv);
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
printf("Nie mozna wczytac biblioteki Winsock!\n");
return 1;
}
// Utworzenie nasłuchującego gniazda
//
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (sListen == SOCKET_ERROR)
{
printf("Funckja socket() zakonczona bledem: %d\n",
WSAGetLastError());
return 1;
}
// Wybór lokalnego interfejsu, z którym zostanie
// związane gniazdo
//
if (bInterface)
{
local.sin_addr.s_addr = inet_addr(szAddress);
if (local.sin_addr.s_addr == INADDR_NONE)
usage();
}
else
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(iPort);
if (bind(sListen, (struct sockaddr *)&local,
sizeof(local)) == SOCKET_ERROR)
{
printf("Funkcja bind() zakonczona bledem: %d\n",
WSAGetLastError());
return 1;
}
listen(sListen, 8);
//
// Oczekiwanie na żądania klientów w ciągłej pętli. Po
// wykryciu próby połączenia, utworzenie wątku i przekazanie
// jego uchwytu.
//
while (1)
{
iAddrSize = sizeof(client);
sClient = accept(sListen, (struct sockaddr *)&client,
&iAddrSize);
if (sClient == INVALID_SOCKET)
{
printf("Funkcja accept() zakonczona bledem: %d\n",
WSAGetLastError());
break;
}
printf("Zaakceptowano klienta: %s:%d\n",
inet_ntoa(client.sin_addr), ntohs(client.sin_port));
hThread = CreateThread(NULL, 0, ClientThread,
(LPVOID)sClient, 0, &dwThreadId);
if (hThread == NULL)
{
printf("Funkcja CreateThread() zakończona błędem: %d\n",
GetLastError());
break;
}
CloseHandle(hThread);
}
closesocket(sListen);
WSACleanup();
return 0;
}
---------------------------------------------------------------------------------------------------------
CLIENT
// Program Client.c
//
// Opis:
// Przykładowa aplikacja klienta powtarzającego. Klient ten łączy
// się z serwerem TCP, wysyła dane i odczytuje dane odesłane
// przez serwer.
//
// Kompilacja:
// cl -o Client Client.c ws2_32.lib
//
// Opcje wiersza poleceń:
// client [-p:x] [-s:IP] [-n:x] [-o]
// -p:x Numer odległego portu
// -s:IP Adres IP serwera lub nazwa węzła
// -n:x Liczba wysyłanych wiadomości
// -o Tylko wysyłanie, bez odbioru
//
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_COUNT 3
#define DEFAULT_PORT 5250
#define DEFAULT_BUFFER 2048
#define DEFAULT_MESSAGE "To jest test klienta echa"
char szServer[128]="gandalf", // Serwer, z którym się łączymy
szMessage[1024]; // Wysyłana wiadomość
int iPort = DEFAULT_PORT; // Numer portu serwera
DWORD dwCount = DEFAULT_COUNT; // Ilość nadań wiadomości
BOOL bSendOnly = FALSE; // Tylko nadawanie, bez odbioru
//
// Funkcja: usage:
//
// Opis:
// Wydruk informacji o działaniu programu
//
void usage()
{
printf("uzycie: client [-p:x] [-s:IP] [-n:x] [-o]\n\n");
printf(" -p:x Numer odleglego portu\n");
printf(" -s:IP Adres IP serwera lub nazwa stacji\n");
printf(" -n:x Ilosc nadan wiadomosci\n");
printf(" -o Tylko nadawanie, bez odbioru\n");
ExitProcess(1);
}
//
// Funkcja: ValidateArgs
//
// Opis:
// Analiza argumentów wiersza poleceń i ustalenie wartości
// niektórych znaczników globalnych, decydujących o wykonaniu
// określonych czynności.
//
void ValidateArgs(int argc, char **argv)
{
int i;
for(i = 1; i < argc; i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (argv[i][1])
{
case 'p': // Odległy port
if (strlen(argv[i]) > 3)
iPort = atoi(&argv[i][3]);
break;
case 's': // Serwer
if (strlen(argv[i]) > 3)
strcpy(szServer, &argv[i][3]);
break;
case 'n': // Liczba nadań wiadomości
if (strlen(argv[i]) > 3)
dwCount = atol(&argv[i][3]);
break;
case 'o': // Tylko nadawanie, bez odbioru
bSendOnly = TRUE;
break;
default:
usage();
break;
}
}
}
}
//
// Funkcja: main
//
// Opis:
// Wątek główny. Inicjacja interfejsu Winsock, analiza
// argumentów wiersza poleceń, tworzenie gniazda, połączenie
// z serwerem, a następnie wysyłanie i odbieranie danych.
//
int main(int argc, char **argv)
{
WSADATA wsd;
SOCKET sClient;
char szBuffer[DEFAULT_BUFFER];
int ret,
i;
struct sockaddr_in server;
struct hostent *host = NULL;
// Analiza wiersza poleceń i wczytanie biblioteki Winsock
//
ValidateArgs(argc, argv);
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
printf("Nie mozna wczytac biblioteki Winsock!\n");
return 1;
}
strcpy(szMessage, DEFAULT_MESSAGE);
//
// Utworzenie gniazda i próba połączenia się z serwerem
//
sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sClient == INVALID_SOCKET)
{
printf("Funkcja socket() zakonczona bledem: %d\n",
WSAGetLastError());
return 1;
}
server.sin_family = AF_INET;
server.sin_port = htons(iPort);
server.sin_addr.s_addr = inet_addr(szServer);
//
// Jeżeli podany adres serwera nie ma postaci
// "aaa.bbb.ccc.ddd" jest to wówczas nazwa węzła,
// więc należy ją odwzorować
//
//
if (server.sin_addr.s_addr == INADDR_NONE)
{
host = gethostbyname(szServer);
if (host == NULL)
{
printf("Nie udalo sie znalezc"
" serwera: %s\n", szServer);
return 1;
}
CopyMemory(&server.sin_addr, host->h_addr_list[0],
host->h_length);
}
if (connect(sClient, (struct sockaddr *)&server,
sizeof(server)) == SOCKET_ERROR)
{
printf("Funkcja connect() zakonczona bledem: %d\n",
WSAGetLastError());
return 1;
}
// Wysyłanie i odbiór danych
//
for(i = 0; i < dwCount; i++)
{
ret = send(sClient, szMessage, strlen(szMessage), 0);
if (ret == 0)
break;
else if (ret == SOCKET_ERROR)
{
printf("Funkcja send() zakonczona bledem: %d\n",
WSAGetLastError());
break;
}
printf("Wyslano %d bajtow\n", ret);
if (!bSendOnly)
{
ret = recv(sClient, szBuffer, DEFAULT_BUFFER, 0);
if (ret == 0) // Zamknięcie uzgodnione
break;
else if (ret == SOCKET_ERROR)
{
printf("Funkcja recv() zakonczona bledem: %d\n",
WSAGetLastError());
break;
}
szBuffer[ret] = '\0';
printf("ODBIOR [%d bajtow]: '%s'\n", ret, szBuffer);
}
}
closesocket(sClient);
WSACleanup();
return 0;
}
Sieci komputerowe wykład 5.
1