commit e06203c5d96929e8a9eb1f52dedbd07131f6f661 Author: jazz (gitea) Date: Thu Mar 12 14:37:23 2026 -0500 initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e2079be --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +{ + "BasedOnStyle": "LLVM", + "UseTab": "Always", + "IndentWidth": 8, + "TabWidth": 8, + "ColumnLimit": 100, + "LineEnding": "LF", + "RemoveBracesLLVM": true, + "AlwaysBreakAfterReturnType": "AllDefinitions", + "BreakBeforeBraces": "Custom", + "BraceWrapping": { + "AfterFunction": true, + "AfterClass": false, + "AfterControlStatement": false, + "AfterNamespace": false, + "AfterStruct": false, + "BeforeElse": false, + "BeforeCatch": false + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..2690da1 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# threaded network chat refactor + +The code has been refactored and it compiles. Hopefully it won't explode, but we were going to be +doing a bunch of bug fixes anyway. + +## What now? + +See TODO. From here we're going to push the code to the gitlab like we were told to after everyone +is set up. Follow the coding style guidelines because otherwise everything will be extremely gross +and inconsistent. If you set up your editor correctly, it will take care of most of that stuff. + +## TODO + +- Finalize coding style, + - **see [STYLE.md](STYLE.md)** +- Get everyone's editors set up for clangd/clang-format and git +- Make sure everyone's compiler toolchains work +- Decide who gets what responsibilities +- Distribute appropriate code to people with those responsibilities +- Have everyone upload their respective code to their sections of the gitlab + +## Code + +Everything is in [src/](src). +A mostly untouched copy of [Scott's original code](https://git.therats.win/scott/threaded_network_chat) is also in [old/](old). +A reformatted version of that code is also in [modified-example.cc](modified-example.cc) but it can be ignored. + +## Build + +``` +cd src/ +make +``` diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000..8efbbcf --- /dev/null +++ b/STYLE.md @@ -0,0 +1,122 @@ +# Project Coding Style + +**Subject to change, let's please discuss this.** + +This is a less extreme version of the style I generally follow, and unless someone has a much better style I would +like to use this one for code we will be collaborating on. Feel free to ignore this if you plan for your own code to +be entirely your responsibility. + +Also don't swear on anything we expect to present to the professor. + +**Please install the [C/C++ Extension Pack](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools-extension-pack) and [clang-format](https://marketplace.visualstudio.com/items?itemName=xaver.clang-format) for Visual Studio Code.** + +Follow the instructions in the previous link to set up the correctly. + +## TL;DR + +Call files `.cc` instead of `.c` or `.cpp` and use `.h` headers for everything. + +If you follow the setup instructions you should be fine. Otherwise: + +Use [BSD Kernel Normal Form (KNF)](https://en.wikipedia.org/wiki/Indentation_style#BSD_KNF) for function bodies +and [Kernighan & Ritchie (K&R)](https://en.wikipedia.org/wiki/Indentation_style#K&R) style (with a few modifications stated in **General Guidelines**) everywhere else. + +## Existing Literature + +I recommend skimming over these for inspiration. + +- [Names](https://research.swtch.com/names) - Russ Cox +- [Notes on Programming in C](http://doc.cat-v.org/bell_labs/pikestyle) - Rob Pike + +## General Guidelines + +- Use 8-width tabs, not spaces, for indentation. +- If you copy code from online, copy it by hand. +- Use [K&R style](https://en.wikipedia.org/wiki/Indentation_style#K&R) for indentation and brace placement where possible. + - When writing function bodies: + - return type goes on its own line, + - function name() is on a line below that, and + - open bracket `{` after that. + - no brackets on `for`, `if`, `while`, etc. statements that only contain one line +- Use `/* comments */` for permanent comments, `// comments` for temporary ones e.g. `TODO`'s or notices +- **Try to make every line 100 characters wide or less.** +- Start source files as `.cc` straight away, even if they're pure **C**. + +### C++-specific guidelines + +- file extension: `.cc`, `.h` +- C++ strings are fine +- Vectors are fine +- Use `printf` and co., not `std::cout`, unless somehow absolutely necessary +- `fstream`s for input/output/log files are fine +- Use C's `struct`s instead of objects unless you really really need an object. +- Use C-style type casting where possible + +TL;DR pretend you're using C with basic modern conveniences. **When in doubt, ask.** + +### Naming and Abbreviations + +The length of a function name, variable name, et cetera should be directly proportional to its importance and lifetime. + +#### Don't + +- abbreviate global variables **EVER**, +- mention the data type in a variable name, +- use a full word where an abbreviation will do + - (especially not in a variable that will die 5 lines later), +- use single-letter abbreviations for anything that lasts more than 15 lines, or +- abbreviate a word when an apt abbreviation does not exist. + +#### Do + +- abbreviate extremely short-lived variables to one letter, + - e.g. `for (int index = 0; index < 10; i++)` -> `for (int i = 0; i < 10; i++)` +- break any rule if following it ruins the readability + +## Source File layout + +```C++ +/* C++ includes */ +#include + +/* C (.h) includes */ +#include + +/* local header includes */ +#include "unicorns.h" + +/* "using" directives */ +using namespace std; + +/* global constants */ +const int life = 42; + +/* global variables */ +int degrees = 90; + +/* function prototypes */ +int div_numb(int n, int d); + +/* main function body */ +int +main(int argc, char *argv[]) +{ + int numerator = 32; + int denominator = 16; + int result; + + puts("What is 32 divided by 16?"); + + result = div_numb(32, 16); + + return 0; +} + +/* other function bodies */ +int +div_numb(int n, int d) +{ + return n / d; +} +``` + diff --git a/formatting-configs/.editorconfig b/formatting-configs/.editorconfig new file mode 100644 index 0000000..ee9f09c --- /dev/null +++ b/formatting-configs/.editorconfig @@ -0,0 +1,32 @@ +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 8 +tab_width = 8 +end_of_line = lf +insert_final_newline = true + +[*.{c,cc,cpp,h,hpp}] + +# Unbraced single-statement blocks +cpp_remove_braces_for_single_line_control_statements = true +resharper_cpp_remove_braces_for_single_statements = true +resharper_cpp_add_braces_for_single_line_if = false + +# Enforce BSD KNF for function bracess +cpp_new_line_before_open_brace_function = new_line +resharper_cpp_brace_style = next_line +resharper_cpp_function_definition_braces = next_line + +# Enforce K&R for everything else +cpp_new_line_before_open_brace_namespace = same_line +cpp_new_line_before_open_brace_type = same_line +cpp_new_line_before_open_brace_block = same_line +cpp_new_line_before_open_brace_lambda = same_line +cpp_new_line_before_else = false +cpp_new_line_before_catch = false +resharper_cpp_namespace_declaration_braces = end_of_line +resharper_cpp_type_declaration_braces = end_of_line +resharper_cpp_control_transfer_braces = end_of_line diff --git a/formatting-configs/clang-format.json b/formatting-configs/clang-format.json new file mode 100644 index 0000000..efc5035 --- /dev/null +++ b/formatting-configs/clang-format.json @@ -0,0 +1,20 @@ +{ + "BasedOnStyle": "LLVM", + "UseTab": "Always", + "IndentWidth": 8, + "TabWidth": 8, + "ColumnLimit": 120, + "LineEnding": "LF", + "RemoveBracesLLVM": true, + "AlwaysBreakAfterReturnType": "AllDefinitions", + "BreakBeforeBraces": "Custom", + "BraceWrapping": { + "AfterFunction": true, + "AfterClass": false, + "AfterControlStatement": false, + "AfterNamespace": false, + "AfterStruct": false, + "BeforeElse": false, + "BeforeCatch": false + } +} diff --git a/formatting-configs/clang-format.yml b/formatting-configs/clang-format.yml new file mode 100644 index 0000000..908da54 --- /dev/null +++ b/formatting-configs/clang-format.yml @@ -0,0 +1,21 @@ +# BSD KNF funcs + K&R blocks + other stuff +# Requires clang-format >=16 +BasedOnStyle: LLVM # defaults +UseTab: Always +IndentWidth: 8 +TabWidth: 8 +ColumnLimit: 120 +LineEnding: LF # unix + +RemoveBracesLLVM: true # single-statement blocks +AlwaysBreakAfterReturnType: AllDefinitions # KNF functions + +BreakBeforeBraces: Custom +BraceWrapping: + AfterFunction: true # KNF + AfterClass: false # K&R + AfterControlStatement: false # K&R + AfterNamespace: false # K&R + AfterStruct: false # K&R + BeforeElse: false # K&R + BeforeCatch: false # K&R diff --git a/old/main.cc b/old/main.cc new file mode 100644 index 0000000..6868ec4 --- /dev/null +++ b/old/main.cc @@ -0,0 +1,390 @@ +/*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"; +const int DEFAULT_CUR_X = 2; // the x position of the preferred default cursor + // position for message entry +const int DEFAULT_CUR_Y = 12; // the x position of the preferred default cursor + // position for message entry + +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 + +string logFileName; // the name of the log file + +pthread_t client_wait_thread; + +int writeToFile(string path, string line, bool incLineNum = true); +void *waitForClient(void *argss); +void *pollForClient(); +void *pollForSever(void *args); + +// 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("%s", line.c_str()); + lineNum++; // increment the row number after + // printing each line + } + num++; + } + move(DEFAULT_CUR_Y, DEFAULT_CUR_X); + refresh(); + 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; + int auxInt; // used in client mode because I didn't want to make another + // struct to keep track of +}; + +void * +waitForClient(void *argss) +{ + waitClientArgs *args = static_cast(argss); + clientSocketDescriptor = + accept(serverSocketDescriptor, (sockaddr *)&args->newSockAddr, &args->newSockAddrSize); + if (clientSocketDescriptor >= 0) { + writeToFile(logFileName, "client connected"); + if (linesInFile(logFileName) > LOG_LENGTH) + linePos++; + displayFile(logFileName, 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(logFileName, + msg); // write the client's message to file + if (linesInFile(logFileName) > LOG_LENGTH) + linePos++; + displayFile(logFileName, 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) { + // keeps from bricking the terminal if this happens + endwin(); + + fprintf(stderr, "Error establishing the server socket!\n"); + exit(0); + } + // bind the socket to its local address + int bindStatus = + bind(serverSocketDescriptor, (struct sockaddr *)&servAddr, sizeof(servAddr)); + if (bindStatus < 0) { + // keeps from bricking the terminal if this happens + endwin(); + + fprintf(stderr, "Error binding socket to local address!\n"); + exit(0); + } + writeToFile(logFileName, "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(logFileName, "Server started sucessfully"); + gettimeofday(&start1, NULL); + + return 0; +} + +void +closeServer() +{ + long e = end1.tv_sec - start1.tv_sec; /* the linter freaks out if you + don't save it as a variable */ + + // we need to close the socket descriptors after we're all done + gettimeofday(&end1, NULL); + close(clientSocketDescriptor); + close(serverSocketDescriptor); + + /* Don't just die silently */ + printf("********Session********\n"); + printf("Bytes written: %i\nBytes Read: %i\n", bytesWritten, bytesRead); + printf("Elapsed time: %ld\n secs\n", e); + printf("Connection closed...\n"); +} + +void +closeClient() +{ + long e = end1.tv_sec - start1.tv_sec; + + gettimeofday(&end1, NULL); + close(clientSocketDescriptor); + printf("********Session********"); + printf("Bytes written: %i\nBytes read: %i\n", bytesWritten, bytesRead); + printf("Elapsed time: %ld\n secs\n", e); + printf("Connection closed...\n"); +} + +void * +pollForSever(void *args) +{ + waitClientArgs *aaa = static_cast(args); + int socketDescriptor = (int)aaa->auxInt; + char msg[1024]; + while (1) { + memset(&msg, 0, sizeof(msg)); // clear the buffer + bytesRead += recv(socketDescriptor, (char *)&msg, sizeof(msg), 0); + + // not needed for proofs of concept testing + /*if(!strcmp(msg, "exit")) + { + writeToFile(logFileName, msg); + displayFile(logFileName, linePos, LOG_LENGTH); + break; + }*/ + // cout << "Server: " << msg << endl; + // printf("Server: %s\n"); + writeToFile(logFileName, msg); + if (linesInFile(logFileName) > LOG_LENGTH) + linePos++; + displayFile(logFileName, linePos, LOG_LENGTH); + } +} + +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(logFileName, "Error connecting to socket!"); + writeToFile(logFileName, "Connected to the server!"); + int bytesRead, bytesWritten = 0; + struct timeval start1, end1; + gettimeofday(&start1, NULL); + auto *aaa = new waitClientArgs{}; // it looks stupid but it works + aaa->auxInt = clientSocketDescriptor; + int rc = pthread_create(&client_wait_thread, nullptr, pollForSever, aaa); + pthread_detach(client_wait_thread); +} + +// 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[]) +{ + if (argc > 1 && argv[1][0] == 'c') { + // client mode + logFileName = "client.txt"; + mode = 2; + } else { + // server mode + logFileName = "server.txt"; + mode = 1; + } + + // putting this lower down to circumvent the terminal brick when the + // socket was already in use results in all network functonality no + // longer working. + 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(logFileName) - LOG_LENGTH + 1; + + char *userInput = new char[1024]; + bool exit = false; + + if (mode == 2) { + writeToFile(logFileName, "CLIENT MODE"); + setupClient(); + } else if (mode == 1) { + writeToFile(logFileName, "SERVER MODE"); + setupServer(PORT_NUM); + } + + while (!exit) { + displayFile(logFileName, linePos, LOG_LENGTH); + + // scroll along the screen if and when required so that it stays + // in sync + if (linesInFile(logFileName) > LOG_LENGTH) + linePos++; + + move(12, 0); + printw("> "); + getstr(userInput); + writeToFile(logFileName, userInput); + if (mode == 1) + send(clientSocketDescriptor, (char *)userInput, strlen(userInput), 0); + else + send(clientSocketDescriptor, (char *)userInput, strlen(userInput), 0); + } + + endwin(); + closeServer(); + + return 0; +}