08
Sep
2010

Introducere

In acest tutorial va voi prezenta API-ul Winsock (Windows Sockets). Acest API reprezinta setul de functii de baza pentru comunicarea in retea. O mare parte a tutorialului va fi prezentarea functiilor API. Exemplele din tutorial vor fi in Visual Basic 6 si C++. Programatorilor VB le recomand API Viewer 2004, un program cu care puteti vedea declaratiile functiilor din DLL-uri, constante, tipuri etc. O veste buna pentru ei: folosind ce veti invata in acest tutorial veti putea crea aplicatii Internet fara a mai avea nevoie de fisiere auxiliare ca mswinsck.ocx sau msinet.ocx. Pentru orice informatie referitoare la o functie sau un tip, o constanta vizitati MSDN. Recomand acest turorial celor obisnuiti cu folosirea functiilor API Windows, celor care stiu ce e o structura, care cunosc diferenta dintre transmiterea catre o functie a parametrilor prin valoare si transmiterea prin referinta, obisnuiti cu folosirea in mod frecvent a constantelor. Folosind un mediu ca VB6 sau C++, nu va fi nevoie de nimic pentru buna functionare a programului. Programul va functiona si pe XP si pe Vista, Windows 7 si e posibil sa ruleze foarte bine chiar si pe Windows 98. De exemplu prin folosirea unui mediu ca VB 2008 sau VC# 2008, programul va avea nevoie de platforma .NET Framework 2.0 pentru a rula.

Nu uitati ca functiile API pot fi folosite in orice limbaj de programare. Pentru programatorii C++ toate declaratiile se afla in Winsock2.h pentru versiunea 2.2 si winsock.h pentru versiunea 1.1, dar este recomandate folosirea ultimei versiuni. Pentru Visual Basic 6 vor trebui declarate toate manual.

O API (Application Programming Interface) este o interfata de programare a aplicatiilor, un grup de functii care pot fi folosite de programatori pentru a scrie programe. Prin anii 80, ARPA (Advanced Research Projects Agency) a oferit Universitatii Berkeley din California fonduri pentru a implementa protocoalele TCP/IP sub sistemul de operare UNIX. Interfata Winsocket este o API pentru retelele TCP/IP. Windows Sockets e o API bazata pe Berkeley Sockets. Momentan, la versiunea 2.2, vechile functii se pastreaza din motive de compatibilitate, sunt in continuare folosite.

Un socket este un terminal pentru comunicatiile in retea, o "componenta" a unei aplicatii prin care aplicatia trimite si receptioneaza date. Pentru o comunicatie in retea sunt necesare doua socket-uri care isi transfera date. De cele mai multe ori pentru comunicatii in retea se foloseste modelul client-server. Astfel, pentru un server: se creaza socketul, se "leaga" de o adresa apoi "asculta" pe un port cereri de conexiune de la clienti, si le accepta sau nu cand le primeste. Pentru un client: se creaza un socket, se conecteaza la server apoi incep sa se trimia/primeasca date. Dupa terminarea transferului de date se inchide conexiunea.

Comunicatia dintre doua socket-uri poate fi de doua feluri: comunictie orientata pe conexiune si comunicatie neorientata pe conexiune, pe baza de datagrame. La comunicatia orientata pe conexiune, trebuie sa existe o legatura logica intre socket-uri, o conexiune. La acest tip de conexiune se asigura ca datele ajung in aceeasi ordine in care au fost trimise, se verifica impotriva erorilor, fara interventia programatorului. O comunicatie neorientata pe conexiune este nesigura: datele pot ajunge in alta ordine sau nu pot ajunge deloc, fara sa se cunoasca acest lucru. Nu se realizeaza o corectie a erorilor. Pentru comunicatia orientata pe conexiune se foloseste protocolul TCP iar pentru comunicatia neorientata pe conexiune se foloseste protocolul UDP. Voi reveni cu detalii.

API-ul Winsock

Winsock API se afla in biblioteca wsock32.dll pentru Winsock 1.1 sau ws2_32.dll pentru Winsock 2.2. Versiunile Winsock de pana acum sunt: 1.0, 1.1, 2.0, 2.1, 2.2. Versiunea curenta de Winsock este 2.2, cea care va fi prezentata in acest tutorial.

Un lucru important la Winsock este ca functiile pot fi cu blocare sau fara blocare. O functie cu blocare impiedica programul sa apeleze orice alta functie Winsock pana cand termina operatiile de retea, se asteapta executarea functiei pentru a se trece mai departe. O parte din functiile specifice Windows sunt asincrone. Functiile asincrone sunt functii care nu produc o blocare. Ele returneaza de cele mai multe ori un handle de task, un identificator, si se efectueaza imediat, returnand handlerul, apoi, dupa ce efectueaza operatia trimit un mesaj handlerului unei ferestre pe care o primesc ca parametru impreuna cu mesajul pe care il trimit, astfel se stie cand functia a terminat operatia.

