Search code examples
c++windowstcpwinsock2

How to receive multiple files through TCP and save them at the same time in C++?


I'm trying to create an app which would accept many connections from clients at the same time and it works for me, but it also should download those files at the same time. In this version of server, even if clients are connected simultaneously, files are written one by one.

#include <stdio.h>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <ctime>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    WSADATA wsaData;
    int winsock_result = WSAStartup(MAKEWORD(2,2), &wsaData);

    if(winsock_result != 0)
    {
        exit(1);
    }

    SOCKET server_socket;
    server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if(server_socket == INVALID_SOCKET)
    {
        WSACleanup();
        exit(1);
    }

    int const max_clients = 100;
    int client_socket[max_clients];

    for (int i = 0; i < max_clients; i++)
    {
        client_socket[i] = 0;
    }

    char* ip_address = "127.0.0.1";
    int port = 6666;

    SOCKADDR_IN server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = inet_addr(ip_address);
    int server_sizeof = sizeof(server);

    int opt = TRUE;
    if( setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
    {
        closesocket(server_socket);
        WSACleanup();
        exit(1);
    }


    if(bind(server_socket,(SOCKADDR *)&server, server_sizeof) == SOCKET_ERROR)
    {
        closesocket(server_socket);
        WSACleanup();
        exit(1);
    }

    if(listen(server_socket, 5) == SOCKET_ERROR)
    {
        std::cout << "Nasluchiwanie portu nieudane." << std::endl;
    }
    else
    {
        std::cout << "Nasluchiwanie portu " << port << " udane." << std::endl << std::endl;
    }

    int const buffer_size = 512;
    char buffer[buffer_size];

    int max_socket_descriptor, socket_descriptor;
    int downloaded_files = 1;

    fd_set readfds;

    while(true)
    {
        FD_ZERO(&readfds);
        FD_SET(server_socket, &readfds);
        max_socket_descriptor = server_socket;

        for (int i = 0 ; i < max_clients ; i++)
        {
            socket_descriptor = client_socket[i];
            if(socket_descriptor > 0)
            {
                FD_SET( socket_descriptor, &readfds);
            }

            if(socket_descriptor > max_socket_descriptor)
            {
                max_socket_descriptor = socket_descriptor;
            }
        }

        if ((select( max_socket_descriptor + 1, &readfds, NULL, NULL, NULL) < 0) && (errno != EINTR))
        {
            std::cout << "Blad funkcji select." << std::endl;
        }

        if (FD_ISSET(server_socket, &readfds))
        {
            int new_sockfd;
            if ((new_sockfd = accept(server_socket,(SOCKADDR *)&server, &server_sizeof)) == SOCKET_ERROR)
            {
                std::cout << "Otrzymanie deskryptora nieudane." << std::endl;
            }
            else
            {
                for (int i = 0; i < max_clients; i++)
                {
                    if( client_socket[i] == 0 )
                    {
                        client_socket[i] = new_sockfd;
                        std::cout << "Dodawanie do listy socketow jako numer " << i << std::endl;

                        break;
                    }
                }
            }
        }

        for (int i = 0; i < max_clients; i++)
        {
            socket_descriptor = client_socket[i];

            if (FD_ISSET( socket_descriptor, &readfds))
            {
                struct sockaddr_in client_address;

                char filename[buffer_size];
                std::stringstream ip_filename;
                ip_filename << "plik" << downloaded_files << "_" << inet_ntoa(client_address.sin_addr);
                strcpy(filename, ip_filename.str().c_str());

                std::cout << "Nazwa pliku (IP klienta): " << filename << std::endl;

                FILE* file;
                file = fopen(filename, "wb");

                const clock_t begin_time = clock();
                int received_size;

                do
                {
                    memset(buffer, 0, buffer_size);
                    received_size = recv(socket_descriptor, buffer, buffer_size, 0);

                    if (received_size == 0 || received_size == -1)
                    {
                        break;
                    }
                    fwrite(buffer, sizeof(char), received_size, file);
                }
                while (received_size != 0);

                fclose(file);
                std::cout << "Czas wysylania pliku: " << float( clock () - begin_time ) /  CLOCKS_PER_SEC << " sekund." << std::endl << std::endl;

                closesocket(socket_descriptor);
                client_socket[i] = 0;

                downloaded_files++;
            }
        }
    }

    closesocket(server_socket);
    WSACleanup();
    system("pause");

    return 0;
}

What should I do to make them write many at the same time? I've tried many modifications of the code above but every time I can't get wanted result. For example:

