Джоні

Winsock падручнік

Калі вы наўмысна прыйшлі ў маім Winsock падручніку, вы, хутчэй за ўсё, знайшлі ідэю вашых уласных прыкладанняў, якія ўзаемадзейнічаюць праз Інтэрнэт, як захапляльная перспектыву, як я. Ці, магчыма, хто-то знайшоў перспектыву аднолькава цікавыя і вы даверана чаго гэта бачанне ў рэальнасць. У любым выпадку, служба Winsock сеткі і гэта кіраўніцтва дапамогуць вам у дасягненні вашых мэтаў камерцыйнага прадпрыемства, проста даследуючы сферу сеткавага праграмавання для асабістага выкарыстання, ці нешта паміж імі.

Вось што мы будзем пакрываць:

  • Стварэнне сокета: Улічваючы невялікую армію сеткавых функцый, мы можам пабудаваць праграму , якая цярпліва чакае ўваходзяць злучэнняў? Так, мы можам .)
  • Стварэнне ўласных злучэнняў:Улічваючы некалькі больш функцый, мы можам стварыць праграму , якая паспяхова звязвае з серверам праслухоўвання? Так, мы можам .)
  • Адпраўка і атрыманне: Пасля таго, як мы дасягнулі актыўны злучэнне, як мы выкарыстоўваем яе для абмену дадзеных паміж двума праграмамі? Вы ўжо здагадаліся --- адправіць () і RECV () .)
  • Дадатковыя падручнікі і спасылкі: Якія рэсурсы ёсць вышэй і па-за гэтага падручніка? Я вылучаю 3 , які павінен трымаць вас заняты на некаторы час (пасля таго, як вы пераварваў мойпадручнік, вядома :-).

Хаця вы можаце быць гатовыя, каб дасягнуць гэтага выклікаюць страх кропкі, у якой ваша прыкладанне паспяхова робіць сваё першае злучэнне, быць у курсе канцэпцый коды. Старайцеся пазбягаць проста маніпулюючы дадзены код у адпаведнасці з вашымі патрэбамі і неадкладныя замест таго, каб вызначыць патрабаванні вашага прыкладання, і толькі затым ажыццявіць тое, што здаецца лепшым рашэннемэтага дастаткова , мой савет Дзэн Software Development на дадзены момант; давайце зробім некаторыя сеткавае праграмаванне ...

Вы можаце спампаваць увесь падручнік кода лістынга . Памятаеце , што любы код , прадстаўлены ў дадзеным кіраўніцтве , павінен быць звязаны з бібліятэкай Winsock, звычайна wsock32.lib або што - той жа назвай. Акрамя таго , пры выкарыстанні кода сапраўды гэтак , як пададзена ў падручніку ў вашай ўласнай асяроддзі (Dev-C ++, Microsoft VC ++, C ++ Builder і г.д.), выбраць для стварэння праекта для Windows з WinMain () , каб пазбегнуць памылак.

Стварэнне слухае сокета

Прымяненне абслугоўванне па-за машыны называюцца серверамі. Серверныя прыкладанні для праслухоўвання для кліентаў ініцыялізацыі адзін або некалькі слухаюць сокеты. Калі кліент падключаецца да аднаго з гэтых слухаюць сокетаў, сервер атрымлівае апавяшчэнне ад Winsock, прымае злучэнне, і пачынае адпраўляць і перахопліваць паведамленні і ад новага кліента. Мабыць, самым спрошчаным спосабам, з дапамогай якога серверы апрацоўваюць мноства кліентаў, каб спарадзіць новы струмень для кожнага кліенцкага злучэння. Гэтая мадэль сервера найбольш часта выкарыстоўвае блакавальныя сокеты, якія часова прыпыніць чакаць паступаючых дадзеных, новае злучэнне, і іншых сеткавых падзей. Па-першае, давайце вызначыць некаторыя структуры, мы павінны ініцыялізаваць сокет Блок пастаўлены:

  • WSADATA: Гэтая структура выкарыстоўваецца для запыту аперацыйнай сістэмы для версіі Winsock нашага код патрабуе. Прыкладанне выклікае WSAStartup (), каб ініцыялізаваць правільна Winsock DLL.
  • SOCKET: Аб'ект (на самай справе, гэта вызначаецца як u_int, цэлы лік без знака, у winsock.h --- добра ведаць , для Smalltalk на вечарынках) , якое выкарыстоўваецца прыкладаннямі для захоўвання дэскрыптара сокета.
  • Sockaddr_in: дадатак выкарыстоўвае гэтую структуру , каб вызначыць , як сокет павінен працаваць. Sockaddr_in змяшчае поля для IP-адрас і нумар порта:

 

struct sockaddr_in
 
{
 
  short sin_family;         // Protocol type
 
  u_short sin_port;         // Port number of socket
 
  struct in_addr sin_addr;  // IP address
 
  char sin_zero[8];         // Unused
 
};

 

Першае поле ўяўляе сабой тып пратаколу, які звычайна AF_INET (TCP / IP). Як хто слухае сокет не звязаны з сеткавым адрасам машыны, на якой ён знаходзіцца, Winsock аўтаматычна прызначае IP-адрас і нумар порта для праслухоўвання сокетаў пры стварэнні.

Мы будзем будаваць наш першы сервер для праслухоўвання з вышэйпералічанымі структурамі і невялікі арміяй сеткавых функцый:

 

 #include <windows.h>
 
#include <winsock.h>
 
#include <stdio.h>
 
 
 
#define NETWORK_ERROR -1
 
#define NETWORK_OK     0
 
 
 
void ReportError(int, const char *);
 
 
 
 
 
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow)
 
{
 
        WORD sockVersion;
 
        WSADATA wsaData;
 
        int nret;
 
 
 
        sockVersion = MAKEWORD(1, 1);                 // We'd like Winsock version 1.1
 
 
 
 
 
        // We begin by initializing Winsock
 
        WSAStartup(sockVersion, &wsaData);
 
 
 
 
 
        // Next, create the listening socket
 
        SOCKET listeningSocket;
 
 
 
        listeningSocket = socket(AF_INET,             // Go over TCP/IP
 
                                SOCK_STREAM,          // This is a stream-oriented socket
 
                                IPPROTO_TCP);         // Use TCP rather than UDP
 
 
 
        if (listeningSocket == INVALID_SOCKET)
 
        {
 
               nret = WSAGetLastError();             // Get a more detailed error
 
               ReportError(nret, "socket()");        // Report the error with our custom function
 
 
 
               WSACleanup();                         // Shutdown Winsock
 
               return NETWORK_ERROR;                 // Return an error value
 
        }
 
 
 
 
 
        // Use a SOCKADDR_IN struct to fill in address information
 
        SOCKADDR_IN serverInfo;
 
 
 
        serverInfo.sin_family = AF_INET;
 
        serverInfo.sin_addr.s_addr = INADDR_ANY;      // Since this socket is listening for connections,
 
                                                     // any local address will do
 
        serverInfo.sin_port = htons(8888);            // Convert integer 8888 to network-byte order
 
                                                     // and insert into the port field
 
 
 
 
 
        // Bind the socket to our local server address
 
        nret = bind(listeningSocket, (LPSOCKADDR)&serverInfo, sizeof(struct sockaddr));
 
 
 
        if (nret == SOCKET_ERROR)
 
        {
 
               nret = WSAGetLastError();
 
               ReportError(nret, "bind()");
 
 
 
               WSACleanup();
 
               return NETWORK_ERROR;
 
        }
 
 
 
 
 
        // Make the socket listen
 
        nret = listen(listeningSocket, 10);           // Up to 10 connections may wait at any
 
                                                     // one time to be accept()'ed
 
 
 
        if (nret == SOCKET_ERROR)
 
        {
 
               nret = WSAGetLastError();
 
               ReportError(nret, "listen()");
 
 
 
               WSACleanup();
 
               return NETWORK_ERROR;
 
        }
 
 
 
 
 
        // Wait for a client
 
        SOCKET theClient;
 
 
 
        theClient = accept(listeningSocket,
 
                          NULL,                       // Optionally, address of a SOCKADDR_IN struct
 
                          NULL);                      // Optionally, address of variable containing
 
                                                     // sizeof ( struct SOCKADDR_IN )
 
 
 
        if (theClient == INVALID_SOCKET)
 
        {
 
               nret = WSAGetLastError();
 
               ReportError(nret, "accept()");
 
 
 
               WSACleanup();
 
               return NETWORK_ERROR;
 
        }
 
 
 
 
 
        // Send and receive from the client, and finally,
 
        closesocket(theClient);
 
        closesocket(listeningSocket);
 
 
 
 
 
        // Shutdown Winsock
 
        WSACleanup();
 
        return NETWORK_OK;
 
}
 
 
 
 
 
void ReportError(int errorCode, const char *whichFunc)
 