Numele functiilor specifice Windows incep cu WSA (Windows Sockets API). Mai jos sunt enumerate cele mai importante functii:

  • accept(): Permite o cerere de conexiune
  • AcceptEx(): Accepta o conexiune returnand adresa locala si adresa indepartata si primeste primul pachet trimis de client
  • bind(): Asociaza un "nume" unui socket
  • closesocket(): Inchide un socket
  • connect(): Realizeaza conexiunea unui socket
  • ConnectEx(): Conecteaza un socket si optional trimite date imediat dupa conexiune
  • DisconnectEx(): Inchide o conexiune a unui socket dar permite ca acesta sa poata fi folosit in continuare
  • EnumProtocols(): Obtine informatii despre un set de protocoale active
  • freeaddrinfo(): Elibereaza memoria pe care functia getaddrinfo o aloca unei structuri addrinfo
  • FreeAddrInfoEx(): Elibereaza memoria pe care functia GetAddrInfoEx o aloca unei structuri addrinfoex
  • FreeAddrInfoW(): Elibereaza memoria pe care functia GetAddrInfoW o aloca unei structuri addrinfoW
  • gai_strerror(): Este folosita pentru mesajele de eroare returnate de functia getaddrinfo
  • GetAcceptExSockaddrs(): Parseaza datele obtinute prin apelul functiei AcceptEx
  • GetAddressByName(): Interogheaza un namespace pentru a obtine informatii despre adresa pentru un serviciu specificat. Procesul e cunoscut ca Service Name Resolution. Un serviciu de retea se poate folosi de asemenea pentru a obtine adresa locala care poate fi folosita cu functia bind
  • getaddrinfo(): Ofera o traducere a unui hostname ANSI intr-o adresa
  • GetAddrInfoW(): Ofera o traducere a unui hostname Unicode la o adresa
  • gethostbyaddr(): Obtine informatiile referitoare la host pentru o adresa
  • gethostbyname(): Obtine informatiile referitoare la un host pentru un hostname dintr-o baza de date. Functia e veche, se recomanda folosirea functiei getaddrinfo in locul sau
  • gethostname(): Obtine hostname-ul pentru calculatorul local
  • GetNameByType(): Obtine numele unui serviciu de retea pentru un anumit tip de serviciu
  • getnameinfo(): Ofera Name Resolution de la o adresa IPv4 sau IPv6 la un hostname ANSI si de la numarul unui port la numele unui serviciu ANSI
  • GetNameInfoW(): Ofera Name Resolution de la o adresa IPv4 sau IPv6 la un hostname Unicode si de la numarul unui port la numele unui serviciu Unicode
  • getpeername(): Obtine adresa perechii la care este conectat un socket
  • getprotobyname(): Obtine informatii despre un protocol dupa nume
  • getprotobynumber(): Obtine informatii despre un protocol dupa un numar
  • getservbyname(): Obtine informatii despre un protocol dupa un nume si un protocol
  • getservbyport(): Obtine informatii despre un protocol dupa un port si un protocol
  • GetService(): Obtine informatii despre serviciul din contextul unui namespace
  • getsockname(): Obtine numele local pentru un socket
  • getsockopt(): Obtine optiunile unui socket
  • GetTypeByName(): Obtine GUID-ul (Globally Unique Identifier) tipului unui serviciu pentru un serviciu specificat prin nume
  • htonl(): Converteste un numar pe 32 de biti din formatul hostului in formatul retelei (byte-order, big-endian)
  • htons(): Converteste un numar pe 16 biti din formatul hostului in formatul retelei (byte-order, big-endian)
  • inet_addr(): Converteste un sir reprezentand o adresa IP in formatul retelei, format necesar pentru structura in_addr
  • inet_ntoa(): Converteste o adresa din formatul retelei in notatia cu punct
  • InetNtop(): Converteste o adresa IPv4 sau IPv6 intr-un sir in formatul standard. Versiunea ANSI a functiei este inet_ntop
  • InetPton(): Converteste o adresa IPv4 sau IPv6 din formatul standard text in formatul sau binar. Versiunea ANSI a functiei este inet_pton
  • ioctlsocket(): Controleaza modul de intrare/iesire al unui socket
  • listen(): Seteaza un socket in modul de ascultare
  • ntohl(): Converteste un numar pe 32 de biti din formatul retelei in formatul hostului (byte-order, care este little-endian pe procesoarele Intel)
  • ntohs(): Converteste un numar pe 16 biti din formatul retelei in formatul hostului (byte-order, care este little-endian pe procesoarele Intel)
  • recv(): Primeste date de la un socket conectat
  • recvfrom(): Primeste o datagrama si memoreaza adresa sursa
  • select(): Determina starea unuia sau a mai multor socket-uri
  • send(): Trimite date printr-un socket conectat
  • sendto(): Trimite date catre o destinatie specifica
  • SetAddrInfoEx(): Inregistreaza un nume, un serviciu si adresa specificata cu un namespace specificat
  • SetService(): Inregistreaza sau sterge din Registry un serviciu sau un tip de serviciu dintr-unul sau mai multe namespace-uri
  • setsockopt(): Seteaza optiuni pentru un socket
  • shutdown(): Opreste trimiterea si receptionarea datelor pentru un socket
  • socket(): Creaza un socket
  • TransmitFile(): Trimite un fisier printr-un socket conectat
  • TransmitPackets(): Trimite date printr-un socket conectat
  • WSAAccept(): Accepta o conexiune si permite transferul datelor
  • WSAAddressToString(): Converteste toate componentele unei structuri sockaddr intr-un sir usor de citit
  • WSAAsyncGetHostByAddr(): Obtine asincron informatii despre un host corespunzator unei adrese
  • WSAAsyncGetHostByName(): Obtine asincron informatii despre un host corespunzator unui nume
  • WSAAsyncGetProtoByName(): Obtine asincron informatii despre un protocol dupa nume
  • WSAAsyncGetProtoByNumber(): Obtine asincron informatii despre un protocol dupa un numar
  • WSAAsyncGetServByName(): Obtine asincron informatii despre un serviciu corespunzator unui nume si unui protocol
  • WSAAsyncGetServByPort(): Obtine asincron informatii despre un servici corespunzator unui port si unui protocol
  • WSAAsyncSelect(): Cere Windows-ului notificare bazata pe mesaje pentru un socket
  • WSACancelAsyncRequest(): Opreste o operatiune asincrona neterminata
  • WSACleanup(): Elibereaza resursele (ws2_32.dll)
  • WSAConnect(): Stabileste o conexiune cu un alt socket
  • WSAGetLastError(): Intoarce codul ultimei erori
  • WSAHtonl(): Converteste un unsigned long din formatul hostului in formatul retelei
  • WSAHtons(): Converteste un unsigned short din formatul hostului in formatul retelei
  • WSAIoctl(): Controleaza modul unui socket
  • WSANtohl(): Converteste un unsigned long din formatul retelei in formatul hostului
  • WSANtohs(): Converteste un unsigned short din formatul retelei in formatul hostului
  • WSARecv(): Primeste date de la un socket conectat
  • WSARecvDisconnect(): Primeste date apoi se deconcteaza daca socketul e unul orientat pe conexiune
  • WSARecvEx(): Primeste date de la un socket conectat
  • WSARecvFrom(): Primeste o datagrama si pastreaza adresa sursa
  • WSARecvMsg(): Primeste date de la un socket conectat sau neconectat
  • WSASend(): Trimite date printr-un socket conectat
  • WSASendDisconnect(): Trimite date si se deconecteaza
  • WSASendMsg(): Trimite date de la un socket conectat sau unul neconectat
  • WSASendTo(): Trimite date catre o destinatie
  • WSASetLastError(): Seteaza ultima eroare
  • WSASetSocketSecurity(): Controleaza securitatea unui socket
  • WSASocket(): Creaza un socket
  • WSAStartup(): Initializeaza Winsock DLL (ws2_32.dll)
  • WSAStringToAddress(): Converteste un sir numeric intr-o adresa sockaddr

