Я пишу консольный чат. А вот мой вопрос: как сделать так, чтобы сервер мог не только принимать, но и передавать, а клиент — не только передавать, но и принимать. То есть взаимодействие будет такое: клиент передаёт сообщение на сервер, сервер смотрит, кому предназначается сообщение, и передаёт его клиенту-адресату.
Здравствуйте, Olegator, Вы писали:
O>Есть две программы:
O>
O>// Chat_Server.cpp
O> SOCKET client = accept(server, NULL, NULL);
// из этого сокета можно не только читать, но и писать в него
O>}
O>
O>
O>// Chat_Client.cpp
O> SOCKET client = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
O> connect(client, (LPSOCKADDR)&name, sizeof(name));
// аналогично: в этот сокет можно не только писать, но и читать из него
Здравствуйте, GarikTot, Вы писали:
GT>Здравствуйте, Olegator, Вы писали:
O>>Есть две программы:
O>>
O>>// Chat_Server.cpp
O>> SOCKET client = accept(server, NULL, NULL);
GT>// из этого сокета можно не только читать, но и писать в него
O>>}
O>>
O>>
O>>// Chat_Client.cpp
O>> SOCKET client = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
O>> connect(client, (LPSOCKADDR)&name, sizeof(name));
GT>// аналогично: в этот сокет можно не только писать, но и читать из него
O>>
GT>Таким образом, у тебя уже всё есть
А как реализовать эти действия? Можно пример, а то у меня почему-то не работает...
1. Обратите внимание на стиль программирования (в частности, мемори лики и спользование контейнеров).
2.
[cc]
//serever:
<skip>
SOCKET client = accept(server, NULL, NULL);
out << ReadLine(client); send(client, "response", sizeof(response), 0)
closesocket(client);
<skip>
//client:
<skip>
connect(client, (LPSOCKADDR)&name, sizeof(name));
send(client, "Text of message", sizeof("Text of message"), 0);
char buffer[VERY_BIG_BUFFER];
recv(client, buffer, VERY_BIG_BUFFER, 0);
Здравствуйте, Vamp, Вы писали:
V>1. Обратите внимание на стиль программирования (в частности, мемори лики и спользование контейнеров). V>2. V>[cc] V>//serever:
<skip>> V>SOCKET client = accept(server, NULL, NULL); V>out << ReadLine(client); V>send(client, "response", sizeof(response), 0) V>closesocket(client);
<skip>>
V>//client:
<skip>>
V>connect(client, (LPSOCKADDR)&name, sizeof(name)); V>send(client, "Text of message", sizeof("Text of message"), 0); V> V>char buffer[VERY_BIG_BUFFER]; V>recv(client, buffer, VERY_BIG_BUFFER, 0); V> V>closesocket(client);
V>[/cc]
V>Например, так.
Это я понимаю. Но я хочу, чтобы, допустим, клиент сначала передавал, потом принимал, потом снова передавал, а сервер, соответственно, принимал-передавал-принимал.
Если я делаю очереди из send'ов и recv'ов, то вообще ни одно из сообщений не отображается!
P. S. Объясни, что означает, а то я не профи : V>1. Обратите внимание на стиль программирования (в частности, мемори лики и спользование контейнеров).
Такой чат должен быть построен на асинхронных сокетах(так проще общаться, чем постоянно говоря "прием"), посему лучшим решением будет использование WSAAsyncSelect — смотри там.
O>Если я делаю очереди из send'ов и recv'ов, то вообще ни одно из сообщений не отображается!
Просто следи, чтобы сокеты попадали в тупик. То есть, передача — прием — передача — прием. В начале каждого пакета сообщай его размер или используй ограничетль, например стандартный нулевой байт. Таким образом после отправки пакета передатчик начинает слушать. В свою очередь приемник, определив конец пакета, отправляет данные.
Альтернативой будет использование неблокирующих сокетов, но это посложнее либо платформо-зависимо.
O>P. S. Объясни, что означает, а то я не профи : V>>1. Обратите внимание на стиль программирования (в частности, мемори лики и спользование контейнеров).
Вот твоя функция:
char* ReadLine(SOCKET source)
{
vector<char> data;
char buffer;
while(recv(source, &buffer, 1, 0) != 0)
data.push_back(buffer); //Здесь зачем-то читаешь по одному символу. Читай помногу сразу в вектор! Только вектор не забывай resize'ить.int size = static_cast<int>(data.size()); // Лишнее приведение. Определи size как vector<char>::size_typechar* line = new char[size + 1]; //Общее правило - выделил, а удалять кто будет? Зачем тебе char*, если есть std::string?
memset(line, 0, size + 1);
for(int i = 0; i < size; i++)
line[i] = data[i]; // А тут лучше воспользоваться std::copy, но см. примечаниеreturn line;
/*На самом деле, тебе вообще не нужна строка. Создаешь возвращаемый std::string прямо из буфера - веткора.*/
}
Здравствуйте, DOOM, Вы писали:
DOO>Здравствуйте, Olegator, Вы писали: DOO>[skipped]
DOO> Такой чат должен быть построен на асинхронных сокетах(так проще общаться, чем постоянно говоря "прием"), посему лучшим решением будет использование WSAAsyncSelect — смотри там.
Что значит "говоря "приём"? Объясни, пожалуйста, как построить чат на асинхронных сокетах (как их вообще создавать?).
Здравствуйте, Olegator, Вы писали:
O>Что значит "говоря "приём"? Объясни, пожалуйста, как построить чат на асинхронных сокетах (как их вообще создавать?).
Асинхронные или неблокирующие сокеты — это сокеты, при работе с которыми функции Socket API возвращают немедленно, а не блокируют исполнение программы... Сделать сокет неблокирующим можно, например, так:
int val, size = sizeof(int);
// Делаю сокет неблокирующим
val = 1;
if(ioctl(sock, FIONBIO, &val) < 0){
// здесь обработка ошибки
}
Здравствуйте, GarikTot, Вы писали:
GT>Здравствуйте, Olegator, Вы писали:
O>>Что значит "говоря "приём"? Объясни, пожалуйста, как построить чат на асинхронных сокетах (как их вообще создавать?).
GT>Асинхронные или неблокирующие сокеты — это сокеты, при работе с которыми функции Socket API возвращают немедленно, а не блокируют исполнение программы... Сделать сокет неблокирующим можно, например, так:
GT>
GT> int val, size = sizeof(int);
GT> // Делаю сокет неблокирующим
GT> val = 1;
GT> if(ioctl(sock, FIONBIO, &val) < 0){
GT> // здесь обработка ошибки
GT> }
GT>
GT>Удачи!
За это, конечно, огромное спасибо! Но всё же, как мне решить мою задачу: клиент передаёт — сервер принимает, потом наоборот, и так много раз. Если я делаю очередь из функций send и recv, то вообще ничего не выводится. Если можно, приведи пример, пожалуйста.
Здравствуйте, GarikTot, Вы писали:
GT>Здравствуйте, Olegator, Вы писали:
O>>А как реализовать эти действия? Можно пример, а то у меня почему-то не работает...
GT>Покажи код, который у тебя не работает... возможно, что-нибудь придумаем
Здравствуйте, Olegator, Вы писали:
O>Здравствуйте, GarikTot, Вы писали:
O>За это, конечно, огромное спасибо! Но всё же, как мне решить мою задачу: клиент передаёт — сервер принимает, потом наоборот, и так много раз. Если я делаю очередь из функций send и recv, то вообще ничего не выводится. Если можно, приведи пример, пожалуйста.
Вот, например, простенькая функция для записи в неблокирующий сокет:
/*
// Return values for sockRead and sockWrite:
// >0 - number of bytes was reading/writing#define RW_BREAK 0 // connection is breaked#define RW_ERROR -1 // some io error#define RW_NODATA -2 // no data is avaliableint sockWrite(int sock, const char *wrBuf, int wrSize, int& wrIdx)
{
wrIdx = 0;
int nWaitingSize = wrSize, res = 0, count = 0;
for(;;){
errno = 0;
res = write(sock, (wrBuf + wrIdx), nWaitingSize);
if(res < 1){
switch(errno){
case EINTR: // Interruptcontinue;
case EAGAIN: // No data is avaliableif(count++ < 5) continue; else return RW_NODATA;
}
return res;
}
count = 0;
wrIdx += res;
nWaitingSize = wrSize - wrIdx;
if(wrIdx == wrSize) break;
}
return wrIdx;
}
ноль вернётся только в том случае, когда соединение было закрыто на другом конце
при обрыве связи write вернёт ошибку только при переполнении буфера (обычно 8k)
аналогично для чтения:
int sockRead(int sock, char* rdBuf, int rdSize, int& rdIdx)
{
rdIdx = 0;
memset(rdBuf, 0, rdSize);
int nWaitingSize = rdSize, res = 0, count = 0;
for(;;){
errno = 0;
res = read(sock, (rdBuf + rdIdx), nWaitingSize);
if(res < 1){
switch(errno){
case EINTR: // Interruptcontinue;
case EAGAIN: // No data is avaliableif(count++ < 5) continue; else return RW_NODATA;
}
return res;
}
count = 0;
rdIdx += res;
nWaitingSize = rdSize - rdIdx;
if(rdIdx == rdSize) break;
}
return rdIdx;
}
А вообще...
Такие вещи делаются обычно так:
В основном потоке создаётся сокет и постоянно прослушивается (select'ом), при очередном подключении делаем accept() и выделяем работу с полученным сокетом в отдельный поток... Основной поток тем временем опять продолжает слушать тот же сокет...
Следует заметить, что sizeof("From server") это всего 4, надо использовать strlen("From server\n")+1 и если выводишь в cout то дополняй строку '\n', чтобы буфер вывелся на экран.
Здравствуйте, Vamp, Вы писали:
V><...>В начале каждого пакета сообщай его размер или используй ограничетль, например стандартный нулевой байт.<...>
Не понимаю, вроде последовательность символов в кавычках уже содержит нулевой символ в конце. Поэтому строка так и называется — "null-terminated"... Что за нулевой байт ты имеешь в виду?
V>Альтернативой будет использование неблокирующих сокетов, но это посложнее либо платформо-зависимо.
Неблокирующие сокеты — это асинхронные сокеты или как?
O>Не понимаю, вроде последовательность символов в кавычках уже содержит нулевой символ в конце. Поэтому строка так и называется — "null-terminated"... Что за нулевой байт ты имеешь в виду?
Ну, если ты только строковые литералы передавать будешь — тогда думать не о чем.
V>>Альтернативой будет использование неблокирующих сокетов, но это посложнее либо платформо-зависимо. O>Неблокирующие сокеты — это асинхронные сокеты или как?
Асихнронных сокетов не бывает Асинхронными бывают опреации. А сокеты — блокирующие и неблокирующие.
Здравствуйте, GarikTot, Вы писали:
GT>Такие вещи делаются обычно так:
GT>В основном потоке создаётся сокет и постоянно прослушивается (select'ом), при очередном подключении делаем accept() и выделяем работу с полученным сокетом в отдельный поток... Основной поток тем временем опять продолжает слушать тот же сокет...
1.Как это реализовать, чтобы удовлетворить потребности консольного чата?
2. И всё же: почему, если я в свой исходный код (1-е сообщение) в серверную часть добавляю функцию send, а в клиентскую — Receive, то как в серверной, так и в клиентской консоли ничего выводится? (Сначала открываю сервер, потом — клиент.) Как этого избежать? Вот код:
Хочу заметить, что, если вышеописанных действий не производить, то всё отлично работает! (То есть в серверной консоли отображается строка "From client\n".)
Здравствуйте, Olegator, Вы писали:
O>Здравствуйте, GarikTot, Вы писали:
O>1.Как это реализовать, чтобы удовлетворить потребности консольного чата?
Ну например так:
1) В основном потоке сервера
// ...int sdServer = socket(..);
bind(...);
listen(...)
// ...
//к этому моменту создан блокирующий сокет
fd_set ready;
int res(0);
for(;;){
FD_ZERO(&ready);
FD_SET(sdServer, &ready);
// ждём очередного клиента
res = select(sdServer + 1, &ready, 0, 0, NULL); // здесь можно поставить тайм-аут при желанииif(res < 0){ //-------------------------- error
// обрабатываем ошибку
}else if(res == 0){ //------------------- timeout
// здесь обрабатываем тайм-аут, если конечно его выставляли
}
if(FD_ISSET(sdServer, &ready)){
// к нам кто-то подсоединилсяint sdClient = accept(...);
// здесь выделяем работу с новым клиентом в отдельный поток
}
}
2) В потоке, работающем с клиентом
Здесь можно реализовать по-разному:
2.1) можно создать два разных потока — для чтения и для записи (асинхронный ввод/вывод)
тогда в одном потоке просточитаешь (recv/read),
а в др. пишешь (send/write) по мере необходимости
2.2) делаешь сокет неблокирующим и с помощью ф-чий чтения/записи неблокирующих сокетов (примеры я приводил в предыдущих постах) читаешь и, соответственно, пишешь то, что нужно. Здесь также можно применять ф-ю select() для проверки доступности данных...
Удачи!
зы.
Это сильно упрощённая схема... В реальности обычно комбинируют различные подходы... А ещё нужно обрабатывать сигналы (типа SIGIO, SIGPIPE и т. д.), а ещё, а ещё .....
В общем советую почитать литературу по созданию сетевых приложений