Две программы связываются посредством Windows Sockets. Одна отсылает другой файл. Вопрос: как второй его правильно получить и записать, если заранее не известен его размер? Интересуют стандартные методы. Спасибо.
Вопрос вдогонку: если одна программа делает send два раза, может ли так случиться, что вторая получит оба буфера одним вызовом receive?
Самый простой вариант. Принимаешь в цикле, допустим с помощью recv() данные, пока функция не вернет, что данных для приема больше нету. По мере приема пишешь в файл последовательно.
Squash Может, если буфер приема по размеру больше размера тех данных, что посылались двумя send(). Естественно при условии, что это одно и тоже соединение (спешу предположить - асинхронные сокеты используете?).
И как это определяется, если их больше нет? Вроде бы recv блокируется, пока не получит порцию данных.
Squash recv - возвращает количество принятых байт, если вернулось 0 или -1 (или зарезервированный какой символ) то конец файла.
Ждите других данных, ну либо завершайте соединение, прибивайте поток, если больше не нужно ничего принимать.
Ну либо надо обернуть контент файла в какое-то своё сообщение, к примеру, struct { unsigned long long file_size; char file_content[1]}, а можно считать, что все данные которые пришли по TCP соединению - это и есть файл. Если последний вариант, то нужно читать из сокета пока тот не будет закрыт. Да, может, может и наоборот. Привязываться к количеству вызовов send\recv нельзя.
Squash Работа по TCP в чем-то похожа на работу со стримами. Например - пайпами. Из стрима можно читать, в стрим можно писать. Данные, отправленные по сокету, могут быть разбиты на произвольные блоки, или же, наоборот, склеены. Можно сделать несколько сендов и принять их одним ресивом, можно наоборот. Это означает, что алгоритмы коммуникации должны строиться из соображений того, что нет никаких гарантий относительно способа разбиения данных на фрагменты во время транспортировки. Если нужно отправить блок данных, размер которых известен отправителю, но неизвестен получателю, то обычно отправитель предварительно сообщает получателю их размер. Чтобы синхронные сокеты не вешали программу намертво, им можно установить тайм-аут на ожидание - через setsockopt, кажется. Асинхронные сокеты, в случае необходимости, можно просто кэнселить. Обычно прием-передача делается так: 1. Очередь исходящих сообщений. У отправителя есть специальная функциональность, которую можно "попросить" передать данные. Она складывает данные в очередь и отправляет по мере успеваемости сети. 2. Диспатчер входящих сообщений. У приемника есть буффер, в который читается все подряд, и по ходу дела разбирается на пакеты уровня приложения и раздается соответствующим получатетлям. Что же касается пересылки простого файла - то самое простое - это отправить его размер (например QWORD), после чего отправить сам файл. В винде же есть готовая функция - TransmitFile.
Squash Созерцай мои простые примеры на Visual C++ с использованием Windows API: КЛИЕНТ Code (Text): #include <winsock2.h> #include <windows.h> #pragma comment(lib, "ws2_32.lib") #define SERVER "127.0.0.1" #define PORT 74 int main(int argc, char* argv[]) { LARGE_INTEGER filesize; LONGLONG size; char buffer[4096]; WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in remote_addr; remote_addr.sin_family = AF_INET; remote_addr.sin_port = htons(PORT); remote_addr.sin_addr.s_addr = inet_addr(SERVER); connect(sock, (sockaddr *)&remote_addr, sizeof(remote_addr)); WIN32_FIND_DATA fd; recv(sock, (char *)&fd, sizeof(fd), 0); filesize.LowPart = fd.nFileSizeLow; filesize.HighPart = fd.nFileSizeHigh; size = 0; HANDLE hFile = CreateFile(fd.cFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); int bytes_recv; DWORD dwBytesWritten; do { bytes_recv = recv(sock, buffer, 4096, 0); WriteFile(hFile, buffer, bytes_recv, &dwBytesWritten, NULL); size += bytes_recv; } while ((size != filesize.QuadPart) && (bytes_recv != SOCKET_ERROR)); SetFileTime(hFile, &fd.ftCreationTime, &fd.ftLastAccessTime, &fd.ftLastWriteTime); CloseHandle(hFile); SetFileAttributes(fd.cFileName, fd.dwFileAttributes); closesocket(sock); WSACleanup(); return 0; } СЕРВЕР Code (Text): #include <stdio.h> #include <winsock2.h> #include <windows.h> #pragma comment(lib, "ws2_32.lib") #define PORT 74 int main(int argc, char* argv[]) { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in local_addr; local_addr.sin_family = AF_INET; local_addr.sin_port = htons(PORT); local_addr.sin_addr.s_addr = INADDR_ANY; bind(sock, (sockaddr *)&local_addr, sizeof(local_addr)); listen(sock, 1); SOCKET client_sock; sockaddr_in client_addr; int client_addr_size = sizeof(client_addr); while (client_sock = accept(sock, (sockaddr *)&client_addr, &client_addr_size)) { HOSTENT *remotehost; remotehost = gethostbyaddr((char *)&client_addr.sin_addr.s_addr, 4, AF_INET); printf("+%s [%s] new connect!\n", (remotehost) ? remotehost->h_name : "", inet_ntoa(client_addr.sin_addr)); WIN32_FIND_DATA fd; HANDLE hFind = FindFirstFile("a.rar", &fd); if (hFind != INVALID_HANDLE_VALUE) { send(client_sock, (char *)&fd, sizeof(fd), 0); HANDLE hFile = CreateFile(fd.cFileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (hFile != INVALID_HANDLE_VALUE) { DWORD dwBytesRead; char buffer[4096]; do if (ReadFile(hFile, buffer, 4096, &dwBytesRead, NULL)) send(client_sock, buffer, dwBytesRead, 0); while (dwBytesRead == 4096); } CloseHandle(hFile); } FindClose(hFind); } closesocket(sock); WSACleanup(); return 0; } Данные примеры алгоритмов - просты и универсальны. Хватит на все случаи жизни.
facetedmind я прием данных оформил бы вот так Code (Text): BOOL ReceiveData(SOCKET sock,char *Data,int len,TIMEVAL *TimeOut) { int Ecc; FD_SET F_Set; FD_ZERO(&F_Set); FD_SET(sock,&F_Set); Ecc = select(0,&F_Set,NULL,NULL,TimeOut); if (Ecc <=0) return FALSE; if (__WSAFDIsSet(sock,&F_Set)) { Ecc = recv(sock,Data,len,0); if (Ecc <=0) return FALSE; } else return FALSE; return TRUE; }
Зачем велосипед изобретать. У HTTP есть формат передачи данных chunked. Передаёшь через сокеты аналогичным образом: [type = 32bit integer, chunk size] [type = binary, data] [type = 32bit integer, chunk size] [type = binary, data] ... [type = 32bit integer, 0]