Главная arrow Программирование arrow PC arrow Как написать клиент TCP/IP Wednesday, June 07 2023  
ГлавнаяКонтактыАдминистрированиеПрограммированиеСсылки
UK-flag-ico.png English Version
GERMAN-flag-ico.png Die deutsche Version
map.gif карта сайта
нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Поделиться:

Как написать клиент TCP/IP Версия для печати
Написал microsin   
27.03.2006

Написать клиента существенно проще, чем сервер, который должен обслуживать несколько одновременных подключений клиентов (см. “Как написать сервер 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 и специальной литературе.

Последнее обновление ( 08.09.2009 )
 

Добавить комментарий

:D:lol::-);-)8):-|:-*:oops::sad::cry::o:-?:-x:eek::zzz:P:roll::sigh:

Защитный код
Обновить

< Пред.   След. >

Top of Page
 
microsin © 2023