Inainte de a apela majoritatea functiilor Winsock trebuie apelata functia WSAStartUp. Functia initializeaza Winsock DLL si ofera detalii despre versiunea Winsock existenta. Dupa terminarea operatiilor Winsock trebuie apelata functia WSACleanUp. WSACleanUp elibereaza resursele Winsock. O aplicatie poate apela de mai mute ori WSAStartUp, dar pentru fiecare apel al sau trebuie apelata WSACleanUp.

WSAStartUp necesita 2 parametri: primul, versiunea Winsock necesara pentru ca programul sa ruleze, iar al doilea, un pointer la o structura de tipul WSADATA. Functia este definita astfel in Visual Basic:

Private Declare Function WSAStartup Lib "ws2_32.dll" (ByVal wVersionRequired As Integer, ByRef lpWSAData As WSADATA) As Long
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData);

Structura WSADATA este definita astfel in C:

typedef struct WSAData {
  WORD wVersion;
  WORD wHighVersion;
  char szDescription[WSADESCRIPTION_LEN+1];
  char szSystemStatus[WSASYS_STATUS_LEN+1];
  unsigned short iMaxSockets;
  unsigned short iMaxUdpDg;
  char FAR *lpVendorInfo;
} WSADATA, 
 *LPWSADATA;

