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