{
 
   char errorMsg[92];                                // Declare a buffer to hold
 
                                                     // the generated error message
 
   
 
   ZeroMemory(errorMsg, 92);                         // Automatically NULL-terminate the string
 
 
 
   // The following line copies the phrase, whichFunc string, and integer errorCode into the buffer
 
   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);
 
 
 
   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);
 
}

 

Адна рэч, вы можаце адразу заўважыць пра код з'яўляецца колькасць намаганняў пакласці ў праверцы памылак. Кожны раз, калі ўзнікае памылка, код атрымлівае код памылкі з WSAGetLastError () і захоўвае вынік у nret. Код памылкі затым адпраўляецца разам са радком, якая паказвае імя якога было адмоўлена функцыі карыстацкай функцыі з імем ReportError (). Там, паведамленне пра памылку пабудавана і паказана карыстачу з дапамогай выкліку MessageBox (), якая з'яўляецца часткай стандартнага WinAPI.Напрыклад, калі б слухаць () пацярпела няўдачу з кодам памылкі 10093 (што вызначаюцца як WSANOTINITIALISED), які быў завершаны радок памылкі будзе «Патэлефануйце, каб слухаць () вярнула памылку 10.093!». Вы, прадбачлівы распрацоўшчык, затым паглядзець код і выявіць, што памылка адбылася з-за паспяховы выклік WSAStartup () яшчэ не быў зроблены.

Аляксандр Паўлаў пашырыў гэтую REPORTERROR () , каб ўключаць у сябе апісання для каля тузіна стандартных памылак сокета. Выкарыстоўваючы сваю мадэрнізаваную версію , вы больш не будзеце мець патрэбу для пошуку , што азначае , што код, і праграма становіцца значна больш зручным з вельмі мала намаганняў з вашага боку.

Таксама ўключана пэўныя для NETWORK_ERROR і NETWORK_OK. Гэта можа быць карысна пры праверцы вяртаецца значэння вашых уласных сеткавых функцый. Калі функцыя вяртаецца адно з гэтых значэнняў, то функцыя выкліку можа выканаць просты тэст на роўнасць, каб выявіць якія-небудзь памылкі: калі (myNetworkingFunction () == NETWORK_ERROR) {...}. Функцыя выкліку можа затым атрымаць спецыяльны код з WSAGetLastError () і апрацаваць памылку адпаведным чынам. У канчатковым рахунку, рэалізацыя добрай схемы апрацоўкі памылак цяпер зэканоміць вам шмат дзён ці тыдняў часу распрацоўкі, як вы будзеце імгненна ведаць, чаму ваша праграма не ўдалася.

У дадатку да вяртання новага падлучэння кліента, прыняць () дазваляе серверу атрымліваць інфармацыю пра кліента, а не з дапамогай метадаў, якія патрабуюць дадатковых выклікаў функцый або часу (што можа стаць праблемай у гульнявых серверах, дзе хуткасць прымае пятлю асабліва крытычны). Каб скарыстацца перавагамі гэтай функцыі, перадаць у адрас у sockaddr_in STRUCT гіпсе паказальніку SOCKADDR, то ёсць (LPSOCKADDR) і aSockaddrInStructure. Акрамя таго, абвясціць цэлалікавых зменную, усталяваць значэнне Int да SIZEOF ў SOCKADDR структуры, і перадаць адрас цэлага ліку ў якасці трэцяга параметру. Калі адрасная інфармацыя павінна быць вернута пасля выкліку функцыі, параметр даўжыні павінен прысутнічаць.

jdarnold папярэджвае нас не верыць у дакументацыі MSDN па гэтым трэцяга параметры. «Дакументы MSDN мяркуюць , што вы не павінны прайсці ў addrlen, што гэта проста дадатковы выхадны параметр, але яны не маюць рацыю уязной ён кажа , колькі байт у буферы SOCKADDR, і выходныя [Winsock] запаўняе колькі [Winsock] выкарыстоўваецца. Калі перадаць нуль, як даўжыня, [Winsock] не будзе тычыцца буфераў «.

Гэта не так шмат сервера, так як ён чакае толькі аднаго карыстальніка для падлучэння, а затым адразу ж адключаецца, але гэта самы асноўны дызайн. Проста каб прыбраць рэчы, выклік WSAStartup () уключае ў сябе WORD, паказаўшы, якую версію вы хочаце загрузіць (у дадзеным выпадку гэта 1,1) і адрас структуры WSADATA. Далей мы разгледзім, як злучыцца з іншымі кампутарамі.

Стварэнне ўласных падлучэнняў

Стварэнне сокета для падлучэння да каго-то яшчэ выкарыстоўвае тыя ж самыя функцыі, за выключэннем HOSTENT структуры:

  • HOSTENT: структура выкарыстоўваецца , каб сказаць раз'ём , да якога кампутар і порт для падлучэння. Гэтыя структуры звычайна з'яўляюцца як LPHOSTENT зменныя, якія з'яўляюцца толькі паказальнікамі на HOSTENT структур. Як вы код для Windows, вы звычайна лічаць, што любы тып дадзеных папярэднічае LP азначае, што тып фактычна з'яўляецца паказальнікам на тып «базавы» (напрыклад, LPCSTR з'яўляецца паказальнікам на радок C, таксама вядомы як сімвал * ).

Такім чынам, давайце пяройдзем прама да кода:

 

 #include <windows.h>
 
#include <winsock.h>
 
#include <stdio.h>
 
 
 
#define NETWORK_ERROR -1
 
#define NETWORK_OK     0
 
 
 
void ReportError(int, const char *);
 
 
 
 
 
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow)
 
{
 
        WORD sockVersion;
 
        WSADATA wsaData;
 
        int nret;
 
 
 
        sockVersion = MAKEWORD(1, 1);
 
 
 
 
 
        // Initialize Winsock as before
 
        WSAStartup(sockVersion, &wsaData);
 
 
 
 
 
        // Store information about the server
 
        LPHOSTENT hostEntry;
 
 
 
        hostEntry = gethostbyname("www.yahoo.com");   // Specifying the server by its name;
 
                                                     // another option: gethostbyaddr()
 
 
 
        if (!hostEntry)
 
        {
 
               nret = WSAGetLastError();
 
               ReportError(nret, "gethostbyname()"); // Report the error as before
 
 
 
               WSACleanup();
 
               return NETWORK_ERROR;
 
        }
 
 
 
 
 
        // Create the socket
 
        SOCKET theSocket;
 
 
 
        theSocket = socket(AF_INET,                   // Go over TCP/IP
 
                          SOCK_STREAM,                // This is a stream-oriented socket
 
                          IPPROTO_TCP);               // Use TCP rather than UDP
 
 
 
        if (theSocket == INVALID_SOCKET)
 
        {
 
               nret = WSAGetLastError();
 
               ReportError(nret, "socket()");
 
 
 
               WSACleanup();
 
               return NETWORK_ERROR;
 
        }
 
 
 
 
 
        // Fill a SOCKADDR_IN struct with address information
 
        SOCKADDR_IN serverInfo;
 
 
 
        serverInfo.sin_family = AF_INET;
 
 
 
        // At this point, we've successfully retrieved vital information about the server,
 
        // including its hostname, aliases, and IP addresses.  Wait; how could a single
 
        // computer have multiple addresses, and exactly what is the following line doing?
 
        // See the explanation below.
 
 
 
        serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);
 
 
 
        serverInfo.sin_port = htons(80);              // Change to network-byte order and
 
                                                     // insert into port field
 
 
 
 
 
        // Connect to the server
 
        nret = connect(theSocket,
 
                      (LPSOCKADDR)&serverInfo,
 
                      sizeof(struct sockaddr));
 
 
 
        if (nret == SOCKET_ERROR)
 
        {
 
               nret = WSAGetLastError();
 
               ReportError(nret, "connect()");
 
 
 
               WSACleanup();
 
               return NETWORK_ERROR;
 
        }
 
 
 
 
 
        // Successfully connected!
 
 
 
 
 
        // Send/receive, then cleanup:
 
        closesocket(theSocket);
 
        WSACleanup();
 
}
 
 
 
 
 
void ReportError(int errorCode, const char *whichFunc)
 
{
 
   char errorMsg[92];                                // Declare a buffer to hold
 
                                                     // the generated error message
 
   
 
   ZeroMemory(errorMsg, 92);                         // Automatically NULL-terminate the string
 
 
 
   // The following line copies the phrase, whichFunc string, and integer errorCode into the buffer
 
   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);
 
 
 
   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);
 
}

 

Самая складаная радок у спісе выглядае наступным чынам:

serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);

паколькі ён выконвае некалькі аперацый --- адна з іх адносна скрыты --- адразу. Давайце разбіраць яго крок за крокам:

Член h_addr_list з HOSTENT структуры ў асноўным вызначаецца як сімвал ** h_addr_list, які з'яўляецца масівам радкоў, або паўкокс * с. gethostbyname () вызначана і капіююцца ўсе вядомыя адрасы сервераў у гэты спіс. Аднак, гэта паняцце некалькі адрасоў прынцыпова мае сэнс? На самай справе, гэта робіць. Ваш кампутар, на самай справе, мае мноства агульных адрасоў сетак. Ваш інтэрнэт-адрас можа быць 205.182.67.96, ваш адрас лакальнай сеткі можа быць 10.0.0.2, і ўсе кампутары, на якіх устаноўлена АС Windows, натуральна, мае «зваротную пятлю» адрас 127.0.0.1, які выкарыстоўваецца кампутар для абазначэння сябе ў лакальнай сеткі , Тое ж самае ставіцца і ў вобласці інтэрнэт-адрасоў або IP-адрасоў, таму спіс неабходны, а не месца для захоўвання аднаго адрасы. Звярніце ўвагу , што пераважны адрас, гэта значыць, самыя даступны адрас, заўсёды капіююцца ў першы элемент спісу, а затым са другімі пераважнымі або іншымі адрасамі.

Што * hostEntry-> h_addr_list рабіць? Можна меркаваць, што аператар пачцівасць (*) выкарыстоўваецца для доступу да аднаго адрасе ў спісе. Аднак, будучы не ў стане даць канкрэтны індэкс, аперацыя разнаймення аўтаматычна адкрывае першы, пераважны адрас. Гэты канкрэтны раздзел эквівалентна * hostEntry-> h_addr_list [0], якая гарантавана існуе, так як сервер павінен мець па крайняй меры адзін адрас.

Далей, сімвал * вернуты разнаймення аперацыі адліваюць у in_addr * ці LPIN_ADDR. Нарэшце, яшчэ адна аперацыя пачцівасць выконваецца вярнуць in_addr-структуру, на якую спасылаецца паказальнік, які можа ўтрымліваць толькі адзін адрас. Атрыманы in_addr структура затым прысвойваецца serverInfo.sin_addr. Наступнае падлучэнне () прымае адзін адрас у якасці параметру пры фарміраванні злучэння з серверам.

Калі IP-адрас сервера, як вядома, сапраўдны HOSTENT можа быць атрыманы за кошт выкарыстання gethostbyaddr () (у адрозненне ад gethostbyname (), які выкарыстоўваецца ў папярэднім лістынгу):

 

 LPHOSTENT hostEntry;
 
in_addr iaHost;
 
 
 
iaHost.s_addr = inet_addr("204.52.135.52");
 
 
 
hostEntry = gethostbyaddr((const char *)&iaHost, sizeof(struct in_addr), AF_INET);
 
 
 
if (!hostEntry)
 
{
 
        // Handle accordingly
 
}

 

У гэтым выпадку, inet_addr () выкарыстоўваюцца для капіявання радкі, якая пазначае IP-адрас непасрэдна ў in_addr структуру. Пасля гэтага адрас структуры разліваюць у канстантнасцю сімвал * у адпаведнасці з патрабаваннямі gethostbyaddr (). Абодва метаду называюцца дазволу адрасу сервера, так як Winsock вяртае поўныя адрасныя запісы з частковай інфармацыі.

Некалькі дадатковых нататкі: порт 80 выкарыстоўваецца проста таму, што пераклады Інтэрнэт вэб-старонкі адбываецца праз гэты порт. Калі вы павінны былі адправіць радок на вэб-сэрвэры, якая запрошвае канкрэтны файл і паспрабаваць атрымаць што-то назад, вы б вельмі просты вэб-браўзэр.Вядома, гэты радок павiнна змяшчаць поўную каманду HTTP. Гэта выдатна, што мы можам слухаць і падлучацца да іншых кампутараў, але сувязь таксама ўключае ў сябе адпраўку і прыём.

Адпраўка і атрыманне

Адпраўка апрацоўваецца, досыць зручна, з дапамогай функцыі адпраўкі ():

 

int send(
 
  SOCKET s,
 
  const char * FAR buf,
 
  int len,
 
  int flags
 
);

 

У асноўным вы б скапіяваць усё, што вы хочаце ў буфер і выкарыстоўваць функцыю адпраўкі () на падлучаны сокет, каб зрабіць дадзеныя перайсці на іншы канец:

 

 char buffer[256];             // Declaring a buffer on the stack
 
char *buffer = new char[256];  // or on the heap
 
 
 
ZeroMemory(buffer, 256);
 
strcpy(buffer, "Pretend this is important data.");
 
 
 