In Visual Basic ea trebui declarata in modul urmator:

Private Type WSADATA
	wVersion As Integer
	wHighVersion As Integer
	szDescription As String * WSADESCRIPTION_LEN
	szSystemStatus As String * WSASYS_STATUS_LEN
	iMaxSockets As Integer
	iMaxUdpDg As Integer
	lpVendorInfo As Long
End Type

Membrii acestei structuri sunt:

  • wVersion: Versiunea pe care se asteapta Winsock sa o primeasca , un numar pe 2 octeti
  • wHighVersion: Versiunea cea mai mare pe care o poate oferi implementarea Winsock
  • szDescription: Un string terminat in NULL in care Winsock copiaza o descriere a implementarii Winsock
  • szSystemStatus: Un string terminat in NULL in care Winsock copiaza un status relevant sau informatie de configurare
  • iMaxSockets: Numarul maxim de socketi care pot fi folositi. Acest parametru ar trebui sa fie ignorat versiunile Winsock 2.0 si mai mari. Este pastrat pentru compatibilitate cu Winsock 1.1
  • iMaxUdpDg: Marimea maxima a unui mesaj datagrama. La fel, ar trebui sa fie ignorat in versiunile 2.0 sau mai mari
  • lpVendorInfo: Un pointer catre informatia despre furnizor. La fel, ar trebui ignorat

Daca WSAStartup se incheie cu succes, returneaza 0. Daca nu, va returna una din valorile: WSASYSNOTREADY, WSAVERNOTSUPPORTED, WSAENETDOWN, WSAEINPROGRESS, WSAEPROCLIM, WSAEFAULT. Erorile sunt descrise in MSDN. Pentru Visual Basic 6, nu uitati sa le declarati explicit.

La sfarsit trebuie apelata functia WSACleanup. Daca WSACleanup se incheie cu succes returneaza 0, daca nu returneaza: WSANOTINITIALISED daca Winsock nu a fost initializat, adica daca nu a fost apelata functia WSAStartup, WSAENETDOWN daca e o problema de retea sau WSAEINPROGRESS daca o functie cu blocare nu s-a terminat. Pentru mesajele de eroare vedeti: "Mesaje de eroare". In Visual Basic ea trebui declarata astfel:

Private Declare Function WSACleanup Lib "ws2_32.dll" () As Long
int WSACleanup(void);

Exemple in C++ si VB

Un prim exemplu ne arata informatiile din structura WSAData. Pentru initializare vom cere versiunea 2.2 Winsock. Acest lucru il vom specifica in primul parametru al functiei WSAStartup. Acest parametru este un numar pe 2 bytes: primul byte reprezinta versiunea minora, al doilea reprezinta versiunea majora. Pentru acest lucru, in C++ ne puteam folosi de macro-urile: MAKEWORD care creaza un numar pe 2 bytes din 2 numere pe 1 byte, in cazul nostru 2 si 2, HIWORD care returneaza octetul cel mai semnificativ si LOWORD care returneaza octetul cel mai putin semnificativ. Pentru versiunea v2.2 putem cere si 0x202 (zecimal 514).

#include <stdio.h>
#include <winsock2.h>

int main()
{ 
    WSADATA wsaData; 
    int rezultat;  
    
    // Apelam functia     
    rezultat = WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    // Daca nu s-a efectuat cu succes, verificam eroarea care a intervenit    
    if(rezultat != 0) 
    {
    	if(rezultat == WSASYSNOTREADY) printf("A intervenit eroarea WSASYSNOTREADY \r\n");
    	else if(rezultat == WSAVERNOTSUPPORTED) printf("A intervenit eroarea WSAVERNOTSUPPORTED \r\n");
    	else if(rezultat == WSAEINPROGRESS) printf("A intervenit eroarea WSAEINPROGRESS \r\n");
    	else if(rezultat == WSAEPROCLIM) printf("A intervenit eroarea WSAEPROCLIM \r\n");
    	else if(rezultat == WSAEFAULT) printf("A intervenit eroarea WSAEFAULT \r\n");    	
    }    
    // Daca s-a efectuat cu succes, afisam informatile oferite    
    else
    {
        printf("Versiune asteptata: %d.%d\n", HIBYTE(wsaData.wVersion), LOBYTE(wsaData.wVersion));
        printf("Versiune maxima: %d.%d\n", HIBYTE(wsaData.wHighVersion), LOBYTE(wsaData.wHighVersion));
        printf("Descriere: %s\n", wsaData.szDescription);
        printf("Status: %s\n", wsaData.szSystemStatus);

        // Ignorate in Winsock2
        // printf("Nr. max. de socketi: %d\n", wsaData.iMaxSockets);
        // printf("Datagrama maxima: %d\n", wsaData.iMaxUdpDg);
        // printf("Furnizor: %s\n", wsaData.lpVendorInfo);
    }

    // Eliberam resursele    
    WSACleanup();

    return 0;  
}

