SOCKADDR_IN: өтініш розетканы жұмыс істеу көрсету үшін осы құрылымды пайдаланады. SOCKADDR_IN IP-мекен-жайы және порт нөмірі үшін өрістерді қамтиды:
struct sockaddr_in
{
short sin_family; // тип протокола
u_short sin_port; // порт-номер сокета
struct in_addr sin_addr; // IP-адрес
char sin_zero[8]; // не используется
};
Бірінші өрісі хаттама түрі, әдетте, 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); // нам бы Winsock версии 1.1
// Мы начинаем с инициализации Winsock
WSAStartup(sockVersion, &wsaData);
// Затем создаем прослушивающий сокет
SOCKET listeningSocket;
listeningSocket = socket(AF_INET, // идем через TCP/IP
SOCK_STREAM, // Это сокет, ориентированный на поток
IPPROTO_TCP); // Используйте TCP вместо UDP
if (listeningSocket == INVALID_SOCKET)
{
nret = WSAGetLastError(); // Получите более подробную ошибку
ReportError(nret, "socket()"); // Сообщите об ошибке с помощью нашей пользовательской функции
WSACleanup(); // Закрываем Winsock
return NETWORK_ERROR; // Верните значение ошибки
}
// используйте SOCKADDR_IN структуру для заполнения адресной информации
SOCKADDR_IN serverInfo;
serverInfo.sin_family = AF_INET;
serverInfo.sin_addr.s_addr = INADDR_ANY; // Так как этот сокет прослушивает подключения,
// любой локальный адрес будет делать
serverInfo.sin_port = htons(8888); // Преобразование целого 8888 на порядок network-byte
// и вставляете в поле порта
// Привяжите сокет к нашему адресу локального сервера
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;
}
// ждите клиента
SOCKET theClient;
theClient = accept(listeningSocket,
NULL, // При желании, адрес SOCKADDR_IN структурируется
NULL); // При желании, адрес переменной содержит
// sizeof ( struct SOCKADDR_IN )
if (theClient == INVALID_SOCKET)
{
nret = WSAGetLastError();
ReportError(nret, "accept()");
WSACleanup();
return NETWORK_ERROR;
}
// Отправляйтe и получайте от клиента, и, наконец,
closesocket(theClient);
closesocket(listeningSocket);
// закрывайте Winsock
WSACleanup();
return NETWORK_OK;
}
void ReportError(int errorCode, const char *whichFunc)
{
char errorMsg[92]; // Декларируйте буфер для удержания
// сообщение об ошибке генерируется
ZeroMemory(errorMsg, 92); // автоматически завершится и обнулится строка
// Следующая строка копирует фразу, whichFunc строку, и интегрирует код ошибки в буфер
sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);
MessageBox(NULL, errorMsg, "socketIndication", MB_OK);
Сіз бірден код аңғаруға болады жалғыз нәрсе қате тексеру лауазымына күш сомасы болып табылады. қате орын кезде, коды WSAGetLastError () пайдалана отырып, тиісті қате кодын қабылдайды және желідегі нәтиже сақтайды. қате коды) ReportError (атты таңдамалы функциясы сәтсіз функцияның атын көрсете отырып, жолдың бірге жіберіледі. Онда, қате туралы хабар көрсетіледі және пайдаланушы стандартты WinAPI бөлігі болып табылады MessageBox () қоңырау, пайдаланып құрылған. Мысалы, толық ақаулық желісі болар еді, (WSANOTINITIALISED ретінде анықталады) қате коды бар 10093 үшін) (тыңдау, сәтсіз аяқталды «) (тыңдауға Call қате 10093 оралды». Сіз үнемді әзірлеуші, сіз код көзқараспен және WSAStartup табысты қоңырау () әлі жасалған жоқ, өйткені қате пайда болды, бұл табылған.
Александр Павлов туралы ондаған стандартты розетка қатенің сипаттау қамтуы,) (осы қате туралы хабарды ұзартылды. Оның соңғы нұсқасын, сіз бұдан былай код, яғни, іздеу үшін қажет болады, және сіздің бағдарлама сіздің бөлігінде өте аз күш неғұрлым ыңғайлы болып.
Сондай-ақ, NETWORK_ERROR және NETWORK_OK анықталған. Сіздің жеке желі функцияларын қайтару мәні тексеру кезінде пайдалы болуы мүмкін. Егер функциясы осы мәндердің бірін қайтарады болса, қоңырау функциясы кез келген қателерді анықтау үшін, теңдік қарапайым тест орындауға болады, егер: (myNetworkingFunction () == NETWORK_ERROR) {…}. Қоңырау шалушы содан тиісінше WSAGetLastError (үшін арнайы код алуға) және қатені өңдеуге болады. сіз бірден неге сіздің бағдарлама polamat білетін болады, өйткені Сайып келгенде, жақсы қате өңдеу схемасы қазір іске асыру сізге дамыту уақыт күн немесе апта көп үнемдеуге мүмкіндік береді.
Сонымен қатар, тапсырыс берушінің жаңа қосылым қайтаруға (қабылдауға) орнына қосымша уақыт немесе (жылдамдық сыни хост цикл әсіресе ойын серверлерінде проблема болуы мүмкін) функциясы қоңырау талап әдістерін пайдаланудан гөрі, сервер тұтынушы туралы ақпаратты алуға мүмкіндік береді. Осы функцияны пайдалануға SOCKADDR индексі құйылған sockaddr_in құрылымында мекенжайына өту, яғни (LPSOCKADDR) & aSockaddrInStructure. Сонымен қатар, SOCKADDR құрылымында sizeof INT құнына бүтін айнымалы жиынтығы мәлімдей, және үшінші параметр ретінде бүтін мекенжайын өтеді. мекен-жайы туралы ақпарат қоңырау кейін қайтарылуы тиіс болса, ұзындығы параметр болуы тиіс.
jdarnold осы параметр бойынша MSDN құжаттаманы үшінші сену емес бізге былай деп ескертеді: «MSDN құжат сіз осы ғана қосымша шығыс параметрі екенін addrlen өтіңіз қажеті жоқ деп есептейді, бірақ олар дұрыс емес Кіріс қанша байт SOCKADDR буфер және шығыс [дейді. Winsock]] көптеген [Winsock толтырады қолданылады. Егер сіз Len ретінде нөлге өтіңіз болса, [Winsock] буферін әсер етпейді «.
Ол қосылу үшін тек бір пайдаланушы үшін күтеді етіп, серверден mnogozavisit, содан кейін бірден өшірілген, бірақ бұл негізгі жобалау болып табылады. жай нәрселерді тазалау үшін, WSAStartup () қоңырау сіз (бұл жағдайда 1.1) жүктеу және WSADATA құрылымын шешу үшін келетін нұсқасы көрсетеді Сөзді кіреді. Келесі, біз басқа компьютерлерге қосу үшін қалай қараймыз.
Сіздің жеке байланыс жасау
басқа біреуге қосылу үшін сокет жасау бөлігі HOSTENT құрылымын қоспағанда, бірдей функцияларды пайдаланады:
HOSTENT: компьютерлік және порт қосылатын сокет айтып үшін пайдаланылады құрылымы. Бұл құрылымдар әдетте hostent құрылымына ғана көрсеткіштер болып табылады HOSTENT IP айнымалы ретінде пайда болады. Егер сіз Windows коды болғандықтан, сіз predshestvovshy LP түрі іс жүзінде екенін көрсетеді деректер кез-келген түрі «базасының» түрі (мысалы, LPCSTR сондай-ақ CHAR * ретінде белгілі, С желісі сілтегіш) көрсетеді екенін түсіну prinitspe.Сондықтан, кодексіне тікелей алуға мүмкіндік:
#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);
// Инициализация Winsock, как и раньше
WSAStartup(sockVersion, &wsaData);
// Хранит информацию о сервере
LPHOSTENT hostEntry;
hostEntry = gethostbyname("www.yahoo.com"); // Указание сервера по его имени;
// другой вариант: gethostbyaddr()
if (!hostEntry)
{
nret = WSAGetLastError();
ReportError(nret, "gethostbyname()"); // Сообщает об ошибке, как и раньше
WSACleanup();
return NETWORK_ERROR;
}
// Создает сокет
SOCKET theSocket;
theSocket = socket(AF_INET, // через TCP/IP
SOCK_STREAM, // Это поток, ориентированный на сокет
IPPROTO_TCP); // используйте TCP вместо UDP
if (theSocket == INVALID_SOCKET)
{
nret = WSAGetLastError();
ReportError(nret, "socket()");
WSACleanup();
return NETWORK_ERROR;
}
// заполните SOCKADDR_IN структуру адресной информацией
SOCKADDR_IN serverInfo;
serverInfo.sin_family = AF_INET;
// На данный момент, мы успешно извлекли жизненно важную информацию о сервере,
// в том числе его имя хоста, псевдонимы и IP-адреса. Подождите; как мог один
// Компьютер иметь несколько адресов, и что именно следующая строка делает?
// Читайте объяснение ниже.
serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);
serverInfo.sin_port = htons(80); // Перейдите в сетевой порядок байт и
// введите в поле порта
// подсоединитесь к серверу
nret = connect(theSocket,
(LPSOCKADDR)&serverInfo,
sizeof(struct sockaddr));
if (nret == SOCKET_ERROR)
{
nret = WSAGetLastError();
ReportError(nret, "connect()");
WSACleanup();
return NETWORK_ERROR;
}
// успешно подсоединено!
// Отправьте / получите, а затем очистите:
closesocket(theSocket);
WSACleanup();
}
void ReportError(int errorCode, const char *whichFunc)
{
char errorMsg[92]; // Декларируйте буфер для удержания
// сообщение об ошибке генерируется
ZeroMemory(errorMsg, 92); // Автоматически NULL-завершает строку
// Следующая строка копирует фразу, которая Func строку, и интегрирует код ошибки в буфер
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);
Салыстырмалы бірден — жасырын солардың бірі болып табылады — бірнеше операцияларды орындайды, өйткені. ның қадамдық оны бір-бірінен алайық:
Мүше HOSTENT h_addr_list құрылымы, әдетте, жолдар немесе CHAR жиымы болып ** h_addr_list, * келіңіздер операндоға ретінде анықталады. Тізімдегі сервердің мекен-жайы белгілі барлық анықтау және көшіру үшін gethostbyname (). Дегенмен, бірнеше мекен-жайы тұжырымдамасы түбегейлі мағынасы қандай? Шын мәнінде, ол жасайды. Компьютеріңіз, шын мәнінде, көптеген ортақ желі мекенжайларын бар. Сіздің Интернет адресі 205.182.67.96 болуы мүмкін, сіздің жергілікті желі мекенжайы 10.0.0.2 болуы мүмкін, және Microsoft Windows іске қосылған барлық компьютерлер, әрине, «қысқа» мекен-жайы 127.0.0.1, компьютерлік жергілікті желіге өзі қараңыз үшін пайдаланылады. сол тұжырымдамасы Интернет мекен-жайы немесе IP мекен-жайы саласындағы қолданылады, сондықтан тізімі қажет, бірақ бір мекенжайын сақтау үшін емес, орын. артықшылықты мекен-жайы, яғни, қолда бар ең жақсы мекен-жайы әрқашан бірінші тізім элементіне көшіріледі, содан кейін екінші артықшылықты немесе басқа мекенжайлары отыр ескеріңіз.
* HostEntry-> h_addr_list нені білдіреді? Сіз разыменовывания оператор (*) тізімінде бірдей мекен-жайы қол жеткізу үшін қолданылады деп ойлаймын мүмкін. Алайда, белгілі бір индексі қамтамасыз етуге жағдайы жоқ, разыменовывания операторы автоматты түрде, бірінші артықшылықты мекенжайын көрсетеді. Бұл арнайы бөлім сервер кем дегенде бір мекен-жайы болуы керек, өйткені, бар кепілдік * hostEntry-> h_addr_list [0], тең.
Келесі, * разыменовывания оператор in_addr * немесе LPIN_ADDR отқа тасталатын операнд қайтарады. Соңында, тағы бір кідіріс операция тек бір мекен-жайы ұстап алады көрсеткіш, сілтеме құрылымын in_addr қайтару үшін жүзеге асырылады. нәтижесінде алынған құрылымы, содан кейін serverInfo.in_addr in_addr тағайындалады. Кейінгі қосылу () серверіне қосылымдар қалыптастыруда параметр ретінде URL мекен-жайын алады.
Серверінің IP-мекен-жайы белгілі болса, жарамды HOSTENT (gethostbyname айырмашылығы () алдыңғы листингіне пайдаланылатын)) (gethostbyaddr пайдалану арқылы алуға болады:
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)
{
// Обращайтесь соответственно
}
Бұл жағдайда, inet_addr () in_addr құрылымында тікелей IP-мекен-жайын көрсете отырып жолды көшіру үшін пайдаланылады. Кейіннен, құрылымы) талаптар gethostbyaddr (сәйкес тұрақты мекен-жайы операнд * алады. Екі әдістері ішінара мекенжай ақпарат Winsock толық жазба ретінде мекенжай қаулы сервер қайтару деп аталады.
Бірнеше қосымша ноталары: Интернетте беттердің аударма осы порты арқылы келіп, өйткені Port 80 жай пайдаланылады. Егер сіз белгілі бір файлды сұратуға және қайтару нәрсе алуға көріңіз веб-серверге жолды жібере болса, сіз өте қарапайым веб-браузерді еді. Әрине, жол толық HTTP команданы болуға тиіс. Бұл біз тыңдап, басқа компьютерлер жалғанатынын тамаша, сондай-ақ жіберуге және алуға мүмкіндік байланысын қамтиды.
Жіберу және алу
Диспетчерлік жүзеге асырылады, ыңғайлы жеткілікті, жіберуге кезінде () функциясы:
int send(
SOCKET s,
const char * FAR buf,
int len,
int flags
);
Жалпы алғанда, сіз буфер келсе бәрін көшіруге еді, және басқа да соңына дейін деректерді жіберу үшін қосылған розеткаға бойынша жіберу () функциясын қолданыңыз:
char buffer[256]; // Декларирование буфера в стеке
char *buffer = new char[256]; // или в куче
ZeroMemory(buffer, 256);
strcpy(buffer, "Pretend this is important data.");
nret = send(theSocket,
buffer,
strlen(buffer), // Заметим, что это указывает длину строки; не
// размер всего буфера
0); // В большинстве случаев равна нулю, но смотрите MSDN для получения дополнительных параметров
delete [] buffer; // Тогда и только тогда, когда была использована декларация динамической памяти
if (nret == SOCKET_ERROR)
{
// Get a specific code
// Handle accordingly
return NETWORK_ERROR;
} else {
// nret содержит кол-во отправленных байт
}
Прием это тот же процесс, наоборот:
char buffer[256]; // На стеке
char *buffer = new char[256]; // или в динамической памяти
nret = recv(theSocket,
buffer,
256, // Полный размер буфера
0);
delete [] buffer; // Манипулирование буфером, а затем удалите, если и только если
// буфер выделяется на динамической памяти
if (nret == SOCKET_ERROR)
{
// Получите специальный код
// Обращайтесь соответственно
return NETWORK_ERROR;
} else {
// nret contains the number of bytes received
}
«(Қабылдау) жіберу / қабылдау» дейді Microsoft Outlook бағдарламасында құралдар тақтасындағы батырмасы бар екенін атап өту қызықты қандай болып табылады. Содан кейін, «алу» көп рет) ғана батырмасы дұрыс көрінеді, немесе ол программист кіріс қабылдау (бір әдеті екеніне көз жеткізу үшін «қабылдау» дейін төмендетілді? (Тараптардың шағын әңгіме қайтадан, жақсы) өз теориясын қыршын қалыптастырады.
өз 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);
}
}
}
Оны сақтау ғарыш желісі ұзындығы байланысты, автоматты түрде ұлғайтылуы мүмкін вектор, орнына алаптың пайдаланылады. қабылдау () (алынған байт көрсетілген нөлден аз) қатені қайтарады болса, жым-жылас қайтарылады. Осы мүмкіндік болғандықтан, проблема функциясы жол пайдалануға Readline () функцияларын оралды қамтамасыз етуге тиіс. цикл ішіндегі бір сипаты жоқ Қызыл жол векторына қосылады, егер желі алынған, және. Осы жолдың сипаты болса, векторының мазмұны C көшіріледі, және жолды қайтарады. жол векторының және memset (артық бір сипаты) жарияланды – iruetsya және қайтару желісі автоматты түрде жойылады, сондықтан нөлге ұмтылады. нөл мәні бар сызық соңы әдетте, жақсы тәжірибе болып табылады бағдарламалау, ерекше қателерді болдырмайды және.
Сондай-ақ, қайтару (бос салмаңыз) және оңай рәміздер өзгерту қабілеті қолдауымен осы Бойко жақсарды нұсқасы:
// Код первоначально написан Нором. Модифицированный немного для
// поддержки MessageBox() API, сделать более читабельной логику,
// выровнять расстояние, и добавлять комментарии. Опубликовано с разрешения.
#define backKey '\b' // Чтобы отключить backspace-ы, #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: // регулировка backspace
if (theVector.size() > 0)
theVector.pop_back();
break;
case endStr: // Если конец строкового символьного операнда достигнут,
case newLine: // или если конец линейного символьного операнда достигнут,
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: // любой обычный операнд
theVector.push_back(buffer);
break;
}
}
}
Non-блоктау және асинхронды Sockets
Пайдаланушы қосатын дейін біз розеткалар бұғаттау туралы айтқан Осы уақытқа дейін мұндай қабылдауға () ретінде, онда функциясы қоңырау белгісіз уақытқа күтеді. ол ештеңе істеу айтты емес, кез келген уақытта розетка (кейінірек үшін бір нәрсе болады деп көрсетіп) табысты нәтижесінде немесе қате немесе ештеңе, не дереу қайтарады емес бұғаттау. осы түрін қолдану кемшілігі қолмен нәтижесі сіз қоңырау әрбір функциясы алынған көру үшін, егер сокет сұрауға мәжбүр болады. Сіз оқу, жазу немесе қайтарылған қате дайын қайсысын көру үшін, () таңдаңыз функциясы үшін қосқыштарының жиынтығын өте алады.
асинхронды розеткалар пайдалану және дереу оралды, бірақ сіз белгілі бір оқиға орын алды, қашан сендердің терезенің рәсімінің жіберу үшін хабарлама көрсетуге болады ерекшеліктері. Мысалы, сіз розетка SOCKET_GOTMSG ол нәрсе өзгерткенде хабарлама жіберген етіп істеуге болады. Әдетте бұл сіз кейінірек қажетсіз проблемалар тудырады болдырмау үшін розеткаға хабар алуым, (ауқымдылығы, бірақ қажетті) қателерді тексеру үшін өте ақылды екен. Біріншіден, біз ның асинхронды сокет жасау үшін пайдаланылатын кейбір функцияларды анықтау көрейік
INT WSAAsyncSelect (Socket с, HWND HWND, INT wMsg, ұзақ Левент пайдаланып)
Бұл функция оған асинхронды хабарламада және буыны ретінде розетканы анықтау үшін пайдаланылады. Бұл розетка, сіз жұмыс істеп жатқан кіммен. HWND сокет оқиғаны пайда болған кезде хабарлама аласыз терезенің дескриптор болып табылады. wMsg сіз (мысал жоғарыдан SOCKET_GOTMSG розетка байланысы болып табылады) Егер терезенің рәсімінің жібергіңіз келетін хабарды болып табылады. IEvent параметр сокет көрсетеді және хабар жіберу үшін қандай жағдайларда бір немесе бірнеше жалаулар алады. осы жалаудың Кейбір: