Как написать клиент 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 и специальной литературе. |