nret = send(theSocket,
 
            buffer,
 
            strlen(buffer),    // Note that this specifies the length of the string; not
 
                               // the size of the entire buffer
 
            0);                // Most often is zero, but see MSDN for other options
 
 
 
delete [] buffer;              // If and only if the heap declaration was used
 
 
 
if (nret == SOCKET_ERROR)
 
{
 
        // Get a specific code
 
        // Handle accordingly
 
        return NETWORK_ERROR;
 
} else {
 
        // nret contains the number of bytes sent
 
}

 

Прыём той жа самы працэс, у адваротным кірунку:

 

 char buffer[256];             // On the stack
 
char *buffer = new char[256];  // or on the heap
 
 
 
nret = recv(theSocket,
 
            buffer,
 
            256,               // Complete size of buffer
 
            0);
 
 
 
delete [] buffer;              // Manipulate buffer, then delete if and only if
 
                               // buffer was allocated on heap
 
 
 
if (nret == SOCKET_ERROR)
 
{
 
        // Get a specific code
 
        // Handle accordingly
 
        return NETWORK_ERROR;
 
} else {
 
        // nret contains the number of bytes received
 
}

 

Што цікава адзначыць, што ёсць кнопка на панэлі інструментаў у Microsoft Outlook з надпісам «Send / ПРЫЁМ.» З'яўляецца Ці «Receive» скарочана «Recv» проста каб пераканацца , што кнопка выглядае правільна, ці гэта звычка праграміста ад уводу ПРЫЁМУ () так шмат разоў? Сфарміраваць свае ўласныя тэорыі змовы (ізноў жа , добра для Smalltalk на вечарынках).

Тут я сутыкнуўся з невялікай праблемай пры напісанні сваіх уласных праграм Winsock. Толькі з дапамогай ПРЫЁМУ () з'яўляецца вялікім, калі вы сапраўды ведаеце, колькі дадзеных вы будзеце атрымліваць (напрыклад, у гульні, дзе першы байт можа быць камандай, і наступныя байты быць параметр, і г.д.), але калі дон «ведаю, што вы робіце? Калі дадзеныя вы атрымліваеце сканчаецца сімвалам новага радка (агульная праблема з Java-кліентаў, якія будуць размаўляць на серверах C), вы можаце напісаць функцыю Readline (), каб захапіць усе да гэтага знака. Вось што я выкарыстаў:

 

 char * readLine()
 
{
 
   vector theVector;
 
   char buffer;
 
   int bytesReceived;
 
 
 
   while (true)
 
   {
 
      bytesReceived = recv(theSocket, &buffer, 1, 0);
 
      if (bytesReceived <= 0)
 
         return NULL;
 
 
 
      if (buffer == '\n')
 
      {
 
         char *pChar = new char[theVector.size() + 1];
 
         memset(pChar, 0, theVector.size() + 1);
 
 
 
         for (int f = 0; f < theVector.size(); f++)
 
            pChar[f] = theVector[f];
 
 
 
         return pChar;
 
      } else {
 
         theVector.push_back(buffer);
 
      }
 
   }
 
}

 

Вектар выкарыстоўваецца замест масіва, паколькі яго захоўвання прастора можа быць аўтаматычна павялічана, каб адпавядаць даўжыні лініі. Калі RECV () вяртае памылку (пазначаны bytesReceived быць менш за нуль), NULL, вяртаецца. Так як гэта магчыма, выклік функцый павінны гарантаваць, што якая вяртаецца радок з ReadLine () дзейнічае перад выкарыстаннем. Ўнутры цыклу, адзін знак прымаецца ад гнязда, і, калі не сімвал новага радка, дадаецца да вектару. Калі гэта знак новага радка, змесціва вектара капіюецца ў радок C і вяртаецца. Радок аб'яўляецца адзін сімвал больш, чым вектар і MemSet () 'Ted да нуля, так што які вяртаецца лінія будзе аўтаматычна нулём. Заканчэнне радкі з NULL прадухіляе незвычайныя памылкі і, як правіла, добрая практыка праграмавання.

Таксама ўяўляе гэтую хітра палепшаную версію з падтрымкай забою і магчымасць лёгка змяніць сімвал новага радка:

 

 // Code originally written by Nor.  Modified slightly to
 
// support the MessageBox() API, make logic more readable,
 
// align spacing, and add comments.  Posted with permission.
 
 
 
#define backKey '\b'                                 // To disable backspaces, #define backKey NULL
 
#define newLine '\n'
 
#define endStr  '\0'
 
 
 