Pentru Visual Basic va trebui sa definim niste functii care returneaza HIBYTE si LOBYTE, iar pentru versiune vom folosi o constanta cu valoarea 514 sau &H202 in hexazecimal.

Private Const VERSIUNE As Long = &H202

Vom defini functiile astfel:

Private Function LoByte(ByVal numar As Integer) As Byte
LoByte = numar And &HFF
End Function

Private Function HiByte(ByVal numar As Integer) As Byte
HiByte = (numar And &HFF00) / 256
End Function

De asemenea vom mai folosi o functie pentru string-uri de lungime fixa. De multe ori este necesar sa folosim string-uri de lungime fixa cand folosim API-uri deoarece acestea nu inteleg practic string-urile normale din VB6. Un string de lungime fixa, are o lungime fixa, pe care nu o poate depasi. Cand este transmis prin referinta catre o functie API, aceasta scrie datele in string, iar restul caracterelor pana la sfarsit sunt caractere NULL (vbNullChar). Pentru a nu avea probleme cu adaugarea sau afisarea datelor dupa string-ul nostru de lungime fixa va trebui sa scapam de acele caractere null, sa pastram doar textul nostru. Vom face acest lucru astfel:

Private Function TrimNull(ByVal sir As String) As String
TrimNull = Left(sir, InStr(1, sir, vbNullChar) - 1)
End Function

Functia este simpla, la fel ca si ideea. Cautam pozitia primului vbNullChar (caracter null) si returnam textul pana la acel caracter. Gasim primul null folosind functia InStr care returneaza positia acestuia. Apoi copiem textul din stanga pana la acea pozitie - 1 ca sa nu copiem si nullul. Astfel, nu vom avea probleme cand vom incerca de exemplu sa afisam un text dupa un string:

MsgBox "Test: " & string_lungime_fixa_cu_null & "zZzZz"

In acest caz, daca stringul nostru are NULL-uri la sfarsit, daca nu am scapat de ele, textul "zZzZz" nu va mai aparea in fereastra de mesaj, insa daca vom scapa de NULL-uri nu vom avea probleme.

Si in final, sa facem acelasi lucru ca si in C++, sa afisam cateva detalii continute de structura wsaData in VB6. Dupa cum observati in VB6 este ceva mai complicat: trebuie sa declaram API-urile, structura wsaData si constantele. De asemenea trebuie sa definim functiile de mai sus:

' Functiile API necesare

Private Declare Function WSAStartup Lib "ws2_32.dll" (ByVal wVersionRequired As Integer, ByRef lpWSAData As wsaData) As Long
Private Declare Function WSACleanup Lib "ws2_32.dll" () As Long

' Constante necesare

Private Const WSADESCRIPTION_LEN As Long = 256
Private Const WSASYS_STATUS_LEN As Long = 128
Private Const WSASYSNOTREADY As Long = 10091&
Private Const WSAVERNOTSUPPORTED As Long = 10092&
Private Const WSAEINPROGRESS As Long = 10036&
Private Const WSAEPROCLIM As Long = 10067&
Private Const WSAEFAULT As Long = 10014&

' Structura wsaData

Private Type wsaData
    wVersion As Integer
    wHighVersion As Integer
    szDescription As String * WSADESCRIPTION_LEN
    szSystemStatus As String * WSASYS_STATUS_LEN
    iMaxSockets As Integer
    iMaxUdpDg As Integer
    lpVendorInfo As Long
End Type

' Versiunea 2.2

Private Const VERSIUNE As Long = &H202

' Functiile pentru operatile la nivel de bit

Private Function LoByte(ByVal numar As Integer) As Byte
LoByte = numar And &HFF
End Function

Private Function HiByte(ByVal numar As Integer) As Byte
HiByte = (numar And &HFF00) / 256
End Function

' Pentru a preveni anumite erori

Private Function TrimNull(ByVal sir As String) As String
TrimNull = Left(sir, InStr(1, sir, vbNullChar) - 1)
End Function

Private Sub Form_Load()

Dim wsa As wsaData
Dim rezultat As Integer
Dim mesaj As String

' Apelam functia

