Как написать клиент TCP/IP Печать
Добавил(а) microsin   

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

1. Делаем самый пустой проект, например, консольный, без всяких MFC, поддержек Windows Sockets и любых других библиотек. Добавляем к проекту библиотеку Ws2_32.lib (свойства проекта\Linker\Input\Additional Dependencies). Добавляем #include < Winsock2.h >.

2. Инициализируем библиотеку Winsock нужной версии:

WSADATA wsaData;
WSAStartup( MAKEWORD( 2, 0 ), &wsaData );

3. Создаем сокет:

SOCKET sClient;
sClient = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

4. Мы имеем для подключения CString csHost - адрес в виде имени или точечной нотации и dwTcpPort - номер порта, к которому будем подключаться. Разбираемся с ними.

struct sockaddr_in server;
struct hostent *host = NULL;
server.sin_family = AF_INET;
server.sin_port = htons(dwTcpPort);
server.sin_addr.s_addr = inet_addr(csHost);
if (server.sin_addr.s_addr == INADDR_NONE)
{
   //имя представлено не в точечной нотации
   host = gethostbyname(csHost);
   if (host == NULL)
   {
       //разборка ошибки и подготовка приложения к завершению по ошибке
   }
   else
   {
       CopyMemory(&server.sin_addr, host->h_addr_list[0], host->h_length);
       WLog("Get address O.K.");
   }
}
else
   //имя было в обычной точечной нотации
   WLog("Get address O.K.");

5. Соединяемся с сервером:

if (connect(sClient, (struct sockaddr *)&server, sizeof(server)) == SOCKET_ERROR)
{
   //разборка ошибки и подготовка приложения к завершению по ошибке
}
else
   WLog("connect() O.K.");

6. Запускаем поток, который обрабатывает прием по TCP. Поток нужен потому, что функция recv() блокирующая:

UCHAR bufTCPrecv[65536];
WORD inTCPpnt, outTCPpnt;
HANDLE hTcpRecvThread;
hTcpRecvThread = CreateThread(NULL, 0, RecvTcpClientThread, (LPVOID)sClient, 0, &dwThreadId);
if (hTcpRecvThread == NULL)
{
   //разборка ошибки и подготовка приложения к завершению по ошибке
}
else
   WLog("spawn RECV_TCP thread OK");

Тело потока должно быть наподобие этакого (WSA_Err_Decode - простая процедура декодирования кода ошибки в текстовое сообщение):

//задаем размер буфера приема
#define DEFAULT_BUFFER 4096
  
DWORD WINAPI RecvTcpClientThread(LPVOID lpParam)
/* Поток, который поддерживает приём через сокет. */
{
   SOCKET cli_sock = (SOCKET)lpParam;
   char   szBuff[DEFAULT_BUFFER];
   int    ret, nLeft, idx;
   DWORD  dwErr;
   CString messtr;
  
   if (iDebugLevel == 1)
   {
      EnterCriticalSection(&cs);
      WLog("Spawn service thread for RECV_TCP client data O.K.");
      LeaveCriticalSection(&cs);
   }
   while(1)
   {
      // Perform a blocking recv() call
      ret = recv(cli_sock, szBuff, DEFAULT_BUFFER, 0);
      if (ret == 0)        // Graceful close
      {
         messtr.Format("TcpClient disconnected");
         EnterCriticalSection(&cs);
         printf(messtr + "\n");
         WLog(messtr);
         LeaveCriticalSection(&cs);
         break;
      }
      else if (ret == SOCKET_ERROR)
      {
         dwErr = WSAGetLastError();
         messtr.Format("TCP recv() failed: error code %d, ", dwErr);
         messtr += (CString)WSA_Err_Decode(dwErr);
         EnterCriticalSection(&cs);
         WLog (messtr);
         LeaveCriticalSection(&cs);
         break;
      }
      EnterCriticalSection(&cs);
      szBuff[ret] = '\0';
      if (iDebugLevel == 1)
      {
         messtr.Format("thread RECV_TCP: '%s'", (CString)szBuff);
         WLog(messtr);
      }
      nLeft = ret;
      idx = 0;
      while(nLeft > 0)
      {
         //Данные тупо пишутся в кольцевой буфер. Оттуда
         // их будет доставать другой поток, нам это неинтересно.
         bufTCPrecv[inTCPpnt] = szBuff[idx];
         inTCPpnt++;
         idx++;
         nLeft--;
      }
      LeaveCriticalSection(&cs);
   }
   return 0;
}

Переменная CRITICAL_SECTION cs должна быть определена в глобальных переменных и активно использоваться там, где несколько потоков могут "подраться" за один ресурс (например, за буфер в памяти, за вывод на экран консоли, на диск).

7. Запускаем поток, работающий на передачу по TCP. В общем-то, я неуверен, что send() является блокирующей функцией, но отдельный поток не помешает:

UCHAR buf232recv[65536];
WORD in232pnt, out232pnt;
HANDLE hTcpSendThread;
hTcpSendThread = CreateThread(NULL, 0, SendTcpClientThread, (LPVOID)sClient, 0, &dwThreadId);
if (hTcpSendThread == NULL)
{
   //разборка ошибки и подготовка приложения к завершению по ошибке
}
else
   WLog("spawn SEND_TCP thread OK");

Тело потока должно быть наподобие этакого:

DWORD WINAPI SendTcpClientThread(LPVOID lpParam)
/* Всё, что в буфере RS232, передаём по TCP на сервер */
{
   SOCKET sock = (SOCKET)lpParam;
   int    ret;
   DWORD  dwErr;
   CString messtr;
   WORD   out232pnt_thread;
   UCHAR  buf[65536];
   WORD   idx;
   bool   bLocalError = false;
  
   out232pnt_thread = out232pnt;
   if (iDebugLevel == 1)
   {
      messtr.Format("Spawn service thread for SEND_TCP data to server O.K.");
      EnterCriticalSection(&cs);
      WLog(messtr);
      LeaveCriticalSection(&cs);
   }
   while(!bError && !bLocalError)
   {
      EnterCriticalSection(&cs);
      idx = 0;
      while (in232pnt != out232pnt_thread)
      {
         //Берем данные, которые нам надо передавать. В моем 
         // примере они поступают от кольцевого буфера COM-порта
         buf[idx] = buf232recv[out232pnt_thread];
         out232pnt_thread++;
         idx++;
      }
      out232pnt = out232pnt_thread;
      LeaveCriticalSection(&cs);
      if (idx != 0)
      {
         buf[idx] = 0;
         ret = send(sock, (const char*)&buf, idx, 0);
         if (ret == SOCKET_ERROR)
         {
            dwErr = WSAGetLastError();
            messtr.Format("TCP send() failed: error code %d, ", dwErr);
            messtr += (CString)WSA_Err_Decode(dwErr);
            EnterCriticalSection(&cs);
            WLog (messtr);
            LeaveCriticalSection(&cs);
            bLocalError = true;
            break;
         }
         else if (ret == idx)
         {
            if (iDebugLevel == 1)
            {
               messtr.Format("thread SEND_TCP: '%s'", buf);
               EnterCriticalSection(&cs);
               WLog(messtr);
               LeaveCriticalSection(&cs);
            }
         }
      }
      Sleep(10);
   }
   if (iDebugLevel == 1)
   {
      messtr.Format("Sending TCP thread terminated");
      EnterCriticalSection(&cs);
      WLog(messtr);
      LeaveCriticalSection(&cs);
   }
   return 0;
}

8. Это все. Почти. Осталось только ожидать отключения сервера и делать по этому событию остановку потоков приема и передачи. При завершении программы (например, по Ctrl-Break) нужно корректно завершать соединение с сервером:

shutdown(sClient, SD_BOTH);
if (0 == TerminateThread(hTcpSendThread, 0))
   WLog("Error terminate SEND_TCP thread");
else
   WLog("SEND_TCP thread terminated");
if (0 == TerminateThread(hTcpRecvThread, 0))
   WLog("Error terminate RECV_TCP thread");
else
   WLog("RECV_TCP thread terminated");
WSACleanup();

Подробности - в исходниках, MSDN и специальной литературе.