Warsztat.GDCompo!ProjektyMediaArtykułyQ&AForumOferty pracyPobieranie

Opisz napotkaną sytuację, a redakcja niezwłocznie znajdzie rozwiązanie!

wyślij anuluj

Podstawy WinSock

Uwaga! Tekst posiada 3 niepotwierdzonych zmian!

Tekst został importowany z Warsztatowych artykułów. Jego oryginalnym autorem jest Kamil 'get' Sienkiewicz. Jeżeli został importowany poprawnie, usuń ten szablon!

{{Opis|}}

Wstęp

Napewno kiedyś pisząc jakąś grę czy też program brakowało Ci komunikacji ze światem przez internet, umożliwia to tytułowa biblioteka – WinSock. Jej podstawy znajdziesz w tym artykule (zastosowanie synchroniczne).

Mamy tu tzw. klienta i serwer, każdy z nich posiada swój adres IP (np. 127.0.0.1 – adres lokalny, 89.xx.xxx.xx – jakiś adres zewnętrzny). Będziemy używali protokołu TCP/IP więc potrzebny będzie nam tzw. port, będący liczbą od 0 do 65 535 (int). Służą one pomocą systemowi informując gdzie chcemy się podłączyć, na komputerze podłączonym do internetu może być otwartych wiele portów. Przykładowo przeglądarki HTTP łączą się domyślnie z portem 80, klient IRC ma inny port, a FTP jeszcze inny. Porty poniżej 1024 są dla procesów „wyższych celów” (np. serwery HTTP). Najbezpieczniej więc używać portów od 1024 do 65 535.

Inicjalizacja WinSock

Na początek uruchom opcje projektu i w ustawieniach linkera dodaj WS2_32.lib, następnie w swoim kodzie dodaj nagłówek winsock2.h. Teraz możemy się zabrać za pisanie odpowiedniego kodu.

Musimy uruchomić coś takiego jak WSA, to jest składnik systemu pozwalający na komunikację z innymi komputerami. Robimy to tak:

WORD version = MAKEWORD(2,2); // używamy wersji 2.2 WSADATA wsaData; // nasze WSA if(!WSAStartup(version, &wsaData)) // tworzymy WSA { // mamy błąd, WSA nie chce sie zainicjować cout << "Błąd inicjalizacji WSA" << endl; WSACleanup(); // usuwamy WSA return 1; } if( LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2 ) { cout << "Nieprawidłowa wersja" << endl; WSACleanup(); // usuwamy WSA return 1; }

Nic chyba nie trzeba tu wyjaśniać, oprócz makr bo możesz ich nie znać. Makro MAKEWORD tworzy zmienną typu word, o niskich bitach o wartości pierwszego argumentu, a wysokich bitach o wartości drugiego argumentu. LOBYTE jak się pewnie domyślasz zwraca niskie bity zmiennej podanej w argumencie, a HIBYTE – wysokie. Jeśli coś pójdzie nie tak, kończymy działanie aplikacji (w tym wypadku).

Serwer

Serwer powinien wykonywać następujące czynności:
- nasłuchiwać na danym porcie
- odbierać komunikaty
- odsyłać komunikaty

Zaczniemy od tego pierwszego, czyli nasłuchiwania. Żeby serwer mógł nasłuchiwać trzeba mu dorobić takie „ucho” którym jest SOCKET:

SOCKET sListen, // nasze "ucho" sClient; // nasz klient // tworzymy nasze "ucho" sListen = socket( AF_INET, SOCK_STREAM, IPPROTO_IP); if( sListen == SOCKET_ERROR ) // sprawdzamy czy sie udało { cout << "Nie mozna stworzyc ucha" << endl; WSACleanup(); return 1; }

No i mamy. Stała AF_INET oznacza to, że korzystamy z protokołu TCP/IP na zasadzie połączenia strumieniowego(SOCK_STREAM), z dodatkową flagą IPPROTO_IP. Teraz musimy mu tylko dać zmysł słuchu, ale to za chwilę, najpierw powiemy naszemu serwerowi – gdzie słuchać.

sockaddr_in local, // ustawienia serwera int port = 6789; // nasz przykładowy port memset(&local, 0, sizeof(local)); // czyścimy strukturę local.sin_addr.s_addr = htonl(INADDR_ANY); // wszyscy mogą się z nami połączyć local.sin_family = AF_INET; // korzystamy z TCP/IP local.sin_port = htons((u_short)port); // wybieramy port if (bind(sListen, (sockaddr*)&local, sizeof(local)) == SOCKET_ERROR) { cout << "Bind zakonczony porazka" << endl; closesocket(sListen) // nowość zamykamy socketa WSACleanup(); return 1; }

