Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Сети > клиент+сервер


Автор: _par 25.1.2007, 18:24
Недавно начал учить C++ пытаюсь написать программу типа клиент сервер под windows.
Нашел примеры клиента и сервера тут проблема в том , что неполучается откомпилировать TCP сервер. Изпользую Dev-C++.


Клиент

Код


 // Пример простого TCP – эхо сервера

  #include <stdio.h>
  #include <winsock2.h>  // Wincosk2.h должен быть 
          // подключен раньше windows.h!
  #include <windows.h>

  #define MY_PORT    666
           // Порт, который слушает сервер

  // макрос для печати количества активных
  // пользователей 
  #define PRINTNUSERS if (nclients)\
  printf("%d user on-line\n",nclients);\
  else printf("No User on line\n");

  // прототип функции, обслуживающий
  // подключившихся пользователей
  DWORD WINAPI SexToClient(LPVOID client_socket);

  // глобальная переменная – количество
  // активных пользователей 
  int nclients = 0;

  int main(int argc, char* argv[])
  {
    char buff[1024];    // Буфер для различных нужд

    printf("TCP SERVER DEMO\n");

    // Шаг 1 - Инициализация Библиотеки Сокетов
    // Т.к. возвращенная функцией информация
    // не используется ей передается указатель на
    // рабочий буфер, преобразуемый
    // к указателю  на структуру WSADATA.
    // Такой прием позволяет сэкономить одну
    // переменную, однако, буфер должен быть не менее
    // полкилобайта размером (структура WSADATA
    // занимает 400 байт)
    if (WSAStartup(0x0202,(WSADATA *) &buff[0])) 
    {
      // Ошибка!
          printf("Error WSAStartup %d\n",
             WSAGetLastError());
      return -1;
    }

    // Шаг 2 - создание сокета
    SOCKET mysocket;
    // AF_INET     - сокет Интернета
    // SOCK_STREAM  - потоковый сокет (с
    //      установкой соединения)
    // 0      - по умолчанию выбирается TCP протокол
    if ((mysocket=socket(AF_INET,SOCK_STREAM,0))<0)
    {
      // Ошибка!
      printf("Error socket %d\n",WSAGetLastError());
      WSACleanup();
        // Деиницилизация библиотеки Winsock
      return -1;
    }

    // Шаг 3 связывание сокета с локальным адресом
    sockaddr_in local_addr;
    local_addr.sin_family=AF_INET;
    local_addr.sin_port=htons(MY_PORT);
             // не забываем о сетевом порядке!!!
    local_addr.sin_addr.s_addr=0;
             // сервер принимает подключения
             // на все IP-адреса

    // вызываем bind для связывания
    if (bind(mysocket,(sockaddr *) &local_addr,
                sizeof(local_addr)))
    {
      // Ошибка
      printf("Error bind %d\n",WSAGetLastError());
      closesocket(mysocket;  // закрываем сокет!
      WSACleanup();
      return -1;
    }

    // Шаг 4 ожидание подключений
    // размер очереди – 0x100
    if (listen(mysocket, 0x100))
    {
      // Ошибка
      printf("Error listen %d\n",WSAGetLastError());
      closesocket(mysocket);
      WSACleanup();
      return -1;
    }

    printf("Ожидание подключений\n");

    // Шаг 5 извлекаем сообщение из очереди
    SOCKET client_socket;    // сокет для клиента
    sockaddr_in client_addr;    // адрес клиента
              // (заполняется системой)

    // функции accept необходимо передать размер
    // структуры
    int client_addr_size=sizeof(client_addr);

    // цикл извлечения запросов на подключение из
    // очереди
    while((client_socket=accept(mysocket, (sockaddr *)
            &client_addr, &client_addr_size)))
    {
      nclients++;      // увеличиваем счетчик
              // подключившихся клиентов

      // пытаемся получить имя хоста
      HOSTENT *hst;
      hst=gethostbyaddr((char *)
          &client_addr.sin_addr.s_addr,4, AF_INET);

      // вывод сведений о клиенте
      printf("+%s [%s] new connect!\n",
      (hst)?hst->h_name:"",
      inet_ntoa(client_addr.sin_addr));
      PRINTNUSERS

      // Вызов нового потока для обслужвания клиента
      // Да, для этого рекомендуется использовать
      // _beginthreadex но, поскольку никаких вызов
      // функций стандартной Си библиотеки поток не
      // делает, можно обойтись и CreateThread
      DWORD thID;
      CreateThread(NULL,NULL,SexToClient,
              &client_socket,NULL,&thID);
    }
    return 0;
  }

  // Эта функция создается в отдельном потоке и
  // обсуживает очередного подключившегося клиента
  // независимо от остальных
  DWORD WINAPI SexToClient(LPVOID client_socket)
  {
    SOCKET my_sock;
    my_sock=((SOCKET *) client_socket)[0];
    char buff[20*1024];
    #define sHELLO "Hello, Sailor\r\n"

    // отправляем клиенту приветствие 
    send(my_sock,sHELLO,sizeof(sHELLO),0);

    // цикл эхо-сервера: прием строки от клиента и
    // возвращение ее клиенту
    while( (int bytes_recv=
                 recv(my_sock,&buff[0],sizeof(buff),0))
              && bytes_recv !=SOCKET_ERROR)
      send(my_sock,&buff[0],bytes_recv,0);

    // если мы здесь, то произошел выход из цикла по
    // причине возращения функцией recv ошибки –
    // соединение клиентом разорвано
    nclients--; // уменьшаем счетчик активных клиентов
    printf("-disconnect\n"); PRINTNUSERS

    // закрываем сокет
    closesocket(my_sock);
    return 0;
  }




Сервер


Код

// Пример простого TCP клиента
  #include <stdio.h>
  #include <string.h>
  #include <winsock2.h>
  #include <windows.h>


  #define PORT 666
  #define SERVERADDR "127.0.0.1"

  int main(int argc, char* argv[])
  {
    char buff[1024];
    printf("TCP DEMO CLIENT\n");

    // Шаг 1 - инициализация библиотеки Winsock
    if (WSAStartup(0x202,(WSADATA *)&buff[0]))
    {
      printf("WSAStart error %d\n",WSAGetLastError());
      return -1;
    }

    // Шаг 2 - создание сокета
    SOCKET my_sock;
    my_sock=socket(AF_INET,SOCK_STREAM,0);
    if (my_sock < 0)
    {
      printf("Socket() error %d\n",WSAGetLastError());
      return -1;
    }

    // Шаг 3 - установка соединения

    // заполнение структуры sockaddr_in
    // указание адреса и порта сервера
    sockaddr_in dest_addr;
    dest_addr.sin_family=AF_INET;
    dest_addr.sin_port=htons(PORT);
    HOSTENT *hst;

    // преобразование IP адреса из символьного в
    // сетевой формат
    if (inet_addr(SERVERADDR)!=INADDR_NONE)
      dest_addr.sin_addr.s_addr=inet_addr(SERVERADDR);
    else
      // попытка получить IP адрес по доменному
      // имени сервера
      if (hst=gethostbyname(SERVERADDR))
      // hst->h_addr_list содержит не массив адресов,
      // а массив указателей на адреса
      ((unsigned long *)&dest_addr.sin_addr)[0]=
        ((unsigned long **)hst->h_addr_list)[0][0];
      else 
      {
        printf("Invalid address %s\n",SERVERADDR);
        closesocket(my_sock);
        WSACleanup();
        return -1;
      }

    // адрес сервера получен – пытаемся установить
    // соединение 
    if (connect(my_sock,(sockaddr *)&dest_addr,
                sizeof(dest_addr)))
    {
      printf("Connect error %d\n",WSAGetLastError());
      return -1;
    }

    printf("Соединение с %s успешно установлено\n\
    Type quit for quit\n\n",SERVERADDR);

    // Шаг 4 - чтение и передача сообщений
    int nsize;
    while((nsize=recv(my_sock,&buff[0],
                      sizeof(buff)-1,0))
                  !=SOCKET_ERROR)
    {
      // ставим завершающий ноль в конце строки 
      buff[nsize]=0;

      // выводим на экран 
      printf("S=>C:%s",buff);

      // читаем пользовательский ввод с клавиатуры
      printf("S<=C:"); fgets(&buff[0],sizeof(buff)-1,
             stdin);

      // проверка на "quit"
      if (!strcmp(&buff[0],"quit\n"))
      {
        // Корректный выход
        printf("Exit...");
        closesocket(my_sock);
        WSACleanup();
        return 0;
      }

      // передаем строку клиента серверу
      send(my_sock,&buff[0],nsize,0);
    }

    printf("Recv error %d\n",WSAGetLastError());
    closesocket(my_sock);
    WSACleanup();
    return -1;





Подкиньте матерьял по сокетам или примеры по доходчивой реализации такой программы. 

Автор: kirjanov 25.1.2007, 19:09
_par, 
на что компилятор ругается?

Автор: _par 25.1.2007, 19:18
Вот ошибки

ну синтаксис я пытался изправить..


 In function `main': 
 `sockaddr_in' undeclared (first use in this function) 
  (Each undeclared identifier is reported only once 
  for each function it appears in.) 
syntax error before "local_addr" 
`local_addr' undeclared (first use in this function) 
sockaddr' undeclared (first use in this function) 
 syntax error before ')' token 
 syntax error before "client_addr" 
 `client_addr' undeclared (first use in this function) 
 syntax error before ')' token 
 too few arguments to function `accept' 
syntax error before ')' token 
[Warning] passing arg 2 of `CreateThread' makes integer from pointer without a cast 
[Warning] passing arg 5 of `CreateThread' makes integer from pointer without a cast 
  At top level: 
 syntax error before "return" 
 In function `SexToClient': 
 syntax error before "bytes_recv" 
 syntax error before ')' token 
 [Build Error]  [client.o] Error 1 

Автор: kirjanov 26.1.2007, 14:36
у тебя компилятор не может найти .h файлы, посмотри, как можно ему указать, где смотреть хидеры.
Последняя ошибка  - что-то со скобками неправильно.
 

Автор: witex 26.1.2007, 17:44
Точно, что именно компилятор не находит хидер, или просто н при линкован obj файл! потому что пример занком! Крисса Касперски. Так что у него пример рабочий!

Автор: _par 26.1.2007, 21:58
да я всё подгружаю... Осталась последняя ошибка.
[Warning] passing NULL used for non-pointer converting 2 of `void* CreateThread(_SECURITY_ATTRIBUTES*, DWORD, DWORD (*)(void*), void*, DWORD, DWORD*)' 

 ктонибудь знает чё он ругается,как исправить ?

Автор: _par 27.1.2007, 00:15
Все разобрался, всем огромное спасибо!!  smile 

Автор: Maugle 6.3.2011, 19:14
Интереснро спусть 4 года тут кто нить ответит на вопросы или нет? ))
Жаль автор не описал как он исправил ошибки и не предоставил полностью рабочий текст.
Ввиду этого я вот скопипастил текст проги у меня следующие ошибки:
 
Сервер:
In function `int main(int, char**)': 
passing NULL used for non-pointer converting 2 of `void* CreateThread(_SECURITY_ATTRIBUTES*, DWORD, DWORD (*)(void*), void*, DWORD, DWORD*)' 
passing NULL used for non-pointer converting 5 of `void* CreateThread(_SECURITY_ATTRIBUTES*, DWORD, DWORD (*)(void*), void*, DWORD, DWORD*)' 
In function `DWORD SexToClient(void*)': 
expected primary-expression before "int" 
expected `)' before "int" 
expected `)' before ';' token 

Клиент:
  [Linker error] undefined reference to `WSAStartup@8' 
  [Linker error] undefined reference to `WSAGetLastError@0' 
  [Linker error] undefined reference to `socket@12' 
  [Linker error] undefined reference to `htons@4' 
  [Linker error] undefined reference to `inet_addr@4' 
  [Linker error] undefined reference to `inet_addr@4' 
  [Linker error] undefined reference to `gethostbyname@4' 
  [Linker error] undefined reference to `closesocket@4' 
  [Linker error] undefined reference to `WSACleanup@0' 
  [Linker error] undefined reference to `connect@12' 
ну еще много похожих строк... в конце
  ld returned 1 exit status 

Помогите кто-нить, а то надо лабу сделать (Домино по сети), а препод дает примеры не рабочие из нэта скопированные.
Зарание огромное спасибо!!!

Автор: Modul 7.3.2011, 13:58
либу подключил для WinSock2 ?

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

Автор: Townsmen 11.9.2011, 14:55
Примеры из первого поста как я понял взят с citforum.ru/book/cook/winsock.shtml

Исправил ошибки в коде сервера, может кому надо будет:
Код

// Пример простого TCP – эхо сервера

  #include <stdio.h>
  #include <winsock2.h>  // Wincosk2.h должен быть 
          // подключен раньше windows.h!
  #include <windows.h>
  #pragma comment(lib, "ws2_32.lib") 

  #define MY_PORT    6000
           // Порт, который слушает сервер

  // макрос для печати количества активных
  // пользователей 
  #define PRINTNUSERS if (nclients)\
  printf("%d user on-line\n",nclients);\
  else printf("No User on line\n");

  // прототип функции, обслуживающий
  // подключившихся пользователей
  DWORD WINAPI SexToClient(LPVOID client_socket);

  // глобальная переменная – количество
  // активных пользователей 
  int nclients = 0;

  int main(int argc, char* argv[])
  {
    char buff[1024];    // Буфер для различных нужд

    printf("TCP SERVER DEMO\n");

    // Шаг 1 - Инициализация Библиотеки Сокетов
    // Т.к. возвращенная функцией информация
    // не используется ей передается указатель на
    // рабочий буфер, преобразуемый
    // к указателю  на структуру WSADATA.
    // Такой прием позволяет сэкономить одну
    // переменную, однако, буфер должен быть не менее
    // полкилобайта размером (структура WSADATA
    // занимает 400 байт)
    if (WSAStartup(0x0202,(WSADATA *) &buff[0])) 
    {
      // Ошибка!
          printf("Error WSAStartup %d\n",
             WSAGetLastError());
      return -1;
    }

    // Шаг 2 - создание сокета
    SOCKET mysocket;
    // AF_INET     - сокет Интернета
    // SOCK_STREAM  - потоковый сокет (с
    //      установкой соединения)
    // 0      - по умолчанию выбирается TCP протокол
    if ((mysocket=socket(AF_INET,SOCK_STREAM,0))<0)
    {
      // Ошибка!
      printf("Error socket %d\n",WSAGetLastError());
      WSACleanup();
        // Деиницилизация библиотеки Winsock
      return -1;
    }

    // Шаг 3 связывание сокета с локальным адресом
    sockaddr_in local_addr;
    local_addr.sin_family=AF_INET;
    local_addr.sin_port=htons(MY_PORT);
             // не забываем о сетевом порядке!!!
    local_addr.sin_addr.s_addr=0;
             // сервер принимает подключения
             // на все IP-адреса

    // вызываем bind для связывания
    if (bind(mysocket,(sockaddr *) &local_addr,
                sizeof(local_addr)))
    {
      // Ошибка
      printf("Error bind %d\n",WSAGetLastError());
      closesocket(mysocket);  // закрываем сокет!
      WSACleanup();
      return -1;
    }

    // Шаг 4 ожидание подключений
    // размер очереди – 0x100
    if (listen(mysocket, 0x100))
    {
      // Ошибка
      printf("Error listen %d\n",WSAGetLastError());
      closesocket(mysocket);
      WSACleanup();
      return -1;
    }

    printf("Ожидание подключений\n");

    // Шаг 5 извлекаем сообщение из очереди
    SOCKET client_socket;    // сокет для клиента
    sockaddr_in client_addr;    // адрес клиента
              // (заполняется системой)

    // функции accept необходимо передать размер
    // структуры
    int client_addr_size=sizeof(client_addr);

    // цикл извлечения запросов на подключение из
    // очереди
    while((client_socket=accept(mysocket, (sockaddr *)
            &client_addr, &client_addr_size)))
    {
      nclients++;      // увеличиваем счетчик
              // подключившихся клиентов

      // пытаемся получить имя хоста
      HOSTENT *hst;
      hst=gethostbyaddr((char *)
          &client_addr.sin_addr.s_addr,4, AF_INET);

      // вывод сведений о клиенте
      printf("+%s [%s] new connect!\n",
      (hst)?hst->h_name:"",
      inet_ntoa(client_addr.sin_addr));
      PRINTNUSERS

      // Вызов нового потока для обслужвания клиента
      // Да, для этого рекомендуется использовать
      // _beginthreadex но, поскольку никаких вызов
      // функций стандартной Си библиотеки поток не
      // делает, можно обойтись и CreateThread
      DWORD thID;
      CreateThread(NULL,NULL,SexToClient,
              &client_socket,NULL,&thID);
    }
    return 0;
  }

  // Эта функция создается в отдельном потоке и
  // обсуживает очередного подключившегося клиента
  // независимо от остальных
  DWORD WINAPI SexToClient(LPVOID client_socket)
  {
    SOCKET my_sock;
    my_sock=((SOCKET *) client_socket)[0];
    char buff[20*1024];
    #define sHELLO "Hello, Sailor\r\n"

    // отправляем клиенту приветствие 
    send(my_sock,sHELLO,sizeof(sHELLO),0);

    // цикл эхо-сервера: прием строки от клиента и
    // возвращение ее клиенту
    while(int bytes_recv=recv(my_sock,&buff[0],sizeof(buff),0)){
      send(my_sock,&buff[0],bytes_recv,0);
      }
    // если мы здесь, то произошел выход из цикла по
    // причине возращения функцией recv ошибки –
    // соединение клиентом разорвано
    nclients--; // уменьшаем счетчик активных клиентов
    printf("-disconnect\n"); PRINTNUSERS

    // закрываем сокет
    closesocket(my_sock);
    return 0;
  }

Автор: bazilio 10.4.2012, 23:35
Возник вопрос по работе с winsock. Использую пример из книги Касперски К. который выложен выложен в первом посте, поэтому и решил написать в этой теме.
В коде сервера 
Код

if (listen(mysocket, 0x100))
    {
      // Ошибка
      printf("Error listen %d\n",WSAGetLastError());
      closesocket(mysocket);
      WSACleanup();
      return -1;
    }

переводим сервер в режим ожидания функцией listen, параметры - дескриптор сокета и размер очереди.
Проблема в том, что мне ни как не удается ограничить кол-во подключающихся клиентов. В примере, как я понимаю, размер очереди указан в шестнадцатеричном представлении, пробовал ставить просто "1" - не помогает, может кто подскажет в чем дело?

Автор: Олег2005 11.4.2012, 08:51
Функция listen() описывается так:
int listen(int sd, int backlog);
где sd – дескриптор сокета, backlog – задает максимальный размер очереди для приходящих запросов соединения (то есть, сколько запросов может быть принято на обслуживание без потерь, обычно этот параметр равен 5). При переполнении очереди будет послано сообщение об ошибке. До сих пор рекомендуемая максимальная длина очереди для многих реализаций равна пяти. Если в настройках стека попытаться указать большее число, то можно в зависимости от реализации или получить сообщение об ошибке, или нет.
На самом деле во многих TCP-модулях различных ОС истинное значение длины очереди буфера для приема входящих соединений расчитывается по формуле: 
backlog * 3 / 2 + 1
и listen(sd, 0) разрешит принять одно соединение,  listen (sd, 5)  - 8. 
Система Solaris интерпретирует listen(sd,0) как запрет на прием заявок, BSD интерпретирует как (0*3/2+1 = 1) – одно соединение. Отрицательное значение backlog практически во всех реализациях трактуется как 0.
Если очередь при поступлении нового запроса окажется переполненной, то модуль TCP не ответит посылкой сегмента RST, а просто отвергнет его, и программа-клиент сможет еще несколько раз слать свой SYN-сегмент (см. описание внутреннего таймаута функции connect()). Если необходимо при работающем сервере послать именно RST, надо просто закрыть слушающий сокет.
Примечание
Современные стеки TCP/IP позволяют устанавливать значение backlog на несколько порядков выше. Формула расчета полной очереди буферов  тоже может меняться.

Автор: bazilio 11.4.2012, 10:57
Цитата

Если очередь при поступлении нового запроса окажется переполненной, то модуль TCP не ответит посылкой сегмента RST, а просто отвергнет его, и программа-клиент сможет еще несколько раз слать свой SYN-сегмент (см. описание внутреннего таймаута функции connect()).

Как я понимаю, при переполненной очереди, клиент будет отправлять SYN-сегмент пока не закончится таймаут. Но если очередь переполнена, то сервер не должен отвечать клиенту, а в этой программе клиенты свободно подключаются при любых значениях backlog.
Цитата

Если необходимо при работающем сервере послать именно RST, надо просто закрыть слушающий сокет.

Но если закрыть сокет, то сервер больше вообще не будет принимать запросы, а мне необходимо, что бы сервер обслуживал ограниченное количество клиентов, и при отключении их, мог принять других до заполнения очереди.
Возможно ли это реализовать используя данный пример клиент-сервера?

Автор: Олег2005 13.4.2012, 10:53
Цитата(bazilio @  11.4.2012,  09:57 Найти цитируемый пост)
а в этой программе клиенты свободно подключаются при любых значениях backlog.

Несколько вопросов.
1. Какая структура сети - т.е. расположены ли все клиенты - и сервер - в одной локалке или на одной машине
2. С какой скоростью(иначе не придумать названия) клиенты бомбят сервер?

Потому как есть вариант - особенно при моделировании - что сервер успевает быстро обработать заявку на соединение - и потому очередь не переполняется.

Добавлено через 6 минут и 14 секунд
Цитата(bazilio @  11.4.2012,  09:57 Найти цитируемый пост)
необходимо, что бы сервер обслуживал ограниченное количество клиентов, и при отключении их, мог принять других до заполнения очереди.

Сделать это наверно не совсем просто.
Я бы предложил "кривой" вариант - использовать простой счетчик подключений (сколько прошло accept) При этом надо использовать
SOCKET WSAAPI  WSAAccept (
IN    SOCKET            sd, 
OUT    struct sockaddr FAR *    addr,
IN OUT    LPINT                addrlen, 
IN    LPCONDITIONPROC        lpfnCondition, 
IN    DWORD                dwCallbackData);
Здесь:
·    addr - необязательный указатель на буфер (структуру), где должен храниться адрес подключаемого объекта; формат адреса определяется типом протокола, заданным при создании сокета;
·    addrlen - необязательный указатель на целую переменную, которая определяет длину аргумента addr;
·    lpfnCondition - адрес необязательный процедуры, которая на основе анализа некоторых условий (например, нежелательный IP-адрес) возвращает CF_ACCEPT для приема, CF_REJECT для отказа обслуживания запроса или CF_DEFER для отложенного анализа. На основе анализа возврата WSAAccept() принимает решение о дальнейшей обработке, или о создании группы сокетов, или подключает сокет к уже существующей группе. Например, надо отказаться от обслуживания запроса. Функция анализа возвращает CF_REJECT и WSAAccept() в свою очередь возвращает INVALID_SOCKET с кодом ошибки WSAECONNREFUSED. Режим вызова процедуры, адресованной lpfnCondition, осуществляется  установкой опции прослушивающего сокета SO_CONDITIONAL_ACCEPT (тип - BOOL). Использование этого режима снижает нагрузку на сеть и повышает устойчивость сервера против атак.
·    DwCallbackData - параметр, возвращаемый приложению. Этот параметр не интерпретируется WinSock.

Автор: bazilio 13.4.2012, 16:40
Пробовал различные варианты - на одной машине; клиент  из локалки; клиент из внешней сети;
Со скоростью не эксперементировал - клиент подключается и ожидает ввода данных что бы их отправить серверу, т.е. получается что:
Цитата(Олег2005 @ 13.4.2012,  10:53)
сервер успевает быстро обработать заявку на соединение - и потому очередь не переполняется.

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

#define PRINTNUSERS if (nclients)\
  printf("%d user on-line\n",nclients);\
  else printf("No User on line\n");

По результатам обязательно отпишусь, думаю кому нибудь пригодится.

Автор: Олег2005 13.4.2012, 19:08
Цитата(bazilio @  13.4.2012,  15:40 Найти цитируемый пост)
Вообщем, backlog ограничивает количество клиентов одновременно обращающихся к серверу,

Именно так.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)