Search code examples
cmultithreadingsocketsservermultiplexing

Client-server echo-chat (multiplexing I/O + threads)


I just started learning C language and I'm trying to get deal with Windows Sockets.

The problem is the server can accept and send message only once. I used debug mode and saw that work stops in select() from the server part. It seems fine in client (but I'm not sure) and don't see the problem in my code. But I have such a result. What's wrong?

I noticed that my tv.tv_sec isn't defined and I did that just before select, nothing was changed.

And just to be sure: as I need to receive and send message, I don't need write descriptor in accept(), right?

Client uses CreateThread function where I try to send message. Send is in while(1) cycle in main()

Server part:

int main(int argc, char* argv[])
{
    /* definitions, WSAStartup(), socket(), bind(), listen() 
    Listening socket is a returned value of listen() function*/

        FD_ZERO(&readSet);
        FD_ZERO(&writeSet);

        while (1)
        {
            // SELECT (LISTENING SOCKET)
            FD_ZERO(&readSet);
            FD_SET(listeningSocket, &readSet);
            tv.tv_sec = 5;
            printf("Listening: Read FD: %d; Write FD : %d;\n", FD_ISSET(listeningSocket, &readSet), FD_ISSET(listeningSocket, &writeSet));
            if ((retVal = select(listeningSocket + 1, &readSet, NULL, NULL, 0)) == SOCKET_ERROR)
            {
                printf("Select error ");
                break;
            }
            else if (retVal == 0)
            {
                printf(". . .\n");
                continue;
            }
            else
            {
                // READ SD
                if ((FD_ISSET(listeningSocket, &readSet)) != SOCKET_ERROR)
                {
                    if ((newSocketDescriptor = accept(listeningSocket, (struct sockaddr *)&clientAddr, &clientAddrSize)) == SOCKET_ERROR)
                    {
                        printf("Accept error ");
                        break;
                    }
                    FD_ZERO(&readSet);
                    FD_SET(newSocketDescriptor, &readSet);

                    HOSTENT *hst = gethostbyaddr((const char *)&serverAddr.sin_addr.s_addr, 4, AF_INET);
                    printf("Welcome %s (%s:%d) new connected\n", hst->h_name, inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
                    printf("Read FD: %d; Write FD : %d;\n", FD_ISSET(newSocketDescriptor, &readSet), FD_ISSET(newSocketDescriptor, &writeSet));

                    // READ
                    if (FD_ISSET(newSocketDescriptor, &readSet) != 0)
                    {
                        if ((numBytes = recv(newSocketDescriptor, &bufferData[0], sizeof(bufferData), 0)) == SOCKET_ERROR)
                        {
                            printf("Recv failed \n");
                            freeSocketInformation(newSocketDescriptor);
                            break;
                        }

                        bufferData[numBytes] = '\0';
                        printf("Client -> Server: %s\n", &bufferData[0]);
                    }

                    // WRITE
                    FD_ZERO(&writeSet);
                    FD_SET(newSocketDescriptor, &writeSet);

                    printf("Read FD: %d; Write FD : %d;\n", FD_ISSET(newSocketDescriptor, &readSet), FD_ISSET(newSocketDescriptor, &writeSet));
                    if (FD_ISSET(newSocketDescriptor, &writeSet) != 0)
                    {
                        //fgets(&bufferData[0], sizeof(bufferData), stdin);
                        if (send(newSocketDescriptor, &bufferData[0], strlen(&bufferData[0]), 0) == SOCKET_ERROR)
                        {
                            printf("Send error ");
                            freeSocketInformation(newSocketDescriptor);
                            break;
                        }
                        bufferData[numBytes] = '\0';
                        printf("Server -> Client: %s\n", &bufferData[0]);

                    }
                    printf("Read FD: %d; Write FD : %d;\n", FD_ISSET(newSocketDescriptor, &readSet), FD_ISSET(newSocketDescriptor, &writeSet));

                    FD_SET(newSocketDescriptor, &readSet);
                }
            }
        }
        //FD_CLR(listeningSocket, &readSet);
        closesocket(newSocketDescriptor);
    } while (FALSE);
    printf("- Error code: %d\n", WSAGetLastError());
    closesocket(listeningSocket);
    WSACleanup();
    return 0;
}

Client part (it uses CreateThread function which is in the end of the code):

/* definitions, socket(), connect()*/
        if (ioctlsocket(socketDescriptor, FIONBIO, (unsigned long *)&nb) != 0)
        {
            printf("ioctlsocket error ");
            break;
        }


            FD_ZERO(&writeSet);
            FD_SET(socketDescriptor, &writeSet);
            if ((retVal = select(socketDescriptor + 1, NULL, &writeSet, NULL, &tv)) == SOCKET_ERROR)
            {
                printf("Send non-blocking error ");
                break;
            }
            else if (retVal == 0)
            {
                printf("Non-blocking connect time limit is expired");
                break;
            }
        }
        printf("Connection with %s\n", SERVERADDR);

        DWORD thID;
        printf("Socket Desciptor: %d\n", socketDescriptor);
        HANDLE hThread = CreateThread(NULL, NULL, HandleReadThread, (LPVOID)socketDescriptor, NULL, &thID);
        printf("Thread ID: %d\n", thID);

        while (1)
        {
            // WRITE
            printf("Client -> Server: ");
            fgets(&bufferData[0], sizeof(bufferData), stdin);

            FD_ZERO(&writeSet);
            FD_SET(socketDescriptor, &writeSet);
            tv.tv_sec = 5;
            if ((retVal = select(socketDescriptor + 1, NULL, &writeSet, NULL, &tv)) == SOCKET_ERROR)
            {
                printf("Send non-blocking error ");
                break;
            }
            if (FD_ISSET(socketDescriptor, &writeSet) != 0)
            {
                if (send(socketDescriptor, bufferData, strlen(&bufferData[0]), 0) == SOCKET_ERROR)
                {
                    printf("Send error ");
                    break;
                }
            }
        }
    } while (FALSE);
    printf("- Error code: %d\n", WSAGetLastError());
    closesocket(socketDescriptor);
    WSACleanup();
    return 0;
}

