Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Использование протокола TCP/IP

Источник: http://trubetskoy1.narod.ru/
Автор: Алексей Трубецкой

В этой статье описывается создание двух простых консольных приложений, одно из которых (intcp) является сервером и получает сообщения, а другое (outtcp) является клиентом и отправляет сообщение серверу, используя протокол TCP/IP. Сообщения успешно отправляются и принимаются на одном компьютере и на разных компьютерах в одной локальной сети. Взаимодействие этих приложений через интернет автором пока не проверялось.

Приложение Outtcp

Код приложения содержит 3 функции:

  • функция ctime() возвращает текущее время
  • функция main() содержит основной код приложения
  • функция getSocketError() выдает сообщение об ошибке

Функция main()

Приведем полностью код функции main():

#pragma argsused
int main(int argc, char* argv[]) {
    char clientRequest[REQUEST_MSG_SIZE];
    sockaddr_in  serverAddr;     //server's socket address
    int sockAddrSize;            //size of socket address structure
    SOCKET sHandle;              //socket descriptor
    unsigned nbyte;              //number of sended bytes
    double starttime, endtime;
    unsigned mlen;             //message's length 
    char* serverName;          //server's name
    WSADATA wsaData;           //data structure that is to receive
                               //details of the Windows Sockets 
                               //implementation

Здесь мы объявляем переменные, используемые функцией.

   serverName = argv[1];

В этой строке мы присваиваем серверу имя, которое представляет собой IP-адрес, полученный в качестве параметра при запуске программы. Если программа будет запущена без параметров, возникнет ошибка. При наличии более одного параметра, остальные параметры будут проигнорированы. Если оба приложения (outtcp и intcp) будут запускаться на одном компьютере, то в качестве имени сервера следует указывать 127.0.0.1

   //initiate use of WS2_32.DLL by a process
   if(WSAStartup(MAKEWORD(1, 1), &wsaData)) {
        getSocketError();
        getch();
        return 0;
   }

Функция WSAStartup() должна быть вызвана в первую очередь любым приложением или библиотекой DLL, которые используют сокеты. В качестве первого параметра указывается версия Windows Sockets, которую может использовать вызывающая программа. При успешном завершении этой функции структура wsaData заполняется информацией, характеризующей данную реализацию Windows Sockets.

   //create client's socket
   if((sHandle = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) 
   {
        getSocketError();
        getch();
        return 0;
   }

Здесь мы создаем сокет с помощью функции socket(). Первый параметр этой функции - формат, в котором будет задан адрес, а второй параметр - тип создаваемого сокета. Для версии Windows Sockets 1.1 можно указывать только один из двух типов: SOCK_STREAM (создается двунаправленный поток байтов, используется протокол TCP) или SOCK_DGRAM (поддерживает передачу дейтаграмм, используется протокол UDP). С помощью третьего параметра можно указать протокол, используемый сокетом.

   // build server socket address
   sockAddrSize = sizeof(sockaddr_in);
   memset(&serverAddr, 0, sockAddrSize);
   serverAddr.sin_family = AF_INET;
   serverAddr.sin_port = htons(SERVER_PORT_NUM);

Константа SERVER_PORT_NUM не является библиотечной и была определена нами в файле tcp.h.

   if((serverAddr.sin_addr.s_addr = inet_addr(serverName)) 
                             == INADDR_NONE) 
   {
       perror("unknown server name");
       closesocket(sHandle);
       getch();
       return 0;
   }

Здесь мы преобразуем полученный в качестве параметра при запуске программы адрес (который пока представлен в виде строки) в формат, соответствующий протоколу. Если адрес является недействительным, функция inet_addr() возвращает INADDR_NONE.

   //connect to server
   if(connect(sHandle, (sockaddr *)&serverAddr,
                 sockAddrSize) == -1)
   {
       getSocketError();
       closesocket(sHandle);
       getch();
       return 0;
   }

Функция connect() получает в качестве параметров дескриптор сокета, адрес структуры, содержащей информацию об адресе сервера, и размер этой структуры.

   //fill string to send
   printf("Introduce the string to send:\n");
   gets(clientRequest);
   mlen = strlen(clientRequest);
   if(mlen >= REQUEST_MSG_SIZE) {
       printf("Max length of message is %d bytes !\n", 
               REQUEST_MSG_SIZE-1);
       getch();
       return 0;
   }

Запрашиваем у пользователя ввод строки, которую нужно передать серверу.

   //send request to server
   starttime = ctime();
   if((nbyte = send(sHandle, (char *)clientRequest,
                  mlen, MSG_DONTROUTE)) == SOCKET_ERROR)
   {
      getSocketError();
      closesocket(sHandle);
      getch();
      return 0;
   }

Отправляем серверу введенную пользователем строку. Функция send() возвращает количество переданных байт или значение SOCKET_ERROR при неуспешном завершении.

   if(nbyte != mlen)
          printf("Write only %d (%d) bytes\n", nbyte, mlen);

   endtime = ctime() - starttime;
   printf("send_time = %f\n", (float)endtime);
   closesocket(sHandle);

Выводим время, затраченное на пересылку данных и закрываем сокет с помощью функции closesocket().

   printf("Press any key\n");
   getch();
   return 0;
}

Функция getSocketError()

void getSocketError() {
   LPVOID lpMsgBuf;
   if (!FormatMessage(
       FORMAT_MESSAGE_ALLOCATE_BUFFER |
       FORMAT_MESSAGE_FROM_SYSTEM |
       FORMAT_MESSAGE_IGNORE_INSERTS,
       NULL,
       GetLastError(),
       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
       (LPTSTR) &lpMsgBuf,
       0,
       NULL ))
   {
      MessageBox(NULL, "Error number not found",
                 "Error", MB_OK | MB_ICONSTOP );
      return;
   }
   // Display the string.
   MessageBox(NULL, (LPCTSTR)lpMsgBuf, "Error",
               MB_OK | MB_ICONSTOP);
   // Free the buffer.
   LocalFree(lpMsgBuf);
}

Функция getSocketError() выводит диалоговое окно с сообщением об ошибке. Текст сообщения мы получаем с помощью функции FormatMessage(), которая анализирует код ошибки, возвращаемый функцией Windows API GetLastError().

Приложение intcp

Код серверного приложения intcp содержит функции main() и getSocketError().

Функция main()

int main() {
   sockaddr_in serverAddr;       // server's socket address
   sockaddr_in clientAddr;       // client's socket address
   int sockAddrSize;             // size of socket address structure
   SOCKET sHandle;               // socket file descriptor
   SOCKET newSHandle;            // socket descriptor from accept
   WSADATA wsaData;      //data structure that is to receive details
                         //of the Windows Sockets implementation
   int nbyte = 0;        //number of sended bytes
   char clientRequest[REQUEST_MSG_SIZE];

   // set up the local address
   sockAddrSize = sizeof(sockaddr_in);
   memset(&serverAddr, 0, sockAddrSize);
   serverAddr.sin_family = AF_INET;
   serverAddr.sin_port = htons(SERVER_PORT_NUM);
   serverAddr.sin_addr.s_addr = INADDR_ANY;

Структура serverAddr заполняется почти так же, как и в предыдущем приложении, но полю serverAddr.sin_addr.s_addr присваивается значение INADDR_ANY. Это означает, что IP-адрес клиента заранее не определен.

   // initiate use of WS2_32.DLL by a process
   if(WSAStartup(MAKEWORD(1, 1), &wsaData)) {
       getSocketError();
       getch();
       return 0;
   }

   // create a TCP-based socket
   if((sHandle = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
       getSocketError();
       getch();
       return 0;
   }

   // bind socket to local address
   if(bind(sHandle, (sockaddr *)&serverAddr,
            sockAddrSize)  == -1 )
   {
       getSocketError();
       closesocket(sHandle);
       getch();
       return 0;
   }

Использование функций WSAStartup() и socket() аналогично предыдущему приложению. После удачного завершения функции socket() мы вызываем функцию bind(), которая связывает локальный адрес с сокетом.

   // create queue for client connection requests
   if(listen(sHandle, SERVER_MAX_CONNECTIONS) == -1) {
       getSocketError();
       closesocket(sHandle);
       getch();
       return 0;
   }

Функция listen() переводит сокет в состояние ожидания запросов от клиентов. Эту функцию следует использовать для сокетов типа SOCK_STREAM. Функция listen() обычно используется серверными приложениями.

   // accept new connect requests and spawn tasks to process them
   // Note: newSHandle socket will take propreties of
   // sHandle socket
   if((newSHandle = accept(sHandle, (sockaddr *)&clientAddr,
                              &sockAddrSize)) == INVALID_SOCKET)
   {
       getSocketError();
       closesocket(sHandle);
       getch();
       return 0;
   }

Функция accept() извлекает из очереди запросов первый запрос для данного сокета, затем создает новый сокет, который и должен обработать этот запрос. Созданный сокет обладает точно такими же свойствами, что и первоначальный сокет, дескриптор которого передается функции в качестве первого параметра. При успешном завершении функция accept() возвращает дескриптор нового сокета.

   nbyte = recv(newSHandle, (char *)clientRequest,
                 sizeof(clientRequest), MSG_PEEK);
   if (nbyte != SOCKET_ERROR) {
       printf("Have received %d bytes \n", nbyte);
       printf("Data: %s\n", clientRequest);
   }
   else
       getSocketError();

Здесь мы получаем сообщение от клиента с помощью функции recv(). Если четвертый параметр функции имеет значение MSG_PEEK, это означает, что функция будет считывать входящие данные. При этом данные копируются в буфер clientRequest (второй параметр), но не удаляются из очереди входящих данных. Если функция завершается удачно, то возвращаемое значение равно количеству полученных байт. В противном случае возвращается SOCKET_ERROR.

   memset(clientRequest, 0, REQUEST_MSG_SIZE);
   closesocket(sHandle);
   closesocket(newSHandle);
   printf("Press any key\n");
   getch();
   return 0;
}

Скачать исходники для приложений outtcp и intcp

Примечание: сначала следует запускать intcp. В противном случае при запуске outtcp будет выдано сообщение об ошибке: "Подключение не установлено, т.к. конечный компьютер отверг запрос на подключение". После запуска intcp запустите outtcp (указав адрес сервера), введите строку, которая будет передана приложению intcp и нажмите Enter. Строка будет выведена в окне приложения intcp.

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 

Комментарии

1.
Аноним
Мне нравитсяМне не нравится
1 марта 2006, 18:31:33
Статья хорошая. Вот только хотелось бы увидеть пример, показывающий как избежать прихода нескольких сообщений в сокет одновременно (и соответственно их склеивания в буфере).
2.
Аноним
Мне нравитсяМне не нравится
14 ноября 2005, 14:28:08
gets(clientRequest);
mlen = strlen(clientRequest);
if(mlen >= REQUEST_MSG_SIZE) { ...

no comments
3.
Аноним
Мне нравитсяМне не нравится
10 ноября 2005, 11:46:52
Статья понравилась. Ждём апдейта) для Internet.
Передача сообщения - хорошо. А по существенней? файла, потока?

Буду ОЧЕНЬ благодарен и готов поучаствовать в разработке оной статьи.
ICQ 225719505
e-mail: art_od@ukr.net
4.
Аноним
Мне нравитсяМне не нравится
9 ноября 2005, 19:25:12
Статья безусловно полезная.
Хотелось бы более подробно рассмотреть четвертый параметр функции send
Не хватает обработки сообщений.
5.
Аноним
Мне нравитсяМне не нравится
9 ноября 2005, 11:17:07
Полезная статья, мне оч. интересно.
Спасибо.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог