Ru-Board.club
← Вернуться в раздел «Прикладное программирование»

» Несколько вопросов по сетевому программированию на С++ (азы)

Автор: Wc3Exp
Дата сообщения: 10.01.2011 16:44
Прочитал на геймдеве серию статей по сетевому программированию (только азы), как что устроено и кк работает. Всё понял, впринципе я как-то так это всё и представлял, но парочка вопросов всётаки появилось:...

1. Как правильно расположить "по старшинству" следующие понятия: порт физический (rj45), порт программный и сокет.
Как я представляю: Физический порт "имеет" 65000+ программных портов, а дальше... Сколько сокетов может быть вообще или их количество ограничено только uint long? 1 сокет можно биндить на 1 программный порт, но 1 программный порт может обслуживать больше чем 1 сoкет одновременно, или как?

2. Организация взаимодействия "клиент-сервер" tcp/ip... Сервер слушает порт через сокет А. Клиент посылает запрос. Сервер принимает пакет и создаёт новый сокет В для нового клиента и отвечает клиенту через него.
Собственно 3 момента:
2.1 На какой АДРЕС сервер отсылает пакет через сокет В, или это всё "привязывается" при создании сокета или как узнаётся?
2.2 Чтобы принять ответ сервера, клиент тоже должен слушать сокетом порт? Или сокет В изначально будет "нацелен" на порт клиента?
2.3 Когда клиент постоянно шлёт данные на сервер, то каждый пришедший пакет создаёт новый сокет? Не логично както. Или как клиенту узнать ид сервеного сокета В, созданного для этого клиента?
Автор: Rudia
Дата сообщения: 11.01.2011 09:51
Видать плохая статья, раз ничего не понятно.
1. Зависит от операционной системы.
Насчет пункта 2, советовал бы почитать про udp и tcp - сокеты.
2.1 В tcp-протоколе клиент устанавливает подключение с сервером, т.е. для каждого клиента создается слейв-сокет на сервере. Т.е. для каждого клиента - отдельная "линия связи". В udp-протоколе на сервере всего один сокет, но при каждом получении пакета мы может определить адрес отправителя и ответить ему.
2.2 Для соединения с сервером клиент создает свой сокет. Ему автоматически назначается какой-то порт(клиенту это не важно, он про это не думает). У клиента обычно всего 2 операции - записать в сокет, прочитать из сокета. Стандартные сокеты блокирующие, т.е. если клиент вызовет метод "прочитать", то он не вернет результат, пока не прочитает, либо пока сервер не разорвет соединение.
2.3 Ответ кроется в пункте 2.1. Схема взаимодействия клиент-сервер
tcp - подключение-запрос-ответ-запрос-......-ответ-отключение
upd - запрос-ответ-запрос-.....
Автор: akaGM
Дата сообщения: 11.01.2011 10:46
Wc3Exp
прально, достойный выбор, выбор настоящего... игрока
[C++ vs C#]

типа оффтоп...
Автор: Wc3Exp
Дата сообщения: 11.01.2011 19:17

Цитата:
прально, достойный выбор, выбор настоящего... игрока

Я понимаю, что ржачно читать мои вопросы, но далеко не всем дано всё понять с 1го прочтения.
Rudia
2.1 уже прочитал про accept и struct sockaddr_in... Ну и с udp и tcp тоже кратко ознакомился.

2.2 про асинхронные сокеты тоже прочитал, вот только не понял почему они рекомендованы только для клиента. Одно дело "подвесить" клиента с 1 игроком, другое - сервер с множеством игроков.
Моё предположение, что, как правило, серверная апликуха - консольная, значит у неё нету ГУИ, следовательно нету дескриптора окна ГУИ, на который "завязан" асинхронный сокет. Так? Если так, то как тогда решён вопрос с сервером?
А так с ответом клиенту я уже разобрался.

2.3 допустим я играю в игру по tcp. Я авторизировался через серверный порт А и сокет Ах (прослушиваемый олько), сервер для обмена со мной создаёт сокет Бх с каким портом? Думаю, что с портом: взятым из struct sockaddr_in после accept (быстрее всего), заранее "забитым" в код (мб мб)...
Так вот суть вопроса в том, что, сокет Бх будет закрыт сервером после единократой предачи-получения данных или когда я выйду из игры (отсоеденюсь от сервера)?

p.s. Сегодня привезут новую клаву и я буду эксперементировать. У нынешней 40% клавишь перестало работать после пролитого чая. Вобщем вопросы ещё будут...
Автор: akaGM
Дата сообщения: 11.01.2011 21:59
Wc3Exp
дурак ты...

я тебя на жабу сажал (добра желая), а уж а С++ и не предполагал...
правильный выбор (без всяких граф. смайликов) :)
Автор: Wc3Exp
Дата сообщения: 11.01.2011 22:06
akaGM
Я пробую на С++ т.к. он мне знаком, а С# имеет немного другой синтаксис...
Сначала буду пробовать как это всё вообще делается на знакомом синтаксисе, а в последствии никто не мешает перейти на C#... Я ещё только учусь, не более.
Автор: akaGM
Дата сообщения: 11.01.2011 22:21
Wc3Exp
самый простой ответ
С++ рулит...
тем более, что ты в курсах...
имхо, бери его, сиди на нём, и никаких сомнений...
Автор: Wc3Exp
Дата сообщения: 11.01.2011 23:35