Pojawiła się nowa struktura i cztery funkcje. Struktura ta, przechowuje parametry naszego socketa, ustawiamy co trzeba i zostawiamy resztę tak jak jest (czyli 0). Funkcja bind przypisuje te ustawienia dla konktretnego socketa, a closesocket zamyka socket’a, htons zmienia port na postać przystępną dla komputera, a htonl robi to samo tyle, że z IP.

A nasz serwer jest nadal głuchy, trzeba mu dać zmysł słuchu! No to do dzieła:

if(listen(sListen, SOMAXCONN) == SOCKET_ERROR) // dajemy słuch { cout << "Operacja dodania słuchu nieudana" << endl; closesocket(sListen); WSACleanup(); return 1; }

No i nasz serwer przestał być głuchy, teraz słucha tak jak programista przykazał. Trzeba wyjaśnić funkcję listen, pierwszy parametr to jak się pewnie domyślasz nasz socket, zaś drugi parametr to liczba maksymalnych połączeń. Użyłem tutaj stałej SOMAXCONN oznaczającą domyślną ilość maksymalnych połączeń.

Dobrze, teraz załóżmy, że ktoś chce się połączyć – co wtedy zrobić? Ano akceptować połączenie:

sockaddr_in client; sClient = accept (sListen, (sockaddr*)&client, (int) sizeof(client)); if(sClient == INVALID_SOCKET) { cout << "Niepoprawne połączenie!" << endl; closesocket(sClient); closesocket(sListen); WSACleanup(); return 1; }

I znowu nowa funkcja – accept. Oczekuje ona na połączenie klienta, czyli nie pójdzie dalej, jeśli nikt się nie połączy – stoimy w miejscu. A sama funckja zwraca nam socket klienta.

No i tak dochodzimy powoli do końca serwera – zostało nam tylko wysyłanie i odbieranie danych. Zaczniemy od wysyłania.

string welcome = "Witam na naszym serwerze!"; send(sClient, welcome.c_str(), welcome.length()+1, 0);

I to już wszystko co związanie z wysyłaniem! Omówmy teraz poszczególne parametry funkcji send, pierwszy oznacza cel podróży wiadomości, czyli klienta – wysyłanie do siebie nie ma większego sensu. Drugi parametr to wskaźnik na element tablicy char, trzeci oznacza długość tej tablicy. Czwartego parametru nie używamy, są to dodatkowe flagi. Funkcja ta zwraca także ilość wysłanych danych.

A jak odebrać wiadomość? Również proste zadanie:

const int MAX_BUFFER = 2048; char buffer[MAX_BUFFER]; recv(sClient, buffer, MAX_BUFFER, 0);

No i mamy! Omówmy funkcję recv chociaż domyślasz się co każdy parametr oznacza. Pierwszy to miejsce z którego pobieramy wiadomość, drugi oznacza wskaźnik na element tablicy, trzeci oznacza maksymalną wielkość odebranej wiadomości, a czwarty dodatkowe pole. Funkcja zwraca ilość odebranych danych. Aha, ta funkcja wstrzymuje program aż coś do niej przyjdzie.

No i to chyba już wszystko co dotyczy serwera. Nie dużo, prawda?

Klient

Teraz przydałoby się napisać tzw. klienta. Po co komu serwer, jeśli nic nie może się do niego podłączyć?

Tworzymy naszego socket’a identycznie jak w przypadku serwera czyli:

SOCKET sClient; sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if(sClient == INVALID_SOCKET) { cout << "Nie mozna stworzyc socketa" << endl; WSACleanup(); return 1; }

No i mamy nasz socket. Teraz sprawa zaczyna wyglądać inaczej, teraz już inaczej musimy wypełnić naszą strukturę oraz znika funkcja accept, a przychodzi connect.

hostent *host = 0; char szServer[64] = "127.0.0.1"; // IP serwera server.sin_family = AF_INET; server.sin_port = htons((u_short)iPort); server.sin_addr.s_addr = inet_addr(szServer); // jeżeli to nie IP, a nazwa węzła if (server.sin_addr.s_addr == INADDR_NONE) { host = gethostbyname(szServer); if (host == NULL) { cout << "Nie moge skonwertowac adresu:" << szServer << endl; closesocket(sClient); WSACleanup(); return 1; } CopyMemory(&server.sin_addr, host->h_addr_list[0], host->h_length); }

Pojawia się tu parę nowych rzeczy, czyli dwie nowe funkcje: inet_addr parametr w postaci tekstu zmienia na postać IP, dostępną dla komputera, funkcja gethostbyname zmienia nazwę serwera (np. localhost) na IP.

No i pozostaje nam tylko podłączyć się do serwera. Jest to bardzo proste:

if( connect(sClient, (sockaddr*)&server, sizeof(server)) == SOCKET_ERROR ) { cout << "Nie można się połączyć" << endl; closesocket(sClient); WSACleanup(); return 1; }

To wszystko. Już możesz się komunikować z serwerem.

Uzyskiwanie numeru błędu

A co jeśli coś pójdzie nie tak? Jak sprawdzić co się stało? Można w prosty sposób zdobyć numer błędu i za pomocą niego identyfikować rodzaj błędu. Służy do tego funkcja WSAGetLastError, zwracająca ostatni błąd. Funkcja nie przyjmuje argumentów.

Zakończenie

No i tak zakończyliśmy mój nudny kurs WinSock. Zostawiam wam parę przykładów aplikacji używających WinSock. Zapraszam do eksperymentowania z tą biblioteką, pisania aplikacji sieciowych, gdyż w przypadku gier wydłuża to ich żywotność – zawsze to lepiej pograć z człowiekiem, niż z komputerem.

files/articles/Podstawy_WinSock/Przykłady.rar

Tekst dodał:
Kamil Marcin Sienkiewicz
27.05.2008 18:46

Ostatnia edycja:
Kamil Marcin Sienkiewicz
27.05.2008 18:46
(+ 3 niepotwierdzonych zmian)

Kategorie:

Aby edytować tekst, musisz się zalogować.

