commit 6f24ef2fb79a4e4830c3df363f8aaa3a3cfac294 Author: Your Name Date: Thu Feb 26 11:16:17 2026 -0600 initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..93c913b --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +# Compiler and flags +CXX = g++ +CXXFLAGS = -pthread -Wall -Wextra -O2 -std=c++17 -lcurses + +# Targets +all: main + +# Client build +main: main.cpp + $(CXX) $(CXXFLAGS) main.cpp -o main + +# Clean build artifacts +clean: + rm -f main + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6debc47 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +an attempt at a threaded ncurses network chat in c++ diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000..bd58677 --- /dev/null +++ b/client.cpp @@ -0,0 +1,86 @@ +/*this network test uses more complicated network functionality but doesnt use threads or ncurses. +that means you have to wait for the other end to send a message before getting to write a new message +to run, do ./client.cpp 127.0.0.1 8080 +this one has args +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +//Client side +int main(int argc, char *argv[]) +{ + //we need 2 things: ip address and port number, in that order + if(argc != 3) + { + cerr << "Usage: ip_address port" << endl; exit(0); + } //grab the IP address and port number + char *serverIp = argv[1]; int port = atoi(argv[2]); + //create a message buffer + char msg[1500]; + //setup a socket and connection tools + struct hostent* host = gethostbyname(serverIp); + sockaddr_in sendSockAddr; + bzero((char*)&sendSockAddr, sizeof(sendSockAddr)); + sendSockAddr.sin_family = AF_INET; + sendSockAddr.sin_addr.s_addr = + inet_addr(inet_ntoa(*(struct in_addr*)*host->h_addr_list)); + sendSockAddr.sin_port = htons(port); + int clientSd = socket(AF_INET, SOCK_STREAM, 0); + //try to connect... + int status = connect(clientSd, + (sockaddr*) &sendSockAddr, sizeof(sendSockAddr)); + if(status < 0) + { + cout<<"Error connecting to socket!"<"; + string data; + getline(cin, data); + memset(&msg, 0, sizeof(msg));//clear the buffer + strcpy(msg, data.c_str()); + if(data == "exit") + { + send(clientSd, (char*)&msg, strlen(msg), 0); + break; + } + bytesWritten += send(clientSd, (char*)&msg, strlen(msg), 0); + cout << "Awaiting server response..." << endl; + memset(&msg, 0, sizeof(msg));//clear the buffer + bytesRead += recv(clientSd, (char*)&msg, sizeof(msg), 0); + if(!strcmp(msg, "exit")) + { + cout << "Server has quit the session" << endl; + break; + } + cout << "Server: " << msg << endl; + } + gettimeofday(&end1, NULL); + close(clientSd); + cout << "********Session********" << endl; + cout << "Bytes written: " << bytesWritten << + " Bytes read: " << bytesRead << endl; + cout << "Elapsed time: " << (end1.tv_sec- start1.tv_sec) + << " secs" << endl; + cout << "Connection closed" << endl; + return 0; +} \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..4f825bf --- /dev/null +++ b/main.cpp @@ -0,0 +1,489 @@ +/*an attempt at pthread and network and ncurses all in the same function +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include //try to make the program work without this without doing anything awful but do it later + +using namespace std; + +const int PORT_NUM = 8888; +const string IP_ADDRESS = "127.0.0.1"; + +int mode = 0; //what mode is this program in. 0 = nothing. 1 = server. 2 = client +int serverSocketDescriptor;//a global variable for storing the server socket descriptor. +int clientSocketDescriptor; +vector clientSocketDescriptors;//a global vector for storing client socket descriptors + +//keep track of the session time using global variables +struct timeval start1, end1; + +//also keep track of the amount of data sent as well +int bytesRead, bytesWritten = 0; + +const int LOG_LENGTH = 10;//number of text log file lines to display +int linePos = 0; //counter for what current position to use in the file + +pthread_t client_wait_thread; + +int writeToFile(string path, string line, bool incLineNum = true); +void* waitForClient(void* argss); +void* pollForClient(); + +//clears ncurses rows in a specific region only +void clearRows(int startingRow, int endingRow) +{ + //preserve the cursor location + int yBefore, xBefore; + getyx(stdscr, yBefore, xBefore); + + for (int i = startingRow; i < endingRow; i++) + { + move(i, 0); + clrtoeol();//clear to end of line + } + + //restore original cursor location + move(yBefore, xBefore); +} + +//display a file using ncurses +int displayFile(string path, int startLineNum = 0, int numLines = 10) +{ + ifstream file(path); + if (!file) + { + return 1; + } + else + { + clearRows(0, numLines+1); //clear the chat area + int lineNum = 0; + string line; + + //print each line directly to the screen + int num = 0; + while(getline(file, line) && num <= numLines + startLineNum + 1) //while there is file content and the line number isn't too high + { + if (num >= startLineNum) + { + move(lineNum, 0); + printw(line.c_str()); + lineNum++;//increment the row number after printing each line + } + num++; + } + return 0; + } +} + +//gets the number of lines in a file. returns -1 if there was an error. +int linesInFile(string path) +{ + ifstream file(path); + if (!file) + { + return 1; + } + else + { + int lineNum = 0; + string line; + + int num = 0; + while(getline(file, line)) + { + num++; + } + return num; + } +} + +struct waitClientArgs +{ + sockaddr_in newSockAddr; + socklen_t newSockAddrSize; +}; + +void* waitForClient(void* argss) +{ + waitClientArgs* args = static_cast(argss); + clientSocketDescriptor = accept(serverSocketDescriptor, (sockaddr *)&args->newSockAddr, &args->newSockAddrSize); + if (clientSocketDescriptor >= 0) + { + writeToFile("test.txt", "client connected"); + displayFile("test.txt", linePos, LOG_LENGTH); + } + delete args;//delete to avoid memory leaks + + return pollForClient(); +} + +void* pollForClient() +{ + char msg[1024]; + while(1) + { + //receive a message from the client (listen) + memset(&msg, 0, sizeof(msg));//clear the buffer + bytesRead += recv(clientSocketDescriptor, (char*)&msg, sizeof(msg), 0); + writeToFile("test.txt", msg);//write the client's message to file + displayFile("test.txt", linePos, LOG_LENGTH); + } + + return nullptr; +} + +//set up a suitable server for this +int setupServer(int port) +{ + //setup a socket and connection tools + sockaddr_in servAddr; + bzero((char*)&servAddr, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_addr.s_addr = htonl(INADDR_ANY); + servAddr.sin_port = htons(port); + + //open stream oriented socket with internet address + //also keep track of the socket descriptor + serverSocketDescriptor = socket(AF_INET, SOCK_STREAM, 0); + if(serverSocketDescriptor < 0) + { + cerr << "Error establishing the server socket" << endl; + exit(0); + } + //bind the socket to its local address + int bindStatus = bind(serverSocketDescriptor, (struct sockaddr*) &servAddr, + sizeof(servAddr)); + if(bindStatus < 0) + { + cerr << "Error binding socket to local address" << endl; + exit(0); + } + writeToFile("test.txt", "Waiting for a client to connect..."); + //listen for up to 5 requests at a time + listen(serverSocketDescriptor, 5); + //receive a request from client using accept + //we need a new address to connect with the client + sockaddr_in newSockAddr; + socklen_t newSockAddrSize = sizeof(newSockAddr); + //accept, create a new socket descriptor to + //handle the new connection with client + auto* aaa = new waitClientArgs{};//it looks stupid but it works + aaa->newSockAddr = newSockAddr; + aaa->newSockAddrSize = newSockAddrSize; + int rc = pthread_create(&client_wait_thread, nullptr, waitForClient, aaa); + pthread_detach(client_wait_thread); + writeToFile("test.txt", "Server started sucessfully"); + gettimeofday(&start1, NULL); + + /*while(1) + { + //receive a message from the client (listen) + cout << "Awaiting client response..." << endl; + memset(&msg, 0, sizeof(msg));//clear the buffer + bytesRead += recv(clientSocketDescriptor, (char*)&msg, sizeof(msg), 0); + if(!strcmp(msg, "exit")) + { + cout << "Client has quit the session" << endl; + break; + } + cout << "Client: " << msg << endl; + cout << ">"; + string data; + getline(cin, data); + memset(&msg, 0, sizeof(msg)); //clear the buffer + strcpy(msg, data.c_str()); + if(data == "exit") + { + //send to the client that server has closed the connection + send(clientSocketDescriptor, (char*)&msg, strlen(msg), 0); + break; + } + //send the message to client + bytesWritten += send(clientSocketDescriptor, (char*)&msg, strlen(msg), 0); + }*/ + + return 0; +} + +void closeServer() +{ + //we need to close the socket descriptors after we're all done + gettimeofday(&end1, NULL); + close(clientSocketDescriptor); + close(serverSocketDescriptor); + cout << "********Session********" << endl; + cout << "Bytes written: " << bytesWritten << " Bytes read: " << bytesRead << endl; + cout << "Elapsed time: " << (end1.tv_sec - start1.tv_sec) + << " secs" << endl; + cout << "Connection closed..." << endl; +} + +void closeClient() +{ + gettimeofday(&end1, NULL); + close(clientSocketDescriptor); + cout << "********Session********" << endl; + cout << "Bytes written: " << bytesWritten << + " Bytes read: " << bytesRead << endl; + cout << "Elapsed time: " << (end1.tv_sec- start1.tv_sec) + << " secs" << endl; + cout << "Connection closed" << endl; +} + +void setupClient() +{ + //we need 2 things: ip address and port number, in that order + //if(argc != 3) + //{ + // cerr << "Usage: ip_address port" << endl; exit(0); + //} //grab the IP address and port number + //create a message buffer + char msg[1024]; + //setup a socket and connection tools + struct hostent* host = gethostbyname(IP_ADDRESS.c_str()); + sockaddr_in sendSockAddr; + bzero((char*)&sendSockAddr, sizeof(sendSockAddr)); + sendSockAddr.sin_family = AF_INET; + sendSockAddr.sin_addr.s_addr = + inet_addr(inet_ntoa(*(struct in_addr*)*host->h_addr_list)); + sendSockAddr.sin_port = htons(PORT_NUM); + clientSocketDescriptor = socket(AF_INET, SOCK_STREAM, 0); + //try to connect... + int status = connect(clientSocketDescriptor, + (sockaddr*) &sendSockAddr, sizeof(sendSockAddr)); + if(status < 0) + { + writeToFile("test.txt", "Error connecting to socket!"); + } + writeToFile("test.txt", "Connected to the server!"); + int bytesRead, bytesWritten = 0; + struct timeval start1, end1; + gettimeofday(&start1, NULL); + /*while(1)s + { + cout << ">"; + string data; + getline(cin, data); + memset(&msg, 0, sizeof(msg));//clear the buffer + strcpy(msg, data.c_str()); + if(data == "exit") + { + send(clientSd, (char*)&msg, strlen(msg), 0); + break; + } + bytesWritten += send(clientSd, (char*)&msg, strlen(msg), 0); + cout << "Awaiting server response..." << endl; + memset(&msg, 0, sizeof(msg));//clear the buffer + bytesRead += recv(clientSd, (char*)&msg, sizeof(msg), 0); + if(!strcmp(msg, "exit")) + { + cout << "Server has quit the session" << endl; + break; + } + cout << "Server: " << msg << endl; + }*/ +} + +//appends a line of text to the end of a given file. returns 0 if the file existed, 1 if it didn't work +int writeToFile(string path, string line, bool incLineNum) +{ + ofstream file; + file.open(path, ios_base::app);//open the file in append mode + + if (file.is_open()) + { + file << line << endl; + //this probably would help but theres too much broken stuff right now to be sure + //if (incLineNum) + //{ + // linePos++; + //} + return 0; + } + else + { + //do something if it didn't work + return 1;//i guess that's good enough for now + } +} + +int main(int argc, char *argv[]) +{ + //doesnt fucking work + if (argc > 1 && argv[1][0] == 'c') + { + //client mode + mode = 2; + } + else + { + //server mode + mode = 1; + } + + + initscr();//creates stdscr. important step + use_default_colors();//this apparently tells it to use default terminal colors whenever there is no color attribute applied + + //printw("Starting server..."); + + //you have to do this to make the scrolling still work correctly if there was already content in the log file + linePos = linesInFile("test.txt") - LOG_LENGTH + 1; + + char *userInput = new char[1024]; + bool exit = false; + + if (mode == 2) + { + writeToFile("test.txt", "CLIENT MODE"); + setupClient(); + } + else if (mode == 1) + { + writeToFile("test.txt", "SERVER MODE"); + setupServer(PORT_NUM); + } + + while (!exit) + { + displayFile("test.txt", linePos, LOG_LENGTH); + + //scroll along the screen if and when required so that it stays in sync + if (linesInFile("test.txt") > LOG_LENGTH) + { + linePos++; + } + + move(12, 0); + printw("> "); + getstr(userInput); + writeToFile("test.txt", userInput); + if (mode == 1) + { + send(clientSocketDescriptor, (char*)&userInput, strlen(userInput), 0); + } + else + { + send(clientSocketDescriptor, (char*)&userInput, strlen(userInput), 0); + } + } + + endwin(); + closeServer(); + + return 0; +} + +//Server side +int main_old(int argc, char *argv[]) +{ + //for the server, we only need to specify a port number + if(argc != 2) + { + cerr << "Usage: port" << endl; + exit(0); + } + //grab the port number + int port = atoi(argv[1]); + //buffer to send and receive messages with + char msg[1500]; + + //setup a socket and connection tools + sockaddr_in servAddr; + bzero((char*)&servAddr, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_addr.s_addr = htonl(INADDR_ANY); + servAddr.sin_port = htons(port); + + //open stream oriented socket with internet address + //also keep track of the socket descriptor + int serverSd = socket(AF_INET, SOCK_STREAM, 0); + if(serverSd < 0) + { + cerr << "Error establishing the server socket" << endl; + exit(0); + } + //bind the socket to its local address + int bindStatus = bind(serverSd, (struct sockaddr*) &servAddr, + sizeof(servAddr)); + if(bindStatus < 0) + { + cerr << "Error binding socket to local address" << endl; + exit(0); + } + cout << "Waiting for a client to connect..." << endl; + //listen for up to 5 requests at a time + listen(serverSd, 5); + //receive a request from client using accept + //we need a new address to connect with the client + sockaddr_in newSockAddr; + socklen_t newSockAddrSize = sizeof(newSockAddr); + //accept, create a new socket descriptor to + //handle the new connection with client + int newSd = accept(serverSd, (sockaddr *)&newSockAddr, &newSockAddrSize); + if(newSd < 0) + { + cerr << "Error accepting request from client!" << endl; + exit(1); + } + cout << "Connected with client!" << endl; + //lets keep track of the session time + struct timeval start1, end1; + gettimeofday(&start1, NULL); + //also keep track of the amount of data sent as well + int bytesRead, bytesWritten = 0; + while(1) + { + //receive a message from the client (listen) + cout << "Awaiting client response..." << endl; + memset(&msg, 0, sizeof(msg));//clear the buffer + bytesRead += recv(newSd, (char*)&msg, sizeof(msg), 0); + if(!strcmp(msg, "exit")) + { + cout << "Client has quit the session" << endl; + break; + } + cout << "Client: " << msg << endl; + cout << ">"; + string data; + getline(cin, data); + memset(&msg, 0, sizeof(msg)); //clear the buffer + strcpy(msg, data.c_str()); + if(data == "exit") + { + //send to the client that server has closed the connection + send(newSd, (char*)&msg, strlen(msg), 0); + break; + } + //send the message to client + bytesWritten += send(newSd, (char*)&msg, strlen(msg), 0); + } + //we need to close the socket descriptors after we're all done + gettimeofday(&end1, NULL); + close(newSd); + close(serverSd); + cout << "********Session********" << endl; + cout << "Bytes written: " << bytesWritten << " Bytes read: " << bytesRead << endl; + cout << "Elapsed time: " << (end1.tv_sec - start1.tv_sec) + << " secs" << endl; + cout << "Connection closed..." << endl; + return 0; +} \ No newline at end of file