Добрый день!
Все делаю по рецепту: https://msdn.microsoft.com/en-us/library/windows/desktop/ms738545(v=vs.85).aspx
ТСР-протокол.
Создал клиента, создал сервер. Байты отправляются и принимаются.
Решил использовать для приема функцию recvfrom, с ее помощью (вроде как) можно определить адрес и порт отправителя.
На сервере после коннекта клиента получаю байты теперь так:
sockaddr_in client_addr;//структура для сохранения порта и адреса int client_addr_size = sizeof(client_addr);//и ее длина cout <<client_addr_size<< endl;//тут я проверил, задался ли размер: выводит всегда 16 байтов count_bytes = recvfrom( ClientSocket, recvbuf, recvbuflen, 0, ( sockaddr *)&client_addr, &client_addr_size);//а было recv(ClientSocket, recvbuf, recvbuflen, 0); //тут я пытаюсь получить из адреса читабельную строку char str[INET_ADDRSTRLEN]; if ( NULL != inet_ntop( client_addr.sin_family, &( client_addr.sin_addr), str, INET_ADDRSTRLEN)) { printf( "%s\n", str); } else//даже если произошла ошибка, я все равно пробую посмотреть { printf( "%s\n", str); cout << "error ip" << endl; } cout<< client_addr.sin_port << endl;//ну и порт посмотреть
Похоже, что структура sockaddr_in client_addr содержит мусор, т.к. если поменять
inet_ntop(client_addr.sin_family, &( client_addr.sin_addr), str, INET_ADDRSTRLEN)
на
inet_ntop(AF_INET.sin_family, &( client_addr.sin_addr), str, INET_ADDRSTRLEN)
(у меня все используют IPv4)
то printf выдаст IP 0.0.0.0 и порт 0, даже если я запускаю сервер на другой машине.
Не использую inet_ntoa, т.к. она считается устаревшей и VS ее просто не пропускает.
Полный код сервера:
Код клиента:
В общем, что я делаю не так, и как определить адрес и порт клиента?
Ramm
> recvfrom
Оно ж, вроде, для UDP?
Ramm
> и как определить адрес и порт клиента?
собрался банить по IP? : )
ArchiDevil
> Оно ж, вроде, для UDP?
А как тогда получить адрес для TCP?
Sh.Tac.
> собрался банить по IP? : )
Ну ведь адрес клиента как-то надо получить...
getpeername
https://msdn.microsoft.com/en-us/library/windows/desktop/ms738533(v=vs.85).aspx
Для получения адреса сокета клиента ТСР...
Т.е. вот этот код показывает адрес:
char str[INET_ADDRSTRLEN]; getpeername(ClientSocket, ( sockaddr *)&client_addr, &client_addr_size); if ( NULL != inet_ntop( client_addr.sin_family, &( client_addr.sin_addr), str, INET_ADDRSTRLEN)) { printf( "%s\n", str); } else { cout << "error ip" << endl; } cout<< client_addr.sin_port << endl;
А recvfrom в случае с ТСР тупо игнорирует последние 2 параметра.
Так вроде?)
Адрес клиента берется на приеме входящего коннекта и храниться если нужен. Вместо recvfrom используеться recv.
А еще предпочтительней не шаманить и новый код писать на готовой библиотеке типа poco или boost.asio во избежание собирания всех возможных грабель.
ShadowTeolog
> во избежание собирания всех возможных грабель
Например, каких? Зато они все известны, на многие вопросы есть ответы (за эти 20 лет существования winsock), да и народу работающих с сокетами очень много.
Ну и выглядит все не очень сложно: создай сокет и пускай пакеты по адресам=)
Ramm
> Зато они все известны, на многие вопросы есть ответы (за эти 20 лет
> существования winsock), да и народу работающих с сокетами очень много.
> Ну и выглядит все не очень сложно: создай сокет и пускай пакеты по адресам=)
В общем, за год-полтора разберешься, завалив по дороге пару проектов ;)
Это не шутка, в самом деле так.
Сложность в том, что вся необходимая информация размазана тонким слоем повсюду, ее так просто не соберешь. Очень много тонкостей и не все они в API. Так что, даже использование готовых библиотек не факт что спасет. Может местами облегчить освоение, а может и наоборот, к заморочкам сети добавить заморочки созданные библиотекой.
Zab
> В общем, за год-полтора разберешься, завалив по дороге пару проектов ;)
Замотивировал...=)
Вопрос, который покажется отдаленным от передачи данных... Но...
Функция send принимает в качестве пакета массив char-ов.
В C# есть тип byte, и удобный конвертер из любого типа... В С++ даже byte-ов нет, эта функция возложена на byte. Как конвертнуть double-int-float в байты (char-ы, получается), т.е. любой int или float должны занять всего 4 char-а (они же в С++ по 4 байта)?
Ramm
> Как конвертнуть double-int-float в байты (char-ы, получается), т.е. любой int или float должны занять всего 4 char-а (они же в С++ по 4 байта)?
union тебе на что? Или же просто двоичным копированием. Или прямым преобразованием типов (она хочет char*, но ты можешь скормить ей все что угодно). В C++ у тебя полная власть над памятью, делай что хочешь, но и за последствия тоже отвечаешь только ты.
Учитывай что числа на разных платформах имеют разное представление. В чисто двоичном аппаратно зависимом виде их гонять по сети опасно, надо специфицировать формат. Т.е. можно гонять, но тот, у кого формат другой обязан будет их конвертировать у себя.
Zab
Я на шарпе делал так: я конвертировал в цикле нужные мне дробные, целые и прочие символы в массивы байтов, создавал один массив длиной с сумму длин этих, а потом отправлял его. А на той стороне в том же порядке конвертировал обратно.
Тут я хочу то же самое. union - немного не то, слишком много неопределенностей и ограничений.
int i = 345; char c2[4]; memcpy(&c2, &i, sizeof(int)); cout << c2 << endl; int a; memcpy(&a, c2, sizeof(int)); cout << a << endl;
Типа такого...
Вот только cout << c2 << endl выдает какую-то фигню, типа кракозябр. Ну да, блин, он думает там символы лежат. А как байты увидеть?=)
for (int z=0;z<4;z++) cout << ( int)c2[z] << endl;
Вроде этого?
Вообще, так можно делать? А потом по сети передавать?
А еще можно так, вроде:
int a2; memcpy_s(&a2, sizeof( int), c2, 4); cout << a2 << endl;
И я так понимаю, что мне не нужно думать за уничтожение и освобождение памяти. Справится сборщик мусора, я же память не выделял...
Ramm
Книжку по С++ или даже по си без плюсов почитай, прежде чем за сеть браться. Если у тебя такие заморочки чисто с базовыми понятиями языка, тебе не до проблем с сетью пока.
Насторожила фраза про "пакеты". Осознаешь, что TCP - совсем не пакетный протокол, он потоковый? Прийти тебе может нарезанным совсем не теми кусками, как ты посылал. Это не UDP.
Zab
> Если у тебя такие заморочки чисто с базовыми понятиями языка
Вполне возможно, что что-то позабыл, бывает... Да и что я не так написал? Я, вроде, сам на свои вопросы ответил, я только спросил, правильно ли я рассуждаю и в том ли направлении иду...
Zab
> Осознаешь, что TCP - совсем не пакетный протокол, он потоковый.
> Прийти тебе может нарезанным совсем не теми кусками, как ты посылал. Это не
> UDP.
Если я послал "abcdefghijkl" за раз, за один send, а потом считываю на сервере, то у меня не может "abc" лежать на сокете, а остальная часть придти потом.
В контексте ТСР я подразумевал под "пакетом" одну отправку.
Если я отправил "abcdefghijkl" многократно, то я должен следить за тем, как считывать, т.е. сам нарезать и следить где начинаются и заканчиваются сообщения в "abcdefghijklabcdefghijkl"
> Прийти тебе может нарезанным
нарезанными мне ничего не может прийти.
Ramm
> Вполне возможно, что что-то позабыл, бывает... Да и что я не так написал?
Представление чисел, способ напечатать их побайтово, если ты этого не знаешь, скорее всего ты вообще очень мало знаешь про С++. Он не осваивается интуитивно, надо знать как что в нем работает, а не догадываться. Ты ж не изолирован от нижнего уровня, все порушить - раз плюнуть, и даже не заметить этого сразу. Никакой аналогии с C#, тот гораздо более высокоуровневый.
Учить языку через форум - занятие дохлое. Не перепечатывать же сюда многие сотни страниц из учебника...
Пара фрагментов, которые ты тут привел - как раз способ все порушить. Так нельзя с С++ обращаться, даже в виде эксперимента. Отсюда я и сделал вывод, что ты языка совсем не знаешь.
TCP гарантирует только что байты придут в той же последовательности, в которой ты их отправлял. Какими группами они придут - как получится. Группу могут как разрезать по пути, так и слить несколько воедино.
Ramm
> И я так понимаю, что мне не нужно думать за уничтожение и освобождение памяти.
> Справится сборщик мусора, я же память не выделял...
В C++ нет сборщика мусора.
Ramm
> Если я послал "abcdefghijkl" за раз, за один send, а потом считываю на сервере,
> то у меня не может "abc" лежать на сокете, а остальная часть придти потом.
Вообще-то, может. Операционная система может по своему усмотрению разрезать и переклеивать пакеты так, как ей вздумается. Например, если ты передашь ОС три посылки: "012345", "6789" и "abcdef", она может перепаковать их в два пакета "012345abc" и "def", просто потому чтобы сэкономить на IP-пакетах. Причём, часть "def" отправится не сразу - ОС ещё немного подождёт, чтобы, если твой код передаст ещё байты, их можно было дописать в тот же пакет, а не конструировать для этого новый.
Если соединение проходит через какой-нибудь прокси, он может перестроить и буферизировать ещё раз, по-своему.
Таким образом, при работе по сети, на места разрезов в TCP-пакетах полагаться нельзя.
То, что нарезки сходятся в твоём конкретном случае - это оптимизация для твоего конкретного случая, когда ОС видит, что оба конца туннеля на одном компьютере, соответственно, весь обмен идёт вообще напрямую через оперативную память в обход сетевых интерфейсов - в этом конкретном случае переклейки принесут больше вреда, чем пользы, поэтому в этом конкретном случае каждая отправка с клиента становится сразу видна целиком на сервере. Но, как я уже несколько раз повторил - это только в твоём конкретном случае, и то без гарантии, что другая версия видноус не сделает по-другому и после очередного обновления твоя программа не сломается.
Ramm
> printf("%s\n", str);
> cout << "error ip" << endl;
Что мешает написать cout << str << endl;? Альтернативно, что мешает написать printf("error ip\n");? Определись, каким из методов ты пользуешься, и не перемешивай их.
> cout <<client_addr_size<< endl;
> cout<< client_addr.sin_port << endl;
Код с как попало расставленными пробелами смотрится ещё хуже, чем код без пробелов. Если у тебя никак не хватает внимательности расставить пробелы одинаково - воспользуйся функцией автоформатирования в своём редакторе.
А вообще, C++ - довольно строгий язык, и к неоплошностям относится непростительно, поэтому лучше всё же научиться поддерживать в коде порядок и делать вещи аккуратно.
Ramm
> union - немного не то, слишком много неопределенностей и ограничений
Неопределённости и ограничения - лишь в твоём воображении, стоит лишь изучить язык - и union становится верным и гибким инструментом.
void writefloat(std::vector<char>& buffer, float value) { union { float asfloat; char asbytes[4]; } u; u.asfloat = value; buffer.insert( buffer.end( ), u.asbytes, u.asbytes + 4); }
Ramm
> memcpy(&c2, &i, sizeof(int));
Если нужна копия в массив, лучше вместо &c2 записать просто c2. Здесь, по счастливому стечению обстоятельств, оба варианта делают одно и то же, тогда как в общем случае, memcpy(arr, ...) запишет в содержание массива, тогда как memcpy(&arr, ...) запишет в указатель arr, вместо адреса содержания массива, байты по второму аргументу. Причём, если третий аргумент будет больше, чем sizeof(arr) - С++ накажет тебя за неоплошность и сломает программу в каком-нибудь неожиданном месте.
Ramm
> Вот только cout << c2 << endl выдает какую-то фигню, типа кракозябр. Ну да,
> блин, он думает там символы лежат. А как байты увидеть?=)
Одна из фишек С++ - это перегрузка операторов. Это означает, что на каждое имя функции и оператора можно привязать множество вариантов, и то, какой именно из вариантов будет выбран в каждом конкретном случае, зависит от того, с аргументами какого типа вызван этот оператор.
cout << c2 - это то же самое, что и явный вызов operator<<(cout, c2).
Учитывая типы переменных cout и c2, среди множества перегрузок operator<<, будет выбран std::ostream& operator<<(std::ostream&, char const*), который записывает в поток си-строку, переданную вторым аргументом. Притом, так как c2 си-строкой не является, - С++ может наказать за неоплошность и сломать программу.
Если нужно, чтобы в поток было записано текстовое представление числа, нужно выбрать перегрузку, которая принимает число, и передать ей значение байта отдельно. Это можно сделать, например, взяв (int)c2[0]; тогда компилятор выберет std::ostream& operator<<(std::ostream&, int), другую функцию с другим поведением - перевести число в текст, и записать этот текст в поток.
Ну и возвращаясь к сборщику мусора, которого нет. В С++, как правило, объекты жёстко привязаны к переменным - объект создаётся в момент объявления переменной, и уничтожается немедленно в момент выхода переменной из зоны видимости. Например:
void writefloat_test(std::vector<char>& buffer, float f) // buffer и f созданы в момент вызова функции { union { float asfloat; char asbytes[4]; } u; // u создан в начале функции u.asfloat = value; for( int i = 0; i < 4; ++i) // i создан сразу перед первой итерацией { int asbyte = u.asbytes[i]; // asbyte создан в начале каждой итерации std::cout << asbyte << std::endl; // asbyte уничтожен в конце каждой итерации } // i уничтожен сразу после последней итерации buffer.insert( buffer.end( ), u.asbytes, u.asbytes + 4); // u уничтожен сразу перед выходом из функции // buffer, f уничтожены сразу перед выходом из функции }
Однако, ни в коем случае не следует воспринимать операции создания и уничтожения обектов как что-то, что расходует время - в подавляющем большинстве случаев, компилятор самостоятельно способен увидеть, что одно и то же место в памяти используется в течение длительного времени разными объектами, и сливает их в одит слот памяти. Напоминает о том, как ОС сливает несколько отправок в один пакет.
Тема в архиве.