rezultat = WSAStartup(VERSIUNE, wsa)

' Daca nu s-a efectuat cu succes, verificam eroarea care a intervenit

If rezultat <> 0 Then
    If rezultat = WSASYSNOTREADY Then MsgBox ("A intervenit eroarea WSASYSNOTREADY")
    ElseIf rezultat = WSAVERNOTSUPPORTED Then MsgBox ("A intervenit eroarea WSAVERNOTSUPPORTED")
    ElseIf rezultat = WSAEINPROGRESS Then MsgBox ("A intervenit eroarea WSAEINPROGRESS")
    ElseIf rezultat = WSAEPROCLIM Then MsgBox ("A intervenit eroarea WSAEPROCLIM")
    ElseIf rezultat = WSAEFAULT Then MsgBox ("A intervenit eroarea WSAEFAULT")
    
    ' Eliberam resursele
    
    WSACleanup
Else

    ' Daca s-a efectuat cu succes, afisam informatile oferite
    
    mesaj = "Versiune asteptata: " & HiByte(wsa.wVersion) & "." & LoByte(wsa.wVersion) & vbCrLf
    mesaj = mesaj & "Versiune maxima: " & HiByte(wsa.wHighVersion) & "." & LoByte(wsa.wHighVersion) & vbCrLf
    mesaj = mesaj & "Descriere:" & TrimNull(wsa.szDescription) & vbCrLf
    mesaj = mesaj & "Status: " & TrimNull(wsa.szSystemStatus) & vbCrLf
    
    ' Ignorate in Winsock 2
    ' mesaj = mesaj & "Nr. max. de socketi: " & wsa.iMaxSockets & vbCrLF
    ' mesaj = mesaj & "Datagrama maxima: " & wsa.iMaxUdpDg & vbCrLF
    ' mesaj = mesaj & "Furnizor: " & wsa.lpVendorInfo & vbCrLF
    
    MsgBox mesaj

    WSACleanup

End If

End Sub

Crearea unui socket

Putem crea in 2 moduri un socket: folosind functia socket() sau folosind functia specifica Winsock, WSASocket().

Functia socket are nevoie de 3 parametri: familia de adrese (af), tipul socketului (lType) si protocolul utilizat de socket (protocol). Prototipul functiei este:

SOCKET WSAAPI socket( int af, int type, int protocol);

In VB o vom declara astfel:

Private Declare Function socket Lib "ws2_32.dll" (ByVal af As Long, ByVal lType As Long, ByVal protocol As Long) As Long

Familia de adrese poate lua urmatoarele valori:

  • AF_UNSPEC: Familie de adrese nespecificata
  • AF_INET: Familia de adrese IPv4
  • AF_INET6: Familia de adrese IPv6
  • AF_BTH/AF_IRDA: Familia de adrese Bluetooth/IrDa, e necesar adaptor si driver instalat (informativ)

De fapt poate lua mai multe valori, dar nu ne intereseaza. Pe noi ne intereseaza decat valorile AF_INET pentru IPv4 si AF_INET6 pentru IPv6.

Tipul socketului poate fi:

  • SOCK_STREAM: Socket bazat pe conexiune. Foloseste protocolul TCP si familia de adrese AF_INET sau AF_INET6
  • SOCK_DGRAM: Socket fara conexiune, bazat pe datagrame. Foloseste UDP si familia de adrese AF_INET sau AF_INET6
  • SOCK_RAW: Socket brut care permite aplicatiei accesul la protocoale inferioare: IP, ICMP, mai exact la headerele acestora

Protocolul poate fi:

  • IPPROTO_TCP: Transmission Control Protocol, este permins cand familia de adrese este AF_INET sau AF_INET 6 si tipul este SOCK_STREAM
  • IPPROTO_UDP: User Datagram Protocol, este permins cand familia de adrese este AF_INET sau AF_INET 6 si tipul este SOCK_DGRAM
  • IPPROTO_IP: Pentru acces la protocolul IP
  • IPPROTO_IPV6: Pentru acces la protocolul IPv6

Daca se foloseste 0 pentru aceasta valoare se va selecta automat un protocol. Daca familia de adrese este AF_INET sau AF_INET6 si tipul socketului este SOCK_RAW, protocolul va aparea in headerul pachetelor IP sau IPv6.

Exemple de folosire a functiei:

// Pentru un socket orientat pe conexiune
socket_handle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 

// Pentru un socket orientat pe datagrame
socket_handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

