Search code examples
c++multithreadingtcpserverpthreads

Why does server output for separate clients go to the same terminal?


I'm working on a client-server application for my Operating Systems class that is supposed to simulate sales of airplane tickets. We are directed to make it so that the main thread on the TCP server is listening for incoming connections and then, as soon as we receive a client connection, creates a new thread to handle that connection. After a lot of initial confusion, I believe I have the program in a mostly-functioning state.

The problem I'm having now is that when I run all the clients from separate terminals (whether it be 2 or 5 or any other number), all the output from the server comes into the most recent terminal that I have launched it on. This isn't a huge deal in and of itself but it also means that when I use Ctrl+C to close the process running on that last terminal, it exits all clients from the server (which is a problem).

So my questions are: 1. Why is all the output from the server being directed to a single terminal rather than sending the responses to the terminal that each client process was launched from? 2. Why do all clients quit as soon as I end the process in terminal 5?

Picture of the terminals for all the clients and the server (may have to open in new tab to see everything). Client-server terminals

Server.cpp (Needs my other class Plane.cpp to compile which I can provide if needed, but I don't think any code in there is relevant to the issue I'm facing):

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <string>
#include <sstream>
#include <stdlib.h>
#include <pthread.h>

#include "Plane.h"

using namespace std;

// default plane sizing
const int DEFAULT_ROWS = 26;
const int DEFAULT_COLUMNS = 6;
// Set up global variables for threads to access (yikes)
int rows, cols;
Plane* plane;
pthread_mutex_t mutexL = PTHREAD_MUTEX_INITIALIZER;
static int clientSocket;
int connections = 0;

void *connection_handler(void*);

struct argList {
    string arg;
    int row, col;
};

bool argParser(string input, argList &argL) {
    stringstream ss;

    ss << input;
    try {
        ss >> argL.arg >> argL.row >> argL.col;
    } catch (exception e) {
        cout << "Invalid arguments\n";
        return false;
    }
    return true;
}

string purchaseTicket(int row, int col) {
    string output;

    // lock this section before we use shared resource
    pthread_mutex_lock(&mutexL);
    cout << "Mutex locked\n";
    if (plane->isAvailable(row, col)) {
        plane->buyTicket(row, col);

        output = "Successfully purchased ticket for row: " + to_string(row) + ", column: " + to_string(col) + "\n";
    } else {
        if (row > plane->getNumRows() || row < 0 || col > plane->getNumCols() || col < 0) {
            output = "Invalid seat location!\n";
        } else {
            output = "Seat unavailable!\n";
        }
    }
    pthread_mutex_unlock(&mutexL);
    cout << "Mutex unlocked\n";
    // unlock when we're done
    return output;
}

string convertMatrix(Plane plane) {
    char** tempMatrix = plane.getSeatMatrix();

    string seats = "";
    for (int i = 0; i < plane.getNumRows(); i++) {
        seats += tempMatrix[i];
        seats += "\n";
    }

    return seats;
}

// arguments to run: column row
int main(int argc, char* argv[]) {
    // array of threads (thread pool)
    pthread_t threads[5];

    if (argc < 3) {
        rows = DEFAULT_ROWS;
        cols = DEFAULT_COLUMNS;
        plane = new Plane(rows, cols);
    } else if (argc == 3) {
        rows = atoi(argv[1]);
        cols = atoi(argv[2]);
        plane = new Plane(rows, cols);
    } else {
        cout << "Only 2 arguments allowed. You entered [" << argc << "]\n";
        return -1;
    }

    // Create socket
    int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listen_sock == -1) {
        cerr << "Failed to create socket\n";
        return -1;      
    }

    // Socket hint stuff
    sockaddr_in hint;
    hint.sin_family = AF_INET;
    hint.sin_port = htons(54000);
    inet_pton(AF_INET, "0.0.0.0", &hint.sin_addr);

    // Bind socket to IP and port
    if (bind(listen_sock, (sockaddr*)&hint, sizeof(hint)) < 0) {
        cerr << "Binding to IP/Port failed\n";
        return -2;
    }

    // Mark the socket for listening
    if (listen(listen_sock, SOMAXCONN) == -1) {
        cerr << "Can't listen";
        return -3;
    }

    char host[NI_MAXHOST];
    char service[NI_MAXSERV];

    int numThread = 0;
    while (numThread < 5) {
        cout << "Listening for connections...\n";

        sockaddr_in client;
        socklen_t clientSize = sizeof(client);
        // accept connections
        clientSocket = accept(listen_sock, (sockaddr*)&client, &clientSize);

        // if connection failed
        if (clientSocket == -1) {
            cerr << "Failed to connect with client";
            return -4;
        } else {
            cout << "Connection successful\n";
            connections++;
        }

        pthread_create(&threads[numThread], NULL, connection_handler, (void*) &clientSocket);

        // 0 out used memory
        memset(host, 0, NI_MAXHOST);
        memset(service, 0, NI_MAXSERV);
        int result = getnameinfo((sockaddr*)&client, 
                                sizeof(client), 
                                host, 
                                NI_MAXHOST, 
                                service,
                                NI_MAXSERV,
                                0);    
        if (result) {
            cout << host << " connected on " << service << endl;
        } else {
            inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
            cout << host << " connected on " << ntohs(client.sin_port) << endl;
        }

        numThread++;
    }

    // join threads together
    for (int i = 0; i < numThread; i++) {
        pthread_join(threads[i], NULL);
    }



    return 0;
}