DWORD WINAPI HandleReadThread(LPVOID serverSocket)
{
    SOCKET socketDescriptor;
    socketDescriptor = (SOCKET)serverSocket;
    char bufferData[MAXDATASIZE] = { 0 };
    int retVal;
    fd_set readSet;
    timeval tv = { 0 };
    tv.tv_sec = 5;

    int numBytes;
    int nclients = 0;

    while (1)
    {
        FD_ZERO(&readSet);
        FD_SET(socketDescriptor, &readSet);

        if ((retVal = select(socketDescriptor + 1, &readSet, NULL, NULL, &tv)) == SOCKET_ERROR)
        {
            printf("Select error. Error code: %d", WSAGetLastError());
            break;
        }
        else if (retVal == 0)
        {
            //printf(". . .\n");
            continue;
        }

        else
        {
            //FD_ZERO(socketDescriptor, &readSet);
            //FD_SET(socketDescriptor, &readSet);
            // READ
            if (FD_ISSET(socketDescriptor, &readSet) != 0)
            {
                if ((numBytes = recv(socketDescriptor, &bufferData[0], sizeof(bufferData), 0)) == SOCKET_ERROR)
                {
                    printf("Recv error in Thread. Error code: %d\n", WSAGetLastError());
                    break;
                }
                printf("\nSocket Desciptor: %d\n", socketDescriptor);
                bufferData[numBytes] = '\0';
                printf("Server -> Client: %s\n", &bufferData[0]);
            }
        }
    }
    closesocket(socketDescriptor);
    return 0;
}

Solution

  • Well, I solved it by myself. The condition

    if ((FD_ISSET(listeningSocket, &readSet)) != 0) 
    

    worked for all while(1) cycle, it should has been finished after accept() function. Also I added these lines in the beginning of while(1) after FD_SET for readSet:

    if (newSocketDescriptor)
    {
        FD_SET(newSocketDescriptor, &readSet);
    }
    

    The chat started working but there are a lot of stuffs to work at :)