Asa se creaza de cele mai multe ori un socket. Primul exemplu pentru protocolul TCP, iar al doilea pentru UDP. Diferenta dintre ele este ca pentru un socket orientat pe conexiune, pentru transferul datelor va trebui mai intai realizata conexiunea logica intre client si server. Dar astfel se va putea cunoaste aparitia unei erori, cum ar fi pierderea conexiunii, sau vom putea sti daca datele nu au ajuns la destinatie. Singurul dezavantaj, destul de neglijabil daca nu e vorba de cantitati mari de date trimise, ar fi faptul ca headerele TCP sunt mai mari decat cele UDP.

Daca functia se incheie cu succes returneaza un handle de socket, daca nu, returneaza INVALID_SOCKET, iar codul de eroare specific poate fi gasit cu WSAGetLastError() care returneaza codul de eroare al ultimei erori aparute.

In C++ putem folosi tipul SOCKET care este de fapt unsigned int. Pentru Visual Basic 6 putem folosi Long. Practic acest handle de socket este un numar unic prin care socketul e recunoscut.

#include <stdio.h>
#include <winsock2.h>

int main()
{ 
    WSADATA wsaData;
    SOCKET hSock;
    
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    hSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    printf("Handlerul socketului: %d\r\n", hSock);
    
    WSACleanup();
    
    return 0;  
}

Si in Visual Basic 6 exemplul arata cam asa:

' Functiile pe care le vom apela

Private Declare Function WSAStartup Lib "ws2_32.dll" (ByVal wVersionRequired As Integer, ByRef lpWSAData As wsaData) As Long
Private Declare Function WSACleanup Lib "ws2_32.dll" () As Long
Private Declare Function socket Lib "ws2_32.dll" (ByVal af As Long, ByVal lType As Long, ByVal protocol As Long) As Long

' Constantele necesare

Private Const WSADESCRIPTION_LEN As Long = 256
Private Const WSASYS_STATUS_LEN As Long = 128

' Structura wsaData

Private Type wsaData
    wVersion As Integer
    wHighVersion As Integer
    szDescription As String * WSADESCRIPTION_LEN
    szSystemStatus As String * WSASYS_STATUS_LEN
    iMaxSockets As Integer
    iMaxUdpDg As Integer
    lpVendorInfo As Long
End Type

' Versiunea 2.2

Private Const VERSIUNE As Long = &H202

' Doar constantele pe care le folosim in acest exemplu
' Pentru o lista completa cu constante folositi API Viewer 2004
' Daca nu gasiti o constanta o cautati in winsock2.h sau ws2def.h pentru SDK mai nou

Private Const AF_INET As Long = 2
Private Const SOCK_STREAM As Long = 1
Private Const IPPROTO_TCP As Long = 6

Private Sub Form_Load()

Dim wsa As wsaData
Dim hSock As Long

WSAStartup VERSIUNE, wsa

' Cream socketul

hSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)

MsgBox "Handlerul socketului: " & hSock

WSACleanup

End Sub

Pentru a crea un socket putem folosi de asemenea functia specifica Winsock WSASocket(). Functia WSASocket are 6 parametri: familia de adrese (af), tipul (type), protocolul (protocol), un pointer la o structura WSAPROTOCOL_INFO care defineste caracteristicile socketului care va fi creat (lpProtocolInfo) (in VB6, ca sa nu mai declaram structura, vom folosi tipul Long si valoarea 0 pentru acest parametru in VB6 - functia necesita de fapt un pointer care e o de fapt o adresa de memorie pe 4 octeti, deci putem face asta), un parametru rezervat (g) si un parametru flag care specifica un atribut pentru socket (dwFlags). Primii 3 parametri sunt identici cu cei ai functiei socket. Daca este specificata o structura WSAPROTOCOL_INFO pentru al 4-lea parametru, se vor folosi datele din structura pentru a defini socketul. Pentru a crea un socket astfel, in VB6 vom folosi:

Private Declare Function WSASocket Lib "ws2_32.dll" Alias "WSASocketW" (ByVal af As Long, ByVal lType As Long, ByVal protocol As Long, ByVal lpProtocolInfo As Long, ByVal g As Long, ByVal dwFlags As Long) As Long

Atentie, functia practic nu e corecta. Parametrul lpProtocolInfo ar trebui sa fie de tipul LPWSAPROTOCOL_INFO, iar parametrul g de tipul GROUP. Si ambii ar trebuii transmisi prin referinta. Insa nu ii vom folosim, si pentru a nu avea probleme ii vom transmite prin valoare, si ii vom declara de tipul Long (VB6), ceea ce practic nu este corect, dar asa nu mai e nevoie sa declaram acele structuri.

Prototipul functiei este:

SOCKET WSASocket(int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags);

Exemplu in C++:

socket_handle = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);

Daca functia se incheie cu succes va returna un handle de socket. Daca nu se incheie cu succes va returna INVALID_SOCKET.

