Создание консольного чата
От: Olegator  
Дата: 04.10.03 10:10
Оценка:
Есть две программы:

// Chat_Server.cpp

#include <iostream>
#include <vector>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

using namespace std;

char* ReadLine(SOCKET source);

int main()
{
    WSADATA wd;
    WSAStartup(WINSOCK_VERSION, &wd);

    SOCKET server = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    SOCKADDR_IN name;
    name.sin_family         = PF_INET;
    name.sin_addr.s_addr    = inet_addr("127.0.0.1");
    name.sin_port           = htons(4000);

    bind(server, (LPSOCKADDR)&name, sizeof(name));
    listen(server, SOMAXCONN);

    SOCKET client = accept(server, NULL, NULL);

    cout << ReadLine(client);

    closesocket(server);
    closesocket(client);
    WSACleanup();

    cin.get();

    return 0;
}

char* ReadLine(SOCKET source)
{
    vector<char> data;
    char buffer;
    while(recv(source, &buffer, 1, 0) != 0)
        data.push_back(buffer);

    int size    = static_cast<int>(data.size());
    char* line    = new char[size + 1];
    memset(line, 0, size + 1);

    for(int i = 0; i < size; i++)
        line[i] = data[i];

    return line;
}


// Chat_Client.cpp

#include <iostream>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

using namespace std;

int main()
{
    WSADATA wd;
    WSAStartup(WINSOCK_VERSION, &wd);

    SOCKET client = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    SOCKADDR_IN name;
    name.sin_family         = PF_INET;
    name.sin_addr.s_addr    = inet_addr("127.0.0.1");
    name.sin_port           = htons(4000);

    connect(client, (LPSOCKADDR)&name, sizeof(name));

    send(client, "Text of message", sizeof("Text of message"), 0);

    closesocket(client);
    WSACleanup();

    cin.get();

    return 0;
}


Я пишу консольный чат. А вот мой вопрос: как сделать так, чтобы сервер мог не только принимать, но и передавать, а клиент — не только передавать, но и принимать. То есть взаимодействие будет такое: клиент передаёт сообщение на сервер, сервер смотрит, кому предназначается сообщение, и передаёт его клиенту-адресату.

Буду признателен за ответ.
RSDN@Home
Re: Создание консольного чата
От: GarikTot  
Дата: 04.10.03 10:54
Оценка:
Здравствуйте, 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));
// аналогично: в этот сокет можно не только писать, но и читать из него

O>


Таким образом, у тебя уже всё есть
Re[2]: Создание консольного чата
От: Olegator  
Дата: 04.10.03 14:20
Оценка:
Здравствуйте, 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>Таким образом, у тебя уже всё есть


А как реализовать эти действия? Можно пример, а то у меня почему-то не работает...
RSDN@Home
Re: Создание консольного чата
От: Vamp Россия  
Дата: 04.10.03 17:57
Оценка:
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);

closesocket(client);

[/cc]

Например, так.
Да здравствует мыло душистое и веревка пушистая.
Re[2]: Создание консольного чата
От: Olegator  
Дата: 05.10.03 15:52
Оценка:
Здравствуйте, 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. Обратите внимание на стиль программирования (в частности, мемори лики и спользование контейнеров).
RSDN@Home
Re[3]: Создание консольного чата
От: GarikTot  
Дата: 06.10.03 07:48
Оценка:
Здравствуйте, Olegator, Вы писали:

O>А как реализовать эти действия? Можно пример, а то у меня почему-то не работает...


Покажи код, который у тебя не работает... возможно, что-нибудь придумаем
Re[3]: Создание консольного чата
От: DOOM Россия  
Дата: 06.10.03 07:51
Оценка:
Здравствуйте, Olegator, Вы писали:
[skipped]

Такой чат должен быть построен на асинхронных сокетах(так проще общаться, чем постоянно говоря "прием"), посему лучшим решением будет использование WSAAsyncSelect — смотри там.
Re[3]: Создание консольного чата
От: Vamp Россия  
Дата: 06.10.03 08:57
Оценка:
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_type
    char* 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 прямо из буфера - веткора.*/
}
Да здравствует мыло душистое и веревка пушистая.
Re[4]: Создание консольного чата
От: Olegator  
Дата: 06.10.03 13:01
Оценка:
Здравствуйте, DOOM, Вы писали:

DOO>Здравствуйте, Olegator, Вы писали:

DOO>[skipped]

DOO> Такой чат должен быть построен на асинхронных сокетах(так проще общаться, чем постоянно говоря "прием"), посему лучшим решением будет использование WSAAsyncSelect — смотри там.


Что значит "говоря "приём"? Объясни, пожалуйста, как построить чат на асинхронных сокетах (как их вообще создавать?).
RSDN@Home
Re[5]: Создание консольного чата
От: GarikTot  
Дата: 07.10.03 14:57
Оценка:
Здравствуйте, Olegator, Вы писали:

O>Что значит "говоря "приём"? Объясни, пожалуйста, как построить чат на асинхронных сокетах (как их вообще создавать?).


Асинхронные или неблокирующие сокеты — это сокеты, при работе с которыми функции Socket API возвращают немедленно, а не блокируют исполнение программы... Сделать сокет неблокирующим можно, например, так:

   int val, size = sizeof(int);

   // Делаю сокет неблокирующим
   val = 1;
   if(ioctl(sock, FIONBIO, &val) < 0){
      // здесь обработка ошибки
   }


Удачи!
Re[6]: Создание консольного чата
От: Olegator  
Дата: 08.10.03 16:36
Оценка:
Здравствуйте, 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, то вообще ничего не выводится. Если можно, приведи пример, пожалуйста.
RSDN@Home
Re[4]: Создание консольного чата
От: Olegator  
Дата: 08.10.03 17:05
Оценка:
Здравствуйте, GarikTot, Вы писали:

GT>Здравствуйте, Olegator, Вы писали:


O>>А как реализовать эти действия? Можно пример, а то у меня почему-то не работает...


GT>Покажи код, который у тебя не работает... возможно, что-нибудь придумаем


// клиент

send(client, "From client", sizeof("From client"), 0);
cout << Receive(client);


// сервер

cout << Receive(client);
send(client, "From server", sizeof("From server"), 0);


Отображается чистый экран...
RSDN@Home
Re[7]: Создание консольного чата
От: GarikTot  
Дата: 08.10.03 17:06
Оценка:
Здравствуйте, 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 avaliable
int 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:  // Interrupt
            continue;
         case EAGAIN: // No data is avaliable
          if(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:  // Interrupt
            continue;
         case EAGAIN: // No data is avaliable
            if(count++ < 5) continue; else return RW_NODATA;
         }
         return res;
      }
      count = 0;
      rdIdx += res;
      nWaitingSize = rdSize - rdIdx;
      if(rdIdx == rdSize) break;
   }
   return rdIdx;
}


А вообще...

Такие вещи делаются обычно так:

В основном потоке создаётся сокет и постоянно прослушивается (select'ом), при очередном подключении делаем accept() и выделяем работу с полученным сокетом в отдельный поток... Основной поток тем временем опять продолжает слушать тот же сокет...
Re[5]: Создание консольного чата
От: GarikTot  
Дата: 08.10.03 17:09
Оценка:
Здравствуйте, Olegator, Вы писали:

O>
O>// клиент

O>send(client, "From client", sizeof("From client"), 0);
O>cout << Receive(client);
O>


O>
O>// сервер

O>cout << Receive(client);
O>send(client, "From server", sizeof("From server"), 0);
O>


O>Отображается чистый экран...


Странно... Это должно работать...
Покажи больше: как ты их открываешь, когда закрываешь и т. п.
Re[6]: Создание консольного чата
От: VVV Россия  
Дата: 09.10.03 12:18
Оценка:
Здравствуйте, GarikTot, Вы писали:
O>>
O>>// клиент

O>>send(client, "From client", sizeof("From client"), 0);
O>>cout << Receive(client);
O>>


O>>
O>>// сервер

O>>cout << Receive(client);
O>>send(client, "From server", sizeof("From server"), 0);
O>>


O>>Отображается чистый экран...


Следует заметить, что sizeof("From server") это всего 4, надо использовать strlen("From server\n")+1 и если выводишь в cout то дополняй строку '\n', чтобы буфер вывелся на экран.
Re[4]: Создание консольного чата
От: Olegator  
Дата: 09.10.03 14:23
Оценка:
Здравствуйте, Vamp, Вы писали:

V><...>В начале каждого пакета сообщай его размер или используй ограничетль, например стандартный нулевой байт.<...>


Не понимаю, вроде последовательность символов в кавычках уже содержит нулевой символ в конце. Поэтому строка так и называется — "null-terminated"... Что за нулевой байт ты имеешь в виду?

V>Альтернативой будет использование неблокирующих сокетов, но это посложнее либо платформо-зависимо.


Неблокирующие сокеты — это асинхронные сокеты или как?
RSDN@Home
Re[5]: Создание консольного чата
От: Vamp Россия  
Дата: 09.10.03 14:36
Оценка:
O>Не понимаю, вроде последовательность символов в кавычках уже содержит нулевой символ в конце. Поэтому строка так и называется — "null-terminated"... Что за нулевой байт ты имеешь в виду?
Ну, если ты только строковые литералы передавать будешь — тогда думать не о чем.

V>>Альтернативой будет использование неблокирующих сокетов, но это посложнее либо платформо-зависимо.

O>Неблокирующие сокеты — это асинхронные сокеты или как?
Асихнронных сокетов не бывает Асинхронными бывают опреации. А сокеты — блокирующие и неблокирующие.
Да здравствует мыло душистое и веревка пушистая.
Re[8]: Создание консольного чата
От: Olegator  
Дата: 09.10.03 17:07
Оценка:
Здравствуйте, GarikTot, Вы писали:

GT>Такие вещи делаются обычно так:


GT>В основном потоке создаётся сокет и постоянно прослушивается (select'ом), при очередном подключении делаем accept() и выделяем работу с полученным сокетом в отдельный поток... Основной поток тем временем опять продолжает слушать тот же сокет...


1.Как это реализовать, чтобы удовлетворить потребности консольного чата?

2. И всё же: почему, если я в свой исходный код (1-е сообщение) в серверную часть добавляю функцию send, а в клиентскую — Receive, то как в серверной, так и в клиентской консоли ничего выводится? (Сначала открываю сервер, потом — клиент.) Как этого избежать? Вот код:

// сервер

cout << Receive(client);
send(client, "From server\n", strlen("From server\n") + 1, 0);


// клиент

send(client, "From client\n", strlen("From client\n") + 1, 0);
cout << Receive(client);


Хочу заметить, что, если вышеописанных действий не производить, то всё отлично работает! (То есть в серверной консоли отображается строка "From client\n".)
RSDN@Home
Re[9]: Создание консольного чата
От: GarikTot  
Дата: 10.10.03 16:56
Оценка:
Здравствуйте, 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 и т. д.), а ещё, а ещё .....

В общем советую почитать литературу по созданию сетевых приложений
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.