# Edytuj Porównaj Czas Autor Rozmiar
#4 edytuj (poprz.) 19.02.2016 13:57 Kamil Marcin Sienkiewicz 11.42 KB (+55)
#3 edytuj (poprz.) (bież.) 11.11.2014 21:04 Kamil Łapiński 11.37 KB (+1.97 KB)
#2 edytuj (poprz.) (bież.) 04.05.2012 21:09 Mike148 9.4 KB (+18)
#1 edytuj (bież.) 27.05.2008 18:46 Kamil Marcin Sienkiewicz 9.38 KB
Zwykły
Do sprawdzenia
Do akceptacji
  • Xion (@Xion) 27 maja 2008 19:48
    Można by jeszcze wspomnieć o socketach UDP. Ale na początek to całkiem niezły tekst.
  • ~Slayer 27 maja 2008 20:53
    Szkoda że nie można pobrać Przykładów :(
  • Kamil Marcin Sienkiewicz (@gecio) 27 maja 2008 22:07
    Przykłady tymczasowo można pobrać tu: http://www.speedyshare.com/791664863.html .
  • Kosai_ (@Kosai) 27 maja 2008 22:15
    bardzo mi się art przyda. dzięki! :)
  • ~now 28 maja 2008 10:45
    Fajnie, ale czekam na artykul o polaczeniach asynchronicznych. Ten sie za bardzo nie nadaje do gier (moze do jakichs turowek, ale i tak nie za bardzo, bo jak sie komus czas skonczy na ruch, to co wtedy?).
  • ~ktos 28 maja 2008 12:34
    A moze tych przykladow nie da sie pobrac bo maja polska litere w nazwie pliku.
  • Michał Korman (@dynax) 03 czerwca 2008 13:58
    Bardzo przydatny artykuł. Dzięki get :)
  • Michał Korman (@dynax) 05 czerwca 2008 14:45
    eee... wszystko dobrze się kompiluje ale po uruchomieniu wyskakuje "Błąd inicjalizacji WSA".
  • ~RedHot 26 czerwca 2008 15:31
    Beznadziejny tutorial, błędy w kodzie. Nie omówione ważne problemy, które było by dobrze zawrzeć, a tłumaczenie też do bani. Żałuję, że od tego zacząłem przygodę z WinSock, ale na szczęśćie są inne źródła. P.S Ewidentnie widać pracę na siłę.
  • bs.mechanik (@bsmechanik) 09 sierpnia 2008 21:37
    Hmmm.. No art calkiem fajny, tylko jakos podobny do tego na winapi.org :PP
  • Kamil Marcin Sienkiewicz (@gecio) 10 sierpnia 2008 18:05
    Lol! Dzięki, śmierdzi tutaj plagiatem! (ze strony winapi.org)
  • Kamil Marcin Sienkiewicz (@gecio) 10 sierpnia 2008 18:09
    A przepraszam, spojrzałem na datę, mój artykuł ukazał się pózniej. No nic, no i tamten artykuł mówi o połączeniach asynchronicznych :).
  • ~LittleMan 31 sierpnia 2008 13:20
    moglby ktos reupnac przyklady ? zaden link nie dziala
  • ~kacperz1 02 września 2008 18:18
    Dynax: Zmień wersję winsocket'a w kodzie z 2,2 na 2,0 i będzie dobrze.
  • Michał Korman (@dynax) 06 października 2008 17:06
    if(!WSAStartup(version, &wsaData)) { // mamy błąd }

    Cytat z MSDN: "If successful, the WSAStartup function returns zero"
  • Kamil Marcin Sienkiewicz (@gecio) 06 października 2008 20:01
    No dokładnie. Dokładnie tak samo jest w tutku :). Sprawdź wersje WSA, jeśli 2.2 nie działa ustaw 2.0 tak jak mowi kacperz1.
  • ~glub 18 października 2008 20:34
    dlaczego poczas kompilacji wywala jakies dziwne błedy??
  • ~glub 18 października 2008 20:36
    a mianowicie takie [Linker error] undefined reference to [email protected]' [Linker error] undefined reference to [email protected]' [Linker error] undefined reference to [email protected]' [Linker error] undefined reference to [email protected]'
  • Kamil Marcin Sienkiewicz (@gecio) 23 października 2008 19:38
    Nie podlinkowales libow (WS2_32.lib)... Prawie na samym poczatku artykulu jest napisane ze trzeba podlinkowac :-).
  • ~HNO 04 grudnia 2008 20:23
    Cytat z MSDN: "If successful, the WSAStartup function returns zero" to oznacza, że powinno być tak:

    if(WSAStartup(version, &wsaData)) { // mamy błąd }

    i teraz działa OK.
  • Kamil Marcin Sienkiewicz (@gecio) 05 grudnia 2008 15:39
    Pozornie działa ok. Nie zapominaj, że 0 to false.
  • Kamil Marcin Sienkiewicz (@gecio) 05 grudnia 2008 15:42
    A tak, przepraszam, do artykułu wkradł się błąd. Przepraszam za powyzszy koment, tak to jest jak sie nie czyta dokladnie ;p. W przykładach mam zapisane dobrze, tu niestety jest źle. Za niedogodność przepraszam.
  • spoxer (@spoxer) 17 grudnia 2008 10:34
    W niektórych miejscach brakuje ";" :P
  • ~anonim 04 stycznia 2009 22:12
    żal plagiat
  • Kamil Marcin Sienkiewicz (@gecio) 20 lutego 2009 21:55
    Dzięki uprzejmości Regedit'a przykłady zostały zamieszczone na dobrym serwerze :): http://regedit.warsztat.gd/Mirror/WinSock%20Samples/
  • ~Rafal 18 kwietnia 2009 19:31
    Ciekawy tutorial, szkoda, że do niektórych nowych rzeczy nie ma słowa wyjaśnienia, tylko na zasadzie: "tak ma być". Znalazłem błąd w funkcji accept - trzeci parametr powinien być <u>wskaźnikiem</u> na rozmiar.
  • ~Miner 06 maja 2009 18:59
    Nie rozumiem, po co wypychać ze strumienia dane?

    cout << "Niepoprawne połączenie!" << endl;

    Po co tam endl?
  • ~xrk 16 maja 2009 14:52
    wie ktos moze jak na winsocku w windows xp zastapic uzycie flagi MSG_WAITALL w funkcji recv() poniewaz nie zostala zaimplementowana?
  • SQB (@SQB) 10 listopada 2009 12:39
    A mnie wyskakuja takie bledy i nie wiem o co chodzi ;p podpowie ktos? File format not recognized ld returned 1 exit status C:\Dev-Cpp\projekty\winsock\Makefile.win [Build Error] [klient.exe] Error 1
  • SQB (@SQB) 10 listopada 2009 12:53
    Zostal mi tylko ostatni blad do zanalizowania. w 2 dwoch pomoglo przeniesienie biblioteki do innego folderu
  • Frondeus (@frondeus) 29 listopada 2009 19:37
    Daj se spokój z Dev-Cpp... założe się że ten sam kod skompiluje się w MSVC++
  • hemik (@hemik) 11 stycznia 2010 21:33
    Pelen nieprofesjonalizm. Kod z bledami... Sa tu gdzies minusowe pkty do postawienia?:>
  • rzuf (@rzuf) 02 marca 2010 15:34
    Błędy srędy, dobry programista jest czujny! :)
  • rflame (@rflame) 04 grudnia 2011 11:08
    ten kurs jest fajny ake nie dokladny ;/ znacie jakis lepszy?????????????????????????/
  • Napisz komentarz:
    Aby dodać swój komentarz, musisz się zalogować.
Licencja Creative Commons

Warsztat używa plików cookies. | Copyright © 2006-2017 Warsztat · Kontakt · Regulamin i polityka prywatności
build #ff080b4740 (Tue Mar 25 11:39:28 CET 2014)