Parametrul dwFlags poate lua mai multe valori, de exemplu poate lua valoarea WSA_FLAG_OVERLAPPED. Astfel, pentru socketul creat se vor putea folosi functiile WSASend, WSASendTo, WSARecv, WSARecvFrom, and WSAIoctl pentru mai multe operatii simultane. Mai poate lua si alte valori, dar doar pentru multicast, nu ne intereseaza.

De asemenea, pentru fiecare socket creat vor trebui eliberate la final resursele. Vom face acest lucru folosind functia closesocket(). Functia elibereaza resursele, si daca o face cu succes returneaza 0, in caz de eroare returneaza SOCKET_ERROR. Prototipurile in C si VB sunt:

int closesocket(SOCKET s);
Private Declare Function closesocket Lib "ws2_32.dll" (ByVal s As Long) As Long

Un exemplu in C++:

#include <stdio.h>
#include <winsock2.h>

int main()
{ 
    WSADATA wsaData;
    SOCKET hSock;
    
    WSAStartup(MAKEWORD(2, 2), &wsaData);
	
    // Cream socketul

    hSock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    
    printf("Handlerul socketului: %d\r\n", hSock);
	
    // Il distrugem
	
    closesocket(hSock);
    
    WSACleanup();
    
    return 0;  
}

Pentru VB6 va trebui sa declaram in plus functiile WSASocket si closesocket. Restul e la fel.

Private Declare Function WSAStartup Lib "ws2_32.dll" (ByVal wVersionRequired As Integer, ByRef lpWSAData As wsaData) As Long
Private Declare Function WSACleanup Lib "ws2_32.dll" () As Long
Private Declare Function WSASocket Lib "ws2_32.dll" Alias "WSASocketW" (ByVal af As Long, ByVal lType As Long, ByVal protocol As Long, ByVal lpProtocolInfo As Long, ByVal g As Long, ByVal dwFlags As Long) As Long
Private Declare Function closesocket Lib "ws2_32.dll" (ByVal s As Long) As Long

Private Const WSADESCRIPTION_LEN As Long = 256
Private Const WSASYS_STATUS_LEN As Long = 128

Private Type wsaData
    wVersion As Integer
    wHighVersion As Integer
    szDescription As String * WSADESCRIPTION_LEN
    szSystemStatus As String * WSASYS_STATUS_LEN
    iMaxSockets As Integer
    iMaxUdpDg As Integer
    lpVendorInfo As Long
End Type

Private Const VERSIUNE As Long = &H202
Private Const AF_INET As Long = 2
Private Const SOCK_STREAM As Long = 1
Private Const IPPROTO_TCP As Long = 6
Private Const WSA_FLAG_OVERLAPPED As Long = &H1

Private Sub Form_Load()

Dim wsa As wsaData
Dim hSock As Long

WSAStartup VERSIUNE, wsa

' Cream socketul

hSock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED)

MsgBox "Handlerul socketului: " & hSock

' Il distrugem

closesocket hSock

WSACleanup

End Sub

Este recomandata folosirea flagului WSA_FLAG_OVERLAPPED. Acest flag e setat automat daca socketul este creat cu functia socket(), insa pentru functia WSASocket() acesta trebuie specificat explicit. Daca nu se foloseste, socketul este unul cu blocare si daca nu este specificat nu putem seta un timeout pentru operatiile de trimitere/primire date. De exemplu, daca socketul este cu blocare, functia recv() nu returneaza nici o valoare pana cand nu incheie primirea datelor. Acest lucru, dintr-o problema sau alta poate dura destul de mult, dar daca folosim acest flag putem seta un timeout. Alte functii care pot provoca astfel de probleme sunt: recvfrom, send, sendto.

Mesaje de eroare (cele care pot sa apara folosind functiile de mai sus):

  • WSANOTINITIALISED : Nu a fost initializat Winsock (functia WSAStartUp nu a fost apelata sau nu s-a terminat cu succes)
  • WSAENETDOWN : Este o problema la retea (conexiune)
  • WSAEFAULT : Apare cand se foloseste un pointer invalid sau un buffer prea mic
  • WSAEINPROGRESS : O operatie cu blocare este in curs de desfasurare
  • WSAEINVAL : Apare cand se specifica un argument invalid
  • WSAENOBUFS : Bufferul nu este destul de mare
  • WSASYSNOTREADY : Apare la initializare cand sunt probleme: nu exista anumite componente sau se folosesc mai multe implementari odata
  • WSAVERNOTSUPPORTED : Versiunea nu este suportata
  • WSAEPROCLIM : Winsock are o limita referitoare la cate procese folosesc. Eroarea apare cand sunt prea multe astfel de procese