char *readLine(SOCKET s)
 
{
 
        vector theVector;
 
        char buffer;
 
        char *pChar;
 
        int bytesReceived;
 
 
 
        while (true)
 
        {
 
               bytesReceived = recv(s, &buffer, 1, 0);
 
 
 
               if (bytesReceived <= 0)
 
               {
 
                       MessageBox(NULL, "recv() returned nothing.", "socketIndication", MB_OK);
 
                       return NULL;
 
               }
 
 
 
               switch (buffer)
 
               {
 
                       case backKey:                  // Handle backspace
 
                               if (theVector.size() > 0)
 
                                      theVector.pop_back();
 
                               break;
 
                       case endStr:                   // If end of string char reached,
 
                       case newLine:                  // or if end of line char reached,
 
                               pChar = new char[theVector.size() + 1];
 
                               memset(pChar, 0, theVector.size() + 1);
 
 
 
                               for (int f = 0; f < theVector.size(); f++)
 
                                      pChar[f] = theVector[f];
 
                               return pChar;
 
                               break;
 
                       default:                       // Any regular char
 
                               theVector.push_back(buffer);
 
                               break;
 
               }
 
        }
 
}

 

Неблокируемые і асінхронныя сокеты

Да гэтага моманту мы гаварылі пра блакавальных сокетаў, дзе выклік функцыі, такія, як прымаць () чакае на нявызначаны час для карыстальніка для падлучэння. Сокет неблокирующая неадкладна вяртаецца кожны раз, калі ён сказаў нешта рабіць, альбо з паспяховым вынікам, памылка або нічога (з указаннем, што там будзе нешта атрымаць пазней). Недахоп выкарыстання гэтага тыпу з'яўляецца тое, што вам давядзецца ўручную запытаць сокет, каб убачыць, калі вынік прыйсці ў кожнай функцыі, якую вы называеце. Вы можаце перадаць набор сокетаў функцыі выбару (), каб убачыць, якія з іх гатовыя для чытання, запісы, ці вярнуліся памылкі.

Функцыі з выкарыстаннем асінхронных сокетаў і неадкладна вярнуцца, але вы можаце паказаць паведамленне для адпраўкі ў вашай аконнай працэдуры, калі азначанае падзея адбылася. Напрыклад, вы можаце мець сокет адправіць паведамленне SOCKET_GOTMSG кожны раз, калі ён атрымлівае што-товычайна гэта разумныя, каб праверыць на наяўнасць памылак (грувасткіх, але неабходных), калі вы атрымліваеце паведамленне сокетаў, каб прадухіліць прычыненне непатрэбных праблем у далейшым.Па-першае, давайце вызначым некаторыя функцыі, якія мы будзем выкарыстоўваць, каб стварыць асінхронны сокет:

  • INT WSAAsyncSelect (SOCKET s, HWND HWND, непадпісаны INT WMSG, доўга Левент) 
    Гэтая функцыя выкарыстоўваецца, каб ідэнтыфікаваць сокет як асінхронны і асацыяваць паведамленне з ім. s гэта сокет, вы працуеце з. HWND з'яўляецца дэскрыптар вокны, які будзе атрымліваць паведамленне, калі сокет генеруе падзея. WMSG гэтага паведамлення, якое вы хочаце адправіць вашу аконную працэдуру (прыклад можа служыць паведамленне SOCKET_GOTMSG зверху). Параметр Левент прымае адзін або некалькі сцягоў, якія кажуць сокет, на якім падзеі, каб адправіць паведамленне. Некаторыя з гэтых сцягоў:
    • FD_READ: Socket гатовы да прыёму дадзеных
    • FD_WRITE: Socket гатовы да перадачы дадзеных
    • FD_ACCEPT: Выкарыстоўваецца ў серверах, гэта паведамленне паказвае на тое карыстальнік падлучаны
    • FD_CONNECT: Выкарыстоўваецца ў кліенцкіх прыкладаннях, гэта паведамленне кажа вам сокет падлучаны
    • FD_CLOSE: Сокет толькі што быў зачынены
  • WSAGETSELECTERROR (LPARAM LPARAM) 
    Вызначае, калі сокет вярнуў памылку. Тэхнічна, гэта не функцыя , а макрас (вы сапраўды можаце генераваць Smalltalk на вечарынках з гэтай маленькай Factoid).
  • WSAGETSELECTEVENT (LPARAM LPARAM) 
    Іншы карысны макрас, вызначаны ў winsock2.h з'яўляецца WSAGETSELECTEVENT (), які выкарыстоўваецца, каб убачыць менавіта тое, што сокет зрабіў.

Такім чынам, давайце створым асінхронны сокет:

 

 // Мы пачнем з стварэння сцяга, Windows, які будзе выкарыстоўвацца, каб звязацца з намі, калі нешта адбываецца

 

 #define THERE_WAS_A_SOCKET_EVENT     WM_USER + 100  // WM_USER is a base for custom messages

 

 

 

 // Дзесьці ў кодзе ініцыялізацыі пасля CreateWindow (), мы называем WSAAsyncSelect ()

 

 WSAAsyncSelect ( theSocket, hwnd, THERE_WAS_A_SOCKET_EVENT, FD_READ | FD_WRITE | FD_CONNECT | ... );

 

 

 

 // Гэта азначае: Windows, калі ласка, звяжыцеся са мной, выкарыстоўваючы сцяг THERE_WAS_A_SOCKET_EVENT, што я

 

 // вызначана раней, калі дадзеныя ёсць для чытання (FD_READ), ці калі я вольны для перадачы дадзеных

 

 // (FD_WRITE), ці калі я паспяхова падлучаны да камусьці іншаму (FD_CONNECT), ці калі ... і г.д ..

 

 

 // У нашай аконнай працэдуры (функцыі, якая апрацоўвае ўсе паведамленні, пасыланыя для Windows у дадатку)

 

 LRESULT WINAPI TheWindowProcedure ( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
 
{
 
 
 
        switch ( msg )
 
        {
 
               case THERE_WAS_A_SOCKET_EVENT:
 
                       if ( WSAGETSELECTERROR ( lParam ) )
 
                       {       // If an error occurred,
 
                               closesocket ( theSocket );
 
                               WSACleanup ();                        // Shutdown Winsock
 
                               return NETWORK_ERROR;
 
                       }
 
                       switch ( WSAGETSELECTEVENT ( lParam ) )
 
                       {       // What happened, exactly?
 
                               case FD_READ:
 
                                      // Receive data
 
                                      break;
 
                               case FD_WRITE:
 
                                      // Write data
 
                                      break;
 
                               case FD_CONNECT:
 
                                      // Just connected to server
 
                                      break;
 
                               case ...                              // Same setup for other flags
 
                                      break;
 
                       }
 
                       break;
 
 
 
               // other case statements with logic that handles other Windows messages
 
 
 
        }
 
}
 

Звярніце ўвагу, што вы не можаце вызначыць адно паведамленне для кожнага падзеі, як SOCKET_GOTMSG для FD_READ, а затым SOCKET_CONNECTED для FD_CONNECT. Гэта адбываецца таму, што неаднаразовыя заклікі да WSAAsyncSelect () для ўстаноўкі кожны сцяг будзе адмяніць дзеянне апошняга выкліку WSAAsyncSelect ().

Дадатковыя падручнікі і спасылкі

Я напісаў гэты падручнік у снежні 2000 года, а сем гадоў або каля таго з тых часоў бачылі пастаянны паток наведвальнікаў і паляпшэнняў. Я спадзяюся , што вы любілі чытаць столькі , колькі я любіў пісаць: дзякуй за выкарыстанне Winsock Падручнік Джоні. Вышэй, але кароткі агляд магчымасцяў вы можаце дасягнуць праз Winsock і іншыя зрабілі значна лепш, чым у мяне на зандаванні спецыфікі гэтай тэмы:

Я згодны з Томасам Bleeker (MadWizard), што «сеткавае праграмаванне здаецца прасцей, чым гэта.» Я не магу выклікаць вам важнасць практыкі выкарыстання гэтых функцый разам з адладчыкам, так што вы можаце ўбачыць, што адбываецца. Вы ў канчатковым выніку будзе мець значна лепшае разуменне таго, як працуюць рэчы, калі вы памыліцеся, высветліць, чаму вы атрымалі гэта няправільна, а затым выпрабаваць задавальненне атрымаць гэта права. Здзяйсненне памылак, іншымі словамі, то, як мы вучымся.

Як я магу палепшыць?

Ці ёсць што - то , што мае патрэбу ў удакладненні? Ці абавязкова падручнік , каб ахапіць тэму Winsock звязаныя , што вы хацелі даведацца аб? Мае падручнік сустрэліся вашыя патрэбы як распрацоўшчык праграмнага забеспячэння? З'яўляецца Ці гэта пацешным? бойка напісаны? празмерна спрошчаным? ці проста ня так?