void *connection_handler(void* listen_sock) {
    cout << "Thread No: " << pthread_self() << "\n-----\n";

    const int clientID = connections;
    // necessary variables for processing
    char buff[4096];
    string custMsg;

    custMsg += to_string(rows) + " " + to_string(cols) + "\n";
    int msgSize = strlen(custMsg.c_str())*sizeof(char);
    send(clientSocket, custMsg.c_str(), msgSize+1, 0);

    // Determine what we do when we receieve messages
    bool firstMsg = true;
    while (true) {
        memset(buff, 0, 4096);
        custMsg = "";


        int bytesRecv = recv(clientSocket, buff, 4096, 0);

        if (bytesRecv == -1) {
            pthread_mutex_lock(&mutexL);
            cerr << "There was a connection issue (client " << clientID << ")\n";
            pthread_mutex_unlock(&mutexL);
            break;
        } else if (bytesRecv == 0) {
            pthread_mutex_lock(&mutexL);
            cout << "Client " << clientID << " disconnected" << endl;
            pthread_mutex_unlock(&mutexL);
        }


        if (bytesRecv > 0)
            cout << "Received: " << string(buff, 0, bytesRecv) << " (client " << clientID << ")\n";

        // do things based on user input
        string inputStr(buff);
        argList args;
        if (argParser(inputStr, args)) {
            if (args.arg == "buy") {
                string purchResult = purchaseTicket(args.row, args.col);
                custMsg += purchResult;
                cout << purchResult << "------\n";
            } else {
                custMsg = "To buy a ticket, enter: 'buy <row> <col>'\n";
            }
        } else {
            custMsg = "Invalid argument list";
        }

        //custMsg += convertMatrix(*plane);
        int msgSize = strlen(custMsg.c_str())*sizeof(char);
        //cout << custMsg << "\n";
        cout << "Responding to client: " << clientID << "\n";
        send(clientSocket, custMsg.c_str(), msgSize+1, 0);
    }
    // Close socket
    close(clientSocket);
    return 0;
}

Client.cpp:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <sstream>
#include <time.h>

using namespace std;

struct serverInfo {
    string ipAddr;
    int portNum;
    int timeout;
};

int getRand(int max) {

    return rand() % max;
}

bool getPlaneInfo(string line, int& rows, int& cols) {
    stringstream ss;
    ss << line;
    try {
        ss >> rows >> cols;
        return true;
    } catch (exception e) {
        cout << "Critical error\n";
        return false;
    }
}

void getServerInfo(ifstream &serverCfg, serverInfo &conn_serv) {
    // variables that we'll read into
    string label, val, eq;
    int i = 0;
    try { // for conversion errors
        while (serverCfg >> label >> eq >> val) {
            if (i == 0)
                conn_serv.ipAddr = val;
            else if (i == 1)
                conn_serv.portNum = stoi(val);
            else if (i == 2)
                conn_serv.timeout = stoi(val);
            else
                break;
            i++;
        }
    } catch (exception e) {
        e.what();
    }
}

// arguments being sent in should be 'automatic' or 'manual' for method of purchasing
// followed by the .ini file containing the server connection info.
int main(int argc, char* argv[]) {
    srand(time(NULL));
    // we get these int variables from the first server response
    int rows, cols;

    bool AUTOMATIC = false;

    // make sure arguments are present and valid
    if (argc != 3) {
        cout << "Invalid number of arguments. Exiting...\n";
    }
    if (strncmp(argv[1],"automatic", 9) != 0 && strncmp(argv[1],"manual", 6) != 0) {
        cout << "Invlaid arguments! Please use 'manual' or 'automatic'. Exiting...\n";
        return -1;
    }

    // check to see if they want automatic ticket purchasing
    if (strncmp(argv[1], "automatic", 9) == 0) {
        AUTOMATIC = true;
    }

    // Handle file processing in getServerInfo function
    string fileName = argv[2];
    ifstream SERVER_CFG;
    SERVER_CFG.open(fileName);

    // store values from file in conn_info
    serverInfo conn_info;
    if(SERVER_CFG) {
        getServerInfo(SERVER_CFG, conn_info);
    } else {
        cout << "Invalid filename. Exiting...\n";
        return -2;
    }
    SERVER_CFG.close();

    // create socket
    int conn_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (conn_sock < 0) {
        cout << "\nFailed to Create Socket. Exiting...\n";
        return -3;
    }

    // get port and ipAddr information that we read from file
    int port = conn_info.portNum;
    string ipAddr = conn_info.ipAddr;

    // make hint
    sockaddr_in hint;
    hint.sin_family = AF_INET;
    hint.sin_port = htons(port);
    inet_pton(AF_INET, ipAddr.c_str(), &hint.sin_addr);

    // try to connect to server socket i times where i is conn_info.timeout
    for (int i = 0; i < conn_info.timeout; i++) {
        int connectVal = connect(conn_sock, (sockaddr*) &hint, sizeof(sockaddr_in));
        if (connectVal < 0 && i >= conn_info.timeout-1) {
            cout << "Failed to connect (" << (i+1) << ")\n";
            cout << "Failed to connect after " << (i+1) << " attempts. Exiting.\n";
            return -4;
        } else if (connectVal == 0) {
            break;
        }
        cout << "Failed to connect (" << (i+1) << ")\n";
        sleep(1);
    }



    char buff[4096];
    string userInput;
    bool firstMsg = true;
    bool needGreet = true;
    do {
        userInput = "";

        int sendResult;

        // Send a greeting message to the server to get plane info
        if (needGreet) {
            userInput = "Greeting the server";
            send(conn_sock, userInput.c_str(), userInput.size() + 1, 0);
            needGreet = false;
            continue;
        }

        if (AUTOMATIC && !firstMsg) {
            int row = getRand(20);
            int col = getRand(6);
            userInput = string("buy ") + to_string(row) + " " + to_string(col);
            cout << "Sending request to buy seat " << row << " " << col << "\n";
            sleep(1);
        } else { // get input if its manual
            if (!firstMsg) {
                cout << "> ";
                getline(cin, userInput);
            }
        }

        // send to server
        sendResult = send(conn_sock, userInput.c_str(), userInput.size() + 1, 0);

        // check if sent successfully
        if (sendResult < 0) { // connection error
            cout << "Failed to send to server\r\n";
            continue;
        }
        // wait for response
        memset(buff, 0, 4096);
        int bytesReceived = recv(conn_sock, buff, 4096, 0);

        // print response
        cout << "Server> " << string(buff, bytesReceived) << "\r\n";
        if (firstMsg) {
            string planeInf(string(buff,bytesReceived));
            if (getPlaneInfo(planeInf, rows, cols)) {
                cout << "Rows: " << rows << ", Columns: " << cols << "\n";
            } else {
                return -5;
            }
            firstMsg = false;
        }

    } while (true);

    // closing socket
    close(conn_sock);
    return 0;
}

Any help is greatly appreciated.


Solution

  • The problem is your use of global variables.

    Your connection thread writes a response to clientSocket, which your main changes with every connection. Every thread will write to the same socket.

    You need to create a class to hold data specific to each connection, and pass a new one of those to each thread. Do not use shared global data to hold thread-specific values.