#include <stdio.h>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <ctime>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    WSADATA wsaData;
    int winsock_result = WSAStartup(MAKEWORD(2,2), &wsaData);

    if(winsock_result != 0)
    {
        exit(1);
    }

    SOCKET server_socket;
    server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if(server_socket == INVALID_SOCKET)
    {
        WSACleanup();
        exit(1);
    }

    int const max_clients = 100;
    int client_socket[max_clients];

    for (int i = 0; i < max_clients; i++)
    {
        client_socket[i] = 0;
    }

    char* ip_address = "127.0.0.1";
    int port = 6666;

    SOCKADDR_IN server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = inet_addr(ip_address);
    int server_sizeof = sizeof(server);

    int opt = TRUE;
    if( setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
    {
        closesocket(server_socket);
        WSACleanup();
        exit(1);
    }


    if(bind(server_socket,(SOCKADDR *)&server, server_sizeof) == SOCKET_ERROR)
    {
        closesocket(server_socket);
        WSACleanup();
        exit(1);
    }

    if(listen(server_socket, 5) == SOCKET_ERROR)
    {
        std::cout << "Nasluchiwanie portu nieudane." << std::endl;
    }
    else
    {
        std::cout << "Nasluchiwanie portu " << port << " udane." << std::endl << std::endl;
    }

    int const buffer_size = 512;
    char buffer[buffer_size];

    int max_socket_descriptor;
    int downloaded_files = 1;

    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(server_socket, &readfds);
    max_socket_descriptor = server_socket;

    while(true)
    {
        if ((select( max_socket_descriptor + 1, &readfds, NULL, NULL, NULL) < 0) && (errno != EINTR))
        {
            std::cout << "Blad funkcji select." << std::endl;
        }

        for (int i = 0 ; i < max_clients ; i++)
        {
            if(FD_ISSET(server_socket, &readfds))
            {
                int new_sockfd;
                if ((new_sockfd = accept(server_socket,(SOCKADDR *)&server, &server_sizeof)) == SOCKET_ERROR)
                {
                    std::cout << "Otrzymanie deskryptora nieudane." << std::endl;
                }
                else
                {
                    for (int i = 0; i < max_clients; i++)
                    {
                        if( client_socket[i] == 0 )
                        {
                            client_socket[i] = new_sockfd;
                            FD_SET( client_socket[i], &readfds);

                            if(client_socket[i] > max_socket_descriptor)
                            {
                                max_socket_descriptor = client_socket[i];
                            }

                            std::cout << "Dodawanie do listy socketow jako numer " << i << std::endl;

                            break;
                        }
                    }
                }
            }

            if(FD_ISSET(client_socket[i], &readfds))
            {
                struct sockaddr_in client_address;

                char filename[buffer_size];
                std::stringstream ip_filename;
                ip_filename << "plik" << downloaded_files << "_" << inet_ntoa(client_address.sin_addr);
                strcpy(filename, ip_filename.str().c_str());

                std::cout << "Nazwa pliku (IP klienta): " << filename << std::endl;

                FILE* file;

                memset(buffer, 0, buffer_size);
                int received_size;

                received_size = recv(client_socket[i], buffer, buffer_size, 0);

                if (received_size <= 0)
                {
                    closesocket(client_socket[i]);
                    FD_CLR(client_socket[i], &readfds);
                    client_socket[i] = 0;
                    break;
                }
                else
                {
                    file = fopen(filename, "ab");
                    fwrite(buffer, sizeof(char), received_size, file);
                    fclose(file);
                }

                downloaded_files++;
            }

        }
    }

    closesocket(server_socket);
    WSACleanup();
    system("pause");

    return 0;
}

I thought about opening and closing those files every received packet and appending every packet to them, but I really don't have idea how to do it. The example of modified code was meant to do it, but it doesn't work.

I'm forbidden to use other processes and threads than the main one, so I'm kinda helpless now. Thanks for your help.


Solution

  • You have the basic loop with select in place, which is good.

    accept is already (mostly) non-blocking. You just need to turn on non-blocking mode on the client sockets and then you'll be able to handle multiple client reads, writes and accepts in your main select loop.

    You can have a vector of client-specific data per client, with each entry containing the client socket, the opened file and any other client-specific state.

    After the accept, you create a new client entry and add it to the vector. Then in the main loop you do FD_SET for accept and all client's reads and writes. After the select, you inspect the the FD sets and handle them one by one. For best performance you will want your file I/O also in non-blocking mode, but for this assignment that's probably overkill.