Начну без обиняков, как-то раз меня постигло откровение(ну не сильно мощное скажу по-честному) и возникла идея напечатать программу которая передает изображение с клиента на сервер. Достаточно просто да? Ну для программиста со стажем так и будет. Условия просты - не использовать сторонние библиотеки. В принципе немного сложнее, но если учесть что придется разбираться и искать примеры, ну такое себе занятие. Я решил, что эта задача мне по плечу. Плюс желательно чтобы было кода столько, чтобы его можно было запостить на форуме, в случае если понадобится помощь. В первую очередь мой взгляд пал на FTP, к слову ОС в которой разрабатывается Windows. Плюс FTP в том, что можно через него передать не только изображение, а любой файл. Скачав Filezilla Server, расшарив одну директорию на чтение/запись и создав юзера с паролем, попробовал подключится Filezilla Client все работало. Создал простенький пример кода на С/С++:
#include <iostream>void main(){FILE* fs;fopen_s(&fs, "1.txt", "w");if (fs){ fwrite("user\r\npassword\r\nsend D:\\share\\1.txt\r\nbye", 1, sizeof("user\r\npassword\r\nsend D:\\share\\1.txt\r\nbye"), fs); fwrite("\000", 1, sizeof("\000"), fs); fclose(fs);}system("ftp -s:1.txt 127.0.0.1");}
Если мне не изменяет память, то на локалхосте все работало, а при передаче по сети возникала ошибка в строчке с send. Что здесь удобно а)коротко б)не нужно устанавливать клиент, а использовать уже встроенную тулзу для ftp от майкрософта. Хотя по-мойму ее надо активировать через программы и компоненты. Если вы разберетесь в чем проблема данного метода и напишите в комментарии, будет отлично.
Не найдя ответа на куче форумов, я оставил данный код и решил использовать интерфейс для сетей сокеты. У меня уже был опыт передачи массива char'ов для другой программы. Кстати можете почитать у Таненбаума, Компьютерные сети, в главе про транспортный уровень. Там есть пример клиента и сервера, правда не для соединения "много клиентов - один сервер", а только "один клиент - один сервер". Поскольку передача идет через Интернет, то нужно зашифровать хоть как-то данные. Для этого используется блочный шифр - сеть Фейстеля. Плюсом на сервере надо сделать несколько(больше одного клиента) клиентов. Для этого воспользуемся Thread'ами, изображение для передачи будет брать скриншот экрана с клиента шифроваться и передаваться на сервер, на котором будет расшифровано и сразу же выведено на экран через дефолтную программу для открытия *.tga изображения.
Код сервера:
#include <iostream>#include <WinSock.h>#pragma comment (lib,"WS2_32.lib")#include <fstream>#include <algorithm>#include <string>#include <iterator>#include <vector>void error(const char* msg){ //perror(msg); std::cout<<'\n'<<WSAGetLastError(); WSACleanup(); std::cin.ignore(); exit(1);}void bzero(char*buf, int l){ for (int i = 0; i < l; i++) buf[i] = '\0';}struct arg_s{ unsigned char* buffer2; bool exit;};char** buffer;struct arg_sa{ struct arg_s* lalk; int current;};#define type struct arg_saint sockfd, * newsockfd;//слушающий и массив клиентских сокетовint buflen2 = 10292000;//максимальный размер изображения в байтах для RGBA*Width*Heightstruct sockaddr_in *cli_addr;int* clilen;int currentclient,cc;//сс-клиент по счету(для записи инкремента имени файла клиента изображения)typedef unsigned long long uint64_t;typedef unsigned int uint32_t;#define N 8//размер блока#define F32 0xFFFFFFFFuint32_t RK[N];//раундовые ключи#define size64 sizeof(uint64_t)#define ROR(x,n,xsize)((x>>n)|(x<<(xsize-n)))#define ROL(x,n,xsize)((x<<n)|(x>>(xsize-n)))#define RKEY(r)((ROR(K,r*3,size64*8))&F32)const uint64_t K = 0x96EA704CFB1CF671;//ключ шифрованияstruct hostent* server;uint32_t F(uint32_t subblk, uint32_t key){ return subblk + key;//функция шифрования}void createRoundKeys(){ for (int i = 0; i < N; i++) RK[i] = (ROR(K, i * 8, size64 * 8)) & F32;}uint64_t decrypt(uint64_t c_block)//расшифровка блоков сетью фейстеля{ //select subblocks uint32_t left = (c_block >> 32) & F32; uint32_t right = c_block & F32; uint32_t left_, right_;//subblock in the end of round for (int r = N - 1; r >= 0; r--) { uint32_t fk = F(left, RK[r]); left_ = left; right_ = right ^ fk; if (r > 0)//swap places to next round { left = right_; right = left_; } else //last round not swap { left = left_; right = right_; } } //collect subblock in block uint64_t block = left; block = (block << 32) | (right & F32); return block;}void session_(LPVOID args)//функция потока ля каждого клиента{ int current = currentclient++; bzero((char*)&(cli_addr[current]), sizeof(&(cli_addr[current]))); newsockfd[current] = accept(sockfd, (struct sockaddr*)&(cli_addr[current]), &(clilen[current])); if (newsockfd[current] < 0) { error("Error on accept\n"); } char* s = new char[100]; int n = recv(newsockfd[current], s, 100, 0); int buflen2 = atoi(s);//получаем число байтов изображения FILE* f; std::string name = "Screen"; cc++; _itoa_s(cc, s, 100, 10); name += s; name += ".tga"; fopen_s(&f,name.c_str(), "wb");//создаем файл изображения с увеличиваещимся на 1 именем, чтобы не перезаписать if (f != NULL) { unsigned char tgaHeader[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; unsigned char header[6]; n = recv(newsockfd[current], buffer[current], sizeof(tgaHeader), 0); fwrite((unsigned char*)buffer[current], 1, sizeof(tgaHeader), f); bzero(buffer[current], buflen2); n = recv(newsockfd[current], buffer[current],sizeof(header), 0); fwrite((unsigned char*)buffer[current], 1, sizeof(header), f);//записали хидеры bzero(buffer[current], buflen2); n = recv(newsockfd[current], buffer[current], buflen2, 0);//получили байты самого изображения // //расшифровка байтов createRoundKeys(); unsigned long long id; std::vector<uint64_t>* plaintext = new std::vector<uint64_t>(); int i = 0; while (i<buflen2) { memcpy(&id, (buffer[current]) + i, N); plaintext->push_back(decrypt(id)); i += 8; } std::cout << "i=" << i << std::endl; i = 0; char str_[N + 1]; memset(str_, 0, N); str_[N] = '\0'; for (std::vector<uint64_t>::iterator it = plaintext->begin(); it != plaintext->end(); ++it) { memcpy(str_, &*it, N); fwrite((unsigned char*)str_, sizeof(unsigned char), N/*strlen(str_)*/, f); i += 8; } std::cout << "i=" << i << std::endl; //конец рашифровки байтов //fwrite((unsigned char*)buffer[current], sizeof(char), buflen2, f); fclose(f); } system(name.c_str());//открываем изображение *.tga встроенным редактором}int main(){ cc = 0; WSADATA ws = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &ws) == 0) { currentclient = 0; int maxclients = 2;//максимальное число клиентов cli_addr = new struct sockaddr_in[maxclients]; clilen = new int[maxclients]; buffer = new char* [maxclients]; for (int i = 0; i < maxclients; i++) { clilen[i] = sizeof(cli_addr[i]); } sockfd = socket(AF_INET, SOCK_STREAM, 0);//tcp сокет if (sockfd < 0) error("ERROR opening socket"); struct sockaddr_in serv_addr; bzero((char*)&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; int port = 30000;//порт serv_addr.sin_port = htons(port); if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) error("ERROR on binding"); if (listen(sockfd, 10) < 0) error("ERROR listen"); HANDLE* thread;//массив потоков для каждого клиента отдельный struct arg_sa* args; while (true) { newsockfd = new int[maxclients]; thread = (HANDLE*)malloc(sizeof(HANDLE) * maxclients); args = new struct arg_sa[maxclients]; for (int i = 0; i < maxclients; i++) { args[i].lalk = new struct arg_s(); buffer[i] = new char[buflen2]; } int i = -1; while (++i < maxclients) { Sleep(1); args[i].current = i; args[i].lalk->exit = false; thread[i] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)(session_), args, 0, 0); } for (int i = 0; i < maxclients; i++) WaitForSingleObject(thread[i], INFINITE);//ждем завершения всех потоков i = -1; while (++i < maxclients) { shutdown(newsockfd[i], 0); TerminateThread(thread[i], 0); } //delete[] newsockfd; //free(thread); currentclient = 0; for (int i = 0; i < maxclients; i++) { //delete args[i].lalk; //delete[] args[i].lalk->buffer; } //delete[] args; } shutdown(sockfd, 0); WSACleanup(); return 0; } std::cin.ignore();}
Вкратце в вечном цикле создаются потоки для каждого клиента и ждут accept пока клиенты подключится. После чего WaitForSingleObject ждет пока они все передадут. У каждого клиента свой сокет и свой буфер передачи. То есть на сервере M+1 сокет, где M количество клиентов. После завершения всех передач, всё повторяется.
Теперь рассмотрим клиент:
#include <iostream>#include <WinSock.h>#include <vector>#pragma comment (lib,"WS2_32.lib")void error(const char* msg){ //perror(msg); std::cout << '\n' << WSAGetLastError(); WSACleanup(); std::cin.ignore(); exit(1);}void bzero(char* buf, int l){ for (int i = 0; i < l; i++) buf[i] = '\0';}typedef unsigned long long uint64_t;typedef unsigned int uint32_t;#define N 8#define F32 0xFFFFFFFFuint32_t RK[N];//раундовые ключи#define size64 sizeof(uint64_t)#define ROR(x,n,xsize)((x>>n)|(x<<(xsize-n)))#define ROL(x,n,xsize)((x<<n)|(x>>(xsize-n)))#define RKEY(r)((ROR(K,r*3,size64*8))&F32)const uint64_t K = 0x96EA704CFB1CF671;//ключ шифрованияvoid createRoundKeys(){ for (int i = 0; i < N; i++) RK[i] = (ROR(K, i * 8, size64 * 8)) & F32;}uint32_t F(uint32_t subblk, uint32_t key){ return subblk + key;//функция шифрования}uint64_t encrypt(uint64_t block)//зашифровка блоков сетью Фейстеля{ //select subblocks uint32_t left = (block >> 32) & F32; uint32_t right = block & F32; uint32_t left_, right_;//subblock in the end of round for (int r = 0; r < N; r++) { uint32_t fk = F(left, RK[r]); left_ = left; right_ = right ^ fk; if (r < N - 1)//swap places to next round { left = right_; right = left_; } else//last round not swap { left = left_; right = right_; } } //collect subblock in block uint64_t c_block = left; c_block = (c_block << 32) | (right & F32); return c_block;}int main(){ keybd_event(VK_LWIN, 0, 0, 0); keybd_event('M', 0, 0, 0); keybd_event('M', 0, KEYEVENTF_KEYUP, 0); keybd_event(VK_LWIN, 0, KEYEVENTF_KEYUP, 0);//эти строки сворачивают все приложения Sleep(1000);//чтобы сделать скриншот рабочего стола WSADATA ws = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &ws) == 0) { int sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serv_addr, cli_addr; bzero((char*)&serv_addr, sizeof(serv_addr)); bzero((char*)&cli_addr, sizeof(cli_addr)); serv_addr.sin_family = AF_INET; const char* add = "127.0.0.1";//адрес сервера serv_addr.sin_addr.s_addr = inet_addr(add); int port = 30000;//порт serv_addr.sin_port = htons(port); int servlen = sizeof(serv_addr); int n = connect(sockfd, (struct sockaddr*)&serv_addr, servlen); //ниже код делает скриншот HDC ScreenDC = GetDC(0); HDC MemoryDC = CreateCompatibleDC(ScreenDC); int ScreenHeight = GetSystemMetrics(SM_CYSCREEN); int ScreenWidth = GetSystemMetrics(SM_CXSCREEN); ScreenWidth = ((ScreenWidth - 1) / 4 + 1) * 4; BITMAPINFO BMI; BMI.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); BMI.bmiHeader.biWidth = ScreenWidth; BMI.bmiHeader.biHeight = ScreenHeight; BMI.bmiHeader.biSizeImage = ScreenWidth * ScreenHeight * 3; BMI.bmiHeader.biCompression = BI_RGB; BMI.bmiHeader.biBitCount = 24; BMI.bmiHeader.biPlanes = 1; DWORD ScreenshotSize; ScreenshotSize = BMI.bmiHeader.biSizeImage; unsigned char* ImageBuffer; HBITMAP hBitmap = CreateDIBSection(ScreenDC, &BMI, DIB_RGB_COLORS, (void**)&ImageBuffer, 0, 0); SelectObject(MemoryDC, hBitmap); BitBlt(MemoryDC, 0, 0, ScreenWidth, ScreenHeight, ScreenDC, 0, 0, SRCCOPY); DeleteDC(MemoryDC); ReleaseDC(NULL, ScreenDC); FILE* sFile = 0; unsigned char tgaHeader[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; unsigned char header[6]; unsigned char tempColors = 0; fopen_s(&sFile, "S.tga", "wb"); if (!sFile) { exit(1); } header[0] = ScreenWidth % 256; header[1] = ScreenWidth / 256; header[2] = ScreenHeight % 256; header[3] = ScreenHeight / 256; header[4] = BMI.bmiHeader.biBitCount; header[5] = 0; fwrite(tgaHeader, 1, sizeof(tgaHeader), sFile); fwrite(header, sizeof(header), 1, sFile); //конец записали изображение в файл //шифруем блоками полезную нагрузку изображения кроме хидеров createRoundKeys(); std::vector<uint64_t>* msg = new std::vector<uint64_t>(),*crpt = new std::vector<uint64_t>(); unsigned long long id; int i = 0; while (i < BMI.bmiHeader.biSizeImage) { memcpy(&id, (ImageBuffer + i), N); msg->push_back(id); i += 8; } std::cout << "i=" << i << std::endl; uint64_t cipher; i = 0; char str_[N + 1]; memset(str_, 0, N); str_[N] = '\0'; for (std::vector<uint64_t>::iterator it = msg->begin(); it != msg->end(); ++it) { cipher = encrypt(*it); memcpy(str_, &cipher, N); fwrite((unsigned char*)str_, sizeof(unsigned char), N, sFile); i += 8; } std::cout << "i=" << i << std::endl; // //fwrite(ImageBuffer, BMI.bmiHeader.biSizeImage, 1, sFile); std::cout << BMI.bmiHeader.biSizeImage << std::endl; fclose(sFile); DeleteObject(hBitmap); FILE* f; fopen_s(&f, "S.tga", "rb"); int count = 0; if (f != NULL) { while (getc(f) != EOF) count++;//считаем байты изображения в счетчик чтобы потом передать fclose(f); } count -= 18; std::cout << count<< std::endl; char* s = new char[100]; _itoa_s(count, s, 100, 10); n = send(sockfd, s, 100, 0);//передаем счетчик char* buffer = new char[count]; fopen_s(&f, "S.tga", "rb"); size_t bytes; if (f != NULL) { memcpy(buffer, tgaHeader, sizeof(tgaHeader)); n = send(sockfd, buffer, sizeof(tgaHeader), 0); bzero(buffer, count); memcpy(buffer, header, sizeof(header)); n = send(sockfd, buffer, sizeof(header), 0); bzero(buffer, count);//передаем хидеры for(int i=0;i<18;i++) fgetc(f); bzero(buffer, count); bytes = fread(buffer, sizeof(unsigned char), count, f); n = send(sockfd,buffer, count, 0);//передаем шифрованные байты изображения fclose(f); } Sleep(1000); shutdown(sockfd, 0); WSACleanup(); //system("del S.tga"); delete[] buffer,s; return 0; } //std::cin.ignore();}
Вот результат работы клиента файл скриншота S.tga, зашифрованный
Видно, что это рабочий стол
А вот результат который был передан на сервер и расшифрован Screen.tga
Как видите обычная сеть Фейстеля не подходит для шифрования, но можно воспользоваться CBC и CFB методами, возможно будет лучше зашифровано, если честно не проверял.
Спасибо за внимание!