Код:
SOCKET client_socket[client_max] , server_socket;
sockaddr_in ws_client_info[client_max] , ws_client_info;

client_socket[client_count] = accept(server_socket,????????,sizeof(sockaddr_in));
Автор: vlary
Дата сообщения: 11.01.2011 23:52
Wc3Exp
sockaddr_in saddr;
accept(server_socket, (struct sockaddr *) &saddr ,sizeof(sockaddr_in));
Вторым параметром ты должен передать ссылку на структуру типа sockaddr.
Автор: Wc3Exp
Дата сообщения: 12.01.2011 00:08
Спасибо, честно говоря уже запутался немного.
В таком виде всё работоспособно...

Код: client_socket[client_count] = accept(server_socket,(struct sockaddr*)&ws_client_info[client_count],&size);
Автор: vlary
Дата сообщения: 12.01.2011 00:14
Wc3Exp Правильно, третий параметр - тоже ссылка.
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
Автор: Wc3Exp
Дата сообщения: 12.01.2011 00:27
vlary
Так, хорошо... А что дальше делать? Как заставить серверное приложение обрабатывать эту тучу созданных сокетов? В примерах ничего не нашёл по этому моменту, в них дальше дестрой сокета и WSACleanup(), кстати что за функция, какой мусор она убирает?...

Для ясности, я в качестве освоения хочу сделать простенький чат на консольном сервере и консольном клиенте...
Автор: vlary
Дата сообщения: 12.01.2011 11:51
Wc3Exp Обычно серверное приложение открывает сокет и слушает на нем. Когда приходит клиентский запрос, по accept открывается новый сокет, создается новый процесс или поток, которому передается этот сокет для работы с клиентом. Далее уже дочерний процесс/поток осуществляет прием-передачу данных между клиентом и сервером. После завершения обмена, либо отключения клиента, либо тайм-аута, сокет закрывают командой close или shutdown. При завершении программы сервера закрывается слушающий сокет, и выполняется WSACleanup(), которая очищает буферы обмена, закрывает сокеты, которые забыли закрыть, и выгружает WS2_32.dl.
Вот простой примерчик многопоточного TCP сервера: Ссылка
Автор: Wc3Exp
Дата сообщения: 13.01.2011 03:51
vlary
Немного разобрался с потоками, написал простенькие, почти рабочие сервер и клиент, код пока индусский... но это пока обучаюсь.

Теперь такие вопросы возникли...
*Запущены и сервер и клиент.
1. Подсоединяюсь к серверу.
2. Сервер принимает коннект, создаёт сокет, создаёт поток и передаёт в него сокет.
3. Из созданного потока сервер отсылает клиенту сообщение об успешном коннекте (оно доходит до клиента). Созданный поток зацикливается на приём сообщения от клиента (этот момент видимо неправильный т.к. происходит какой-то бред).
4. Клиент в отдельном потоке принимает сообщение и выводит на экран. Приём тоже зациклен и тоже много бреда.
5. (вопрос) Куда посылать сообщение от клиента к серверу? Неужто на тот серверный слушающий сокет, к которому коннектился клиент? Т.к. больше некуда, то полагаю, что на него. Ведь на самом сервере созданный аccept'ом сокер биндится на структуру с его данными. Видимо это как-то взаимосвязано. Тогда наверное нужно в отдельном потоке выставлять слушающий сокет на приём данных (именно приём а не коннект)?

Ну ещё... Я так понял, что функции send(...) и recv(...) не блокируют программу, именно поэтому у меня получается много бреда, если её вызывать из бесконечного while(true)... Тогда как можно создать событие, что на какой-то сокет принял данные, или дорога только к асинхронным сокетам?
Автор: Rudia
Дата сообщения: 13.01.2011 10:01
Wc3Exp
Вызвали connect - получили номер сокета, далее работа с send() и recv(). Если хоть одна из функций возвращает ошибку - closesocket() и далее по новой - socket(), connect().

Цитата:
Ну ещё... Я так понял, что функции send(...) и recv(...) не блокируют программу, именно поэтому у меня получается много бреда, если её вызывать из бесконечного while(true)... Тогда как можно создать событие, что на какой-то сокет принял данные, или дорога только к асинхронным сокетам?

Изучать функцию select().
Автор: vlary
Дата сообщения: 13.01.2011 10:26
Wc3Exp Ну, в данном случае, как и написал уже Rudia, нужно либо использовать функцию select(), либо работать с событиями. Пример с использованием select() можно посмотреть здесь:
Ссылка
Автор: Wc3Exp
Дата сообщения: 15.01.2011 00:11
Что-то уже совсем ничего не понимаю. При попытке коннекта, со стороны клиента связь не устанавливается, но при этом на сервере accept успешно завершает свою функцию... Как так?

Добавлено:
Тестовое сообщение с сервера на клиент естественно не передаётся.
Автор: vlary
Дата сообщения: 15.01.2011 15:52
Wc3Exp Показывай, что делаешь, иначе - к телепатам. Обычно все учебные примеры работают на ура.
Автор: Wc3Exp
Дата сообщения: 15.01.2011 19:39
Не помещаю под тег //more// для удобства ковыряния.

Вот это код клиента:

Код: #include "stdafx.h"
#pragma comment (lib, "ws2_32.lib")    
#include <iostream>
#include <winsock2.h>

// globals
    sockaddr_in s_inf;
    int len = sizeof(struct sockaddr_in);
    SOCKET s;
    HANDLE InThr;
DWORD ThreadId;
    WSADATA wsa_dat;
    WSAEVENT sEvent;
    char IP[15];
// endglobals

DWORD WINAPI InThrWork(LPVOID lpParam)
{
    SOCKET s = (SOCKET)lpParam;
    char ch[256];
    int lch = sizeof(ch)*2;
    int eventCode = 0;
    WSABUF inbuff;
WSANETWORKEVENTS NetEvent;

    printf("%s","Thread start!\n"); //*
    while (true)
    {
        inbuff.buf = ch;
        inbuff.len = lch;
        printf("%s","Input thread : waiting data!\n"); //*
        eventCode = WSAWaitForMultipleEvents(1,&sEvent,FALSE,INFINITE,FALSE);
        printf("%s","Input thread : event run!\n"); //*
        WSAEnumNetworkEvents(s,sEvent,&NetEvent);
        if((NetEvent.lNetworkEvents & FD_READ) && (NetEvent.iErrorCode[FD_ACCEPT_BIT]==0))
        {
            printf("%s","Input thread : READ event!\n"); //*
            WSARecv(s,&inbuff,1,0,0,NULL,NULL);
            printf("%s",inbuff.buf);
        }
        if((NetEvent.lNetworkEvents & FD_CLOSE) && (NetEvent.iErrorCode[FD_ACCEPT_BIT]==0))
        {
            printf("%s","Input thread : CLOSE event!\n"); //*
            ExitThread(ThreadId);
        }
    }
return 0;
}
    
int main (int argc, char* argv[])
{
    char ch[256];
    int lch = sizeof(ch)*2;
    WSABUF outbuff;
    unsigned long bl = 0;


    if ( WSAStartup(2,&wsa_dat) !=0 )
    {
        puts("WSAStartup - False.\n");
        exit (1);
    }
    
if (wsa_dat.wVersion != 2)
    {
        puts("WSA Version - False.\n");
        WSACleanup ();
        exit (1);
    }

    s = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,NULL);

    if ((s==INVALID_SOCKET) | (s==SOCKET_ERROR))
    {        
        puts("SOCKET create - False.\n");
        exit (1);    
    }else
    {
        sEvent = WSACreateEvent();
        WSAEventSelect(s,sEvent,FD_READ | FD_CLOSE);
    }


    puts("Enter IP adress:");
    scanf("%15s",IP);
if (strlen(IP)<7)
    {
        strcpy(IP,"127.0.0.1");
    }

    s_inf.sin_family = AF_INET;
    s_inf.sin_port = htons(1501);
    s_inf.sin_addr.s_addr = inet_addr(IP);

    if (WSAConnect(s,(struct sockaddr*)&s_inf,sizeof(s_inf),NULL,NULL,0,0)==SOCKET_ERROR)
    {
        printf("%s","Connection - False.\n");
    }else
    {
        InThr = CreateThread(NULL,0,InThrWork,(LPVOID)s,0,&ThreadId);
    }

    puts("====================\n");
    while(true)
    {
        scanf("%512s",ch);
        outbuff.buf = ch;
        outbuff.len = lch;
        WSASend(s,&outbuff,1,0,0,NULL,NULL);
        puts("--------------------");
    }
return 0;
}
Автор: vlary
Дата сообщения: 15.01.2011 20:51
Wc3Exp Ну вот сразу же: в обработчике

Цитата:
 int client_index = (int)lpParam - 1;
и в главном потоке
Цитата:
NewTreade[client_count] = CreateThread(NULL,0,client_work,(LPVOID)client_count,0,&TreadeId[client_count]);

Ты не правильно параметры передаешь и читаешь.
В сервере должно быть (LPVOID) &client_count, а в клиентском потоке (int)*lpParam
Иначе обработчик вместо индекса сокета нового клиента получит хрен знает что.



Автор: Wc3Exp
Дата сообщения: 15.01.2011 22:27
vlary
Это упущение есть, но до него, из-за проблемы коннекта, дело ещё не дошло.

Вот скрин, на котором отображена попытка коннекта...

[more] [/more]

Кстати, спасибо за терпение и внимание, я думал будет совсем подругому...

Добавлено:
Поискал инфу по нету и выяснил, что даже если WSAConnec(...) = SOCKET_ERROR и при этом WSAGetLastError() возвращает 10035, то ещё не значит, что нету коннекта. В моём случае WSAConnec(...) устанавливает соединение в "скрытом" режиме.

Написал лесенку событий, отслеживание оных и вывод текста при их срабатывании.

Перед лесенкой воткнул:
WSAEnumNetworkEvents(s,sEvent,&NetEvent);

В каждый блок лесенки вписал строку:
printf("%s%i\n","Event id = ",NetEvent.lNetworkEvents);

И начлись чудеса!... При срабатывании события FD_CONNECT получаю текст в консоль:
Event Id = 18
(именно 18, не очепятка, при том, что FD_CONNECT = 16, а следующий по возрастанию только FD_CLOSE = 32)

Выбегаю опять инет, лажу по форума и статьям. Результат один - нету такого эвента.

Я даже не знаю что спросить, просто в недоумении.
Автор: vlary
Дата сообщения: 16.01.2011 14:14

Цитата:
Это упущение есть, но до него, из-за проблемы коннекта, дело ещё не дошло.
Как раз до него и дошло. Когда клиент коннектится на слушающий сокет, создается новый сокет, который передается новому потоку, который будет далее обслуживать это соединение. Но потоку передается вместо сокета полная ерунда, и соединения как такового не возникает.
Кстати, в Вижуал Студио имеется встроенный дебаггер, с помощью которой можно отслеживать выполнение программы по шагам. В том числе проследить значения переменных, передающихся и получаемых другими потоками. Рекомендую воспользоваться.
Автор: Wc3Exp
Дата сообщения: 16.01.2011 18:00
vlary
Всё дело было в WSARecv и WSASend (обе возвращали -1)... Заменил из на обычные recv и send и вновь связь наладилась (клиент принимает пересылку сервера при коннекте).

Осталось неясным как передать сообщение на сервер и как принять его на сервере. На какой порт и сокет посылать?
Клиент "знает" только адрес слушающего серверного порта и не имеет понятия о новом созданном сокете.

Как организовать пересылку данных на сервер?
Автор: vlary
Дата сообщения: 16.01.2011 19:05
Wc3Exp
Цитата:
Как организовать пересылку данных на сервер?
Тот же сокет, который у тебя использует функция WSAConnect, используется и для приема-отправки данных. Если это обмен по определенному алгоритму (слушается приветствие сервера, отправляется команда, принимаются данные, опять отправляется команда, принимаются данные, и так далее - то все делается просто. Если у клиента и сервера обмен асинхронный, тогда также используется select() или WSAWaitForMultipleEvents. Появились на сокете данные для чтения - считал, проанализировал, что-то ответил. Или сам что-то серверу отправил.

Автор: Wc3Exp
Дата сообщения: 16.01.2011 20:05
vlary
Немного не на том акцентировал внимание... Как принять данные на сервере?

Я на сервере, в отдельном потоке, пробовал контролировать listen-сокет на событие FD_WRITE...

При запуске сервера срабатывала WSAWaitForMultipleEvents с событием FD_WRITE и "заливала" экран консоли потоком бессмысленный данных.
Автор: vlary
Дата сообщения: 17.01.2011 01:16
Wc3Exp Причем здесь listen-сокет? Весь обмен с клиентом осуществляется в потоке-обработчике. client_work чтением-записью в client_soc. Опять же в простейшем случае блокирующих сокетов при запуске client_work в сокет пишется что нибудь типа 220 Ready,
и затем выполняется команда чтения. Получил от клиента какие-то данные, интерпретировал их как команду, выполнил, результат отправил в сокет, либо если это неправильная команда, отправил что-нибудь типа 500 Error.
Простейшим примером является эхо-сервер. Все, что он получает от клиента, сразу отправляет ему обратно. Можешь реализовать такой алгоритм, а для проверки просто зайти телнетом на порт своего сервера и что-то ему передать.
Автор: Wc3Exp
Дата сообщения: 17.01.2011 06:35
vlary
Что-то я начинаю подозревать, что я установил баганую среду... Очень много финтов с кодом и не только:
- после перезапуска может выдать до 30 "вниманий" при компайле, рекомпайл - и всё в порядке.
- ни с того ни с сего появляется 1 ошибка... Пропадает только после рестарта компа.
- меняю кпримеру WSASend на send, компайл, билд, меняю обратно как было, компайл, и получаю 2-5 ошибок... Лечится только ребутом компа.
- создаю новый проект, аналогичный предыдущему по опциям, копипаст кода - до 15 ощибок на пустом месте. Сэйв, выход, запуск среды, лоад - всё в норме.
- и таких прелестей море...

Я не отмахиваюсь, у меня пиратская среда... Вирей быть не может - антивирь "по доступу", файрволл "всё блокировать и уведомлять при попытке" + регулярные "рейды" антивируса по критическим местам.

Могла MS VC++ криво встать или чего-то нужно настроить?
Автор: vlary
Дата сообщения: 17.01.2011 10:38
Wc3Exp
Цитата:
Могла MS VC++ криво встать или чего-то нужно настроить?
Может быть криво настроен проект, не подключены пути к хидерам, библиотекам. Ворнинги - дело нормальное, а ошибки нужно анализировать конкретно. Разработка программ - это не столько ремесло, сколько искусство, со всеми вытекающими отсюда последствиями.

Автор: Wc3Exp
Дата сообщения: 21.01.2011 06:12
vlary
Расслабил мозг пару дней и слёту нашёл недоработку...

Вобщем суть кратка: перед использованием эвентов неплохобы их создавать... WSACreateEvent()...

Теперь всё отлично, сервер уверенно принимает входящие сообщения и успешно спамит на все активные, динамические сокеты.

Осталось исправить мелкие недоработки и консольный чат будет завершён. Ура, товарищи! )
Автор: akaGM
Дата сообщения: 21.01.2011 07:24
Wc3Exp

Цитата:
Ура, товарищи! )

лихо ты въехал, уважаю...
удачи!

---

vlary

Цитата:
Ворнинги - дело нормальное...

нормальное, но тем не менее от них тоже нужно избавляться, если уж компилятор на что-то указывает, это точно непорядок (раз уж в него это вшили, формализовали, и он это что-то обнаружил)...
сошлюсь на любимой мною пример во флуде:

Цитата:
когда французская ракета Ариан грохнулась на взлёте из-за ошибки на Сях в вычислении эйлеровых углов (год 2001-2002)...

ребята поленились тайпкаст float-double сделать...

Страницы: 12

Предыдущая тема: Компиляция QEMU


Форум Ru-Board.club — поднят 15-09-2016 числа. Цель - сохранить наследие старого Ru-Board, истории становления российского интернета. Сделано для людей.