Compare commits

..

10 Commits

Author SHA1 Message Date
jazz
75961f9dab cleanup 2026-04-15 23:49:22 -05:00
jazz
2004f084be improve visual clarity 2026-04-15 22:18:32 -05:00
jazz
bc996f6ca1 usage formatting 2026-04-15 18:09:21 -05:00
jazz
e7f7a83c57 comment out unnecessary include 2026-04-15 11:21:32 -05:00
jazz
55cb068585 update readme 2026-04-15 11:19:47 -05:00
jazz
07e742aa4e add options to set address and port, improved usage page 2026-04-15 11:12:05 -05:00
jazz
e3cf81ab3f listen on 0.0.0.0 by default 2026-04-15 10:38:52 -05:00
jazz
2e70901ef8 enforce mode selection, fix indentation, add usage 2026-04-15 10:32:03 -05:00
99dac0f416 Updated Documentation Page 2026-04-15 10:04:03 -05:00
ad882a84d0 Updated Textbox to look nicer 2026-04-08 10:31:45 -05:00
7 changed files with 228 additions and 80 deletions

View File

@@ -2,11 +2,8 @@
## TODO ## TODO
- Finalize coding style, - Improve documentation
- **see [STYLE.md](STYLE.md)** - Fix cursor going in the wrong place when either user receives a message
- Get everyone's editors set up for clangd/clang-format and git
- Make sure everyone's compiler toolchains work
- Assign responsibilities to each team member
## Build ## Build
@@ -17,11 +14,13 @@ make
## Usage ## Usage
``` ```
./ct # run as server ./ct -m server # run as server
``` ```
``` ```
./ct client # run as client ./ct -m client # run as client
``` ```
Running the program without any options will print a help page with more information.
Chat logs are stored in `./server.txt` and `./client.txt`. Chat logs are stored in `./server.txt` and `./client.txt`.

View File

@@ -35,28 +35,23 @@ closeClient()
void * void *
pollForSever(void *args) pollForSever(void *args)
{ {
int b;
char msg[1024], msgf[1033];
waitClientArgs *aaa = static_cast<waitClientArgs *>(args); waitClientArgs *aaa = static_cast<waitClientArgs *>(args);
int socketDescriptor = (int)aaa->auxInt; int socketDescriptor = (int)aaa->auxInt;
char msg[1024];
while (1) { while (1) {
server_message_loop: server_message_loop:
memset(&msg, 0, sizeof(msg)); // clear the buffer memset(&msg, 0, sizeof(msg)); /* clear buffer */
bytesRead += recv(socketDescriptor, (char *)&msg, sizeof(msg), 0); b = recv(socketDescriptor, (char *)&msg, sizeof(msg), 0);
msg[b] = '\0';
// not needed for proofs of concept testing bytesRead += b;
/*if(!strcmp(msg, "exit"))
{
writeToFile(logFileName, msg);
displayFile(logFileName, linePos, LOG_LENGTH);
break;
}*/
// cout << "Server: " << msg << endl;
// printf("Server: %s\n");
if (msg[0] == '\0') if (msg[0] == '\0')
goto server_message_loop; goto server_message_loop;
writeToFile(logFileName, msg); snprintf(msgf, sizeof(msgf), "Server: %s", msg);
writeToFile(logFileName, msgf);
if (linesInFile(logFileName) > LOG_LENGTH) if (linesInFile(logFileName) > LOG_LENGTH)
linePos++; linePos++;
@@ -71,13 +66,14 @@ void
setupClient() setupClient()
{ {
// we need 2 things: ip address and port number, in that order // 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 // create a message buffer
char msg[1024]; char msg[1024];
// setup a socket and connection tools
if (IP_ADDRESS == "0.0.0.0")
IP_ADDRESS = "127.0.0.1";
/* prepare socket and connection tools */
struct hostent *host = gethostbyname(IP_ADDRESS.c_str()); struct hostent *host = gethostbyname(IP_ADDRESS.c_str());
sockaddr_in sendSockAddr; sockaddr_in sendSockAddr;
bzero((char *)&sendSockAddr, sizeof(sendSockAddr)); bzero((char *)&sendSockAddr, sizeof(sendSockAddr));
@@ -85,7 +81,8 @@ setupClient()
sendSockAddr.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr *)*host->h_addr_list)); sendSockAddr.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr *)*host->h_addr_list));
sendSockAddr.sin_port = htons(PORT_NUM); sendSockAddr.sin_port = htons(PORT_NUM);
clientSocketDescriptor = socket(AF_INET, SOCK_STREAM, 0); clientSocketDescriptor = socket(AF_INET, SOCK_STREAM, 0);
// try to connect...
/* try to connect... */
int status = int status =
connect(clientSocketDescriptor, (sockaddr *)&sendSockAddr, sizeof(sendSockAddr)); connect(clientSocketDescriptor, (sockaddr *)&sendSockAddr, sizeof(sendSockAddr));

64
ct.1
View File

@@ -1,22 +1,62 @@
.TH "Threaded Network Chat" 1 .TH CT 1
.SH NAME .SH NAME
\fBct\fR - Threaded Network Chat ct \- simple threaded network chat program
.SH SYNOPSIS .SH SYNOPSIS
.SY .B ct
./ct # start as server .br
.B ct client
./ct # start as client
/quit # while program running
.YS
.SH DESCRIPTION .SH DESCRIPTION
.LP
\fBct\fR is a simple terminal-based chat application that allows communication
between two users over a network connection. The program operates in either
server or client mode.
.LP .LP
This program is meant to run as two instances. One for a "host" or "server" user When run without arguments, the program starts in server mode and listens for
to initiate a session and the other for a "client" user to join the host. incoming connections on port 8888. When run with the \fBclient\fR argument, the
program connects to a server running on localhost.
.LP .LP
Messages are sent as buffered plain text with minimal processing, if any. The interface is text-based and uses the ncurses library to display a scrolling
chat log and an input area. Messages entered by the user are sent over the
network and recorded in a local log file.
.LP
All messages are transmitted as plain text with minimal processing.
.SH MODES
.TP
.B Server Mode
Default mode. Starts a server and waits for a client to connect. Messages are
logged to \fBserver.txt\fR.
.TP
.B Client Mode
Activated by running \fBct client\fR. Connects to a server at 127.0.0.1 on port
8888. Messages are logged to \fBclient.txt\fR.
.SH COMMANDS
.TP
.B /quit
Exit the program.
.SH FILES
.TP
.B server.txt
Log file used in server mode.
.TP
.B client.txt
Log file used in client mode.
.SH NOTES
.LP
This program currently supports a single client-server connection.
.LP
The program must be run in two separate instances: one as server and one as
client.

View File

@@ -24,7 +24,7 @@ clearRows(int startingRow, int endingRow)
// display a file using ncurses // display a file using ncurses
int int
displayFile(string path, int startLineNum = 0, int numLines = 10) displayFile(string path, int startLineNum = 0, int numLines = height - 10)
{ {
ifstream file(path); ifstream file(path);
if (!file) { if (!file) {
@@ -41,14 +41,14 @@ displayFile(string path, int startLineNum = 0, int numLines = 10)
// line number isn't too high // line number isn't too high
{ {
if (num >= startLineNum) { if (num >= startLineNum) {
move(lineNum, 0); move(lineNum, 2);
printw("%s", line.c_str()); printw("%s", line.c_str());
lineNum++; // increment the row number after lineNum++; // increment the row number after
// printing each line // printing each line
} }
num++; num++;
} }
move(DEFAULT_CUR_Y, DEFAULT_CUR_X); movetobox();
refresh(); refresh();
return 0; return 0;
} }

149
main.cc
View File

@@ -13,7 +13,8 @@
#include <unistd.h> #include <unistd.h>
#include <string> #include <string>
#include <vector> //try to make the program work without this without doing anything awful but do it later // #include <vector> //try to make the program work without this without doing anything awful but do
// it later
#include "client.h" #include "client.h"
#include "disp.h" #include "disp.h"
@@ -23,13 +24,14 @@
using namespace std; using namespace std;
const int PORT_NUM = 8888; int PORT_NUM = 8888;
string IP_ADDRESS = "127.0.0.1"; string IP_ADDRESS = "0.0.0.0";
const int DEFAULT_CUR_X = 2; // the x position of the preferred default cursor const int DEFAULT_CUR_X = 2; // the x position of the preferred default cursor
// position for message entry // position for message entry
const int DEFAULT_CUR_Y = 12; // the x position of the preferred default cursor const int DEFAULT_CUR_Y = 12; // the x position of the preferred default cursor
// position for message entry // position for message entry
int width, height;
chatmode_t mode = NO_MODE; // what mode is this program in. 0 = nothing. 1 = server. 2 = client chatmode_t mode = NO_MODE; // what mode is this program in. 0 = nothing. 1 = server. 2 = client
int serverSocketDescriptor; // a global variable for storing the server socket int serverSocketDescriptor; // a global variable for storing the server socket
@@ -37,8 +39,8 @@ int serverSocketDescriptor; // a global variable for storing the server socket
int clientSocketDescriptor; int clientSocketDescriptor;
// This has not been used: // This has not been used:
//vector<int> clientSocketDescriptors; // a global vector for storing client // vector<int> clientSocketDescriptors; // a global vector for storing client
// socket descriptors // socket descriptors
// keep track of the session time using global variables // keep track of the session time using global variables
struct timeval start1, end1; struct timeval start1, end1;
@@ -60,15 +62,53 @@ struct waitClientArgs {
// struct to keep track of // struct to keep track of
}; };
void usage(char *progname, bool *m);
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
if (argc > 1 && (!strncmp(argv[1], "client", 6))) { /*
* ch: getopt
* exit: set to true when infinite loop is to end
* userInput: character buffer for the user's next message
* modeless: check whether a modeless exit was accidental
*/
int ch, inlen;
bool exit = false;
char *userInput = new char[1024];
char *userInputF = new char[1032];
bool modeless = false;
if (argc == 1) {
usage(argv[0], &modeless);
goto leave;
}
while ((ch = getopt(argc, argv, "m:a:p:h")) != -1)
switch (ch) {
case 'm':
if (!strncmp(optarg, "client", 6)) {
logFileName = "client.txt"; logFileName = "client.txt";
mode = CLIENT_MODE; mode = CLIENT_MODE;
} else { } else if (!strncmp(optarg, "server", 6)) {
logFileName = "server.txt"; logFileName = "server.txt";
mode = SERVER_MODE; mode = SERVER_MODE;
} else {
usage(argv[0], &modeless);
goto leave;
}
break;
case 'a':
IP_ADDRESS = optarg;
break;
case 'p':
PORT_NUM = atoi(optarg);
break;
case 'h':
default:
usage(argv[0], &modeless);
goto leave;
break;
} }
// putting this lower down to circumvent the terminal brick when the // putting this lower down to circumvent the terminal brick when the
@@ -85,17 +125,14 @@ main(int argc, char *argv[])
// there was already content in the log file // there was already content in the log file
linePos = linesInFile(logFileName) - LOG_LENGTH + 1; linePos = linesInFile(logFileName) - LOG_LENGTH + 1;
char *userInput = new char[1024];
bool exit = false;
switch (mode) { switch (mode) {
case CLIENT_MODE: case CLIENT_MODE:
writeToFile(logFileName, "CLIENT MODE"); writeToFile(logFileName, "SYSTEM: CLIENT MODE");
setupClient(); setupClient();
linePos++; linePos++;
break; break;
case SERVER_MODE: case SERVER_MODE:
writeToFile(logFileName, "SERVER MODE"); writeToFile(logFileName, "SYSTEM: SERVER MODE");
setupServer(PORT_NUM); setupServer(PORT_NUM);
linePos++; linePos++;
break; break;
@@ -105,6 +142,34 @@ main(int argc, char *argv[])
} }
while (!exit) { while (!exit) {
clear();
// Draw outer chat border
box(stdscr, 0, 0);
// Get screen size
// int height, width;
getmaxyx(stdscr, height, width);
// Draw separator line above input
mvhline(height - 3, 1, 0, width - 2);
// Labels
mvprintw(0, 2, " Chat ");
// mvprintw(height - 3, 2, " Input ");
switch (mode) {
case CLIENT_MODE:
mvprintw(height - 3, 2, " Client ");
break;
case SERVER_MODE:
mvprintw(height - 3, 2, " Server ");
break;
default:
mvprintw(height - 3, 2, " Input ");
break;
}
// Display Chat Log
displayFile(logFileName, linePos, LOG_LENGTH); displayFile(logFileName, linePos, LOG_LENGTH);
// scroll along the screen if and when required so that it stays // scroll along the screen if and when required so that it stays
@@ -112,21 +177,36 @@ main(int argc, char *argv[])
if (linesInFile(logFileName) > LOG_LENGTH) if (linesInFile(logFileName) > LOG_LENGTH)
linePos++; linePos++;
/* clear message box / reset cursor */ // Input area
move(12, 0); move(height - 2, 2);
printw(">\t\t\t"); clrtoeol();
move(12, 2); printw("You: ");
movetobox();
refresh();
getstr(userInput); getstr(userInput);
writeToFile(logFileName, userInput);
if (!strncmp(userInput, "/quit", 5)) if (!strncmp(userInput, "/quit", 5))
exit = true; exit = true;
if (mode == 1) inlen = strlen(userInput);
send(clientSocketDescriptor, (char *)userInput, strlen(userInput), 0);
else switch (mode) {
send(clientSocketDescriptor, (char *)userInput, strlen(userInput), 0); case SERVER_MODE:
snprintf(userInputF, 1032 * sizeof(char), "Server: %s", userInput);
send(clientSocketDescriptor, (char *)userInput, inlen, 0);
break;
case CLIENT_MODE:
snprintf(userInputF, 1032 * sizeof(char), "Client: %s", userInput);
send(clientSocketDescriptor, (char *)userInput, inlen, 0);
break;
default:
goto leave;
break;
}
writeToFile(logFileName, userInputF);
} }
leave: leave:
@@ -141,11 +221,34 @@ leave:
closeServer(); closeServer();
break; break;
default: default:
puts("Warn: program appears to have successfully finished without ever setting " if (!modeless) {
"mode."); puts("Warn: program appears to have successfully finished without ever "
"setting mode.");
return 1; return 1;
}
return 2;
break; break;
} }
return 0; return 0;
} }
/* remember to call 'goto leave' after running */
void
usage(char *progname, bool *m) // m:a:p:h
{
printf("\x1b[1mUsage:\x1b[0m\n"
" %s client\t\x1b[3m# run as client\x1b[0m\n"
" %s server\t\x1b[3m# run as server\x1b[0m\n"
"\n\n"
"\x1b[1mOptions:\x1b[0m\n"
" \x1b[1m-m [client|server]\x1b[0m\tMode \x1b[1;31m(required)\x1b[0m\n"
"\n"
" \x1b[1m-a [255.255.255.255]\x1b[0m\tAddress\n"
" \t\t\tserver mode: listen address \x1b[33m(default: 0.0.0.0)\x1b[0m\n"
" \t\t\tclient mode: address of server \x1b[33m(default: 127.0.0.1)\x1b[0m\n"
"\n"
" \x1b[1m-p [0-65535]\x1b[0m\t\tPort number\n",
progname, progname);
*m = true;
}

View File

@@ -7,8 +7,9 @@ using namespace std;
typedef enum { NO_MODE = 0, SERVER_MODE = 1, CLIENT_MODE = 2 } chatmode_t; typedef enum { NO_MODE = 0, SERVER_MODE = 1, CLIENT_MODE = 2 } chatmode_t;
extern int width, height;
extern chatmode_t mode; extern chatmode_t mode;
extern const int PORT_NUM; extern int PORT_NUM;
extern string IP_ADDRESS; extern string IP_ADDRESS;
extern const int DEFAULT_CUR_X; extern const int DEFAULT_CUR_X;
extern const int DEFAULT_CUR_Y; extern const int DEFAULT_CUR_Y;
@@ -27,4 +28,7 @@ void *waitForClient(void *argss);
void *pollForClient(); void *pollForClient();
void *pollForSever(void *args); void *pollForSever(void *args);
#define movetobox() \
move(height - 2, 7)
#endif #endif

View File

@@ -26,7 +26,7 @@ waitForClient(void *argss)
clientSocketDescriptor = clientSocketDescriptor =
accept(serverSocketDescriptor, (sockaddr *)&args->newSockAddr, &args->newSockAddrSize); accept(serverSocketDescriptor, (sockaddr *)&args->newSockAddr, &args->newSockAddrSize);
if (clientSocketDescriptor >= 0) { if (clientSocketDescriptor >= 0) {
writeToFile(logFileName, "client connected"); writeToFile(logFileName, "SYSTEM: Client connected.");
if (linesInFile(logFileName) > LOG_LENGTH) if (linesInFile(logFileName) > LOG_LENGTH)
linePos++; linePos++;
displayFile(logFileName, linePos, LOG_LENGTH); displayFile(logFileName, linePos, LOG_LENGTH);
@@ -39,17 +39,22 @@ waitForClient(void *argss)
void * void *
pollForClient() pollForClient()
{ {
char msg[1024]; int b;
char msg[1024], msgf[1033];
while (1) { while (1) {
client_message_loop: client_message_loop:
// receive a message from the client (listen) /* receive a message from the client (listen) */
memset(&msg, 0, sizeof(msg)); // clear the buffer memset(&msg, 0, sizeof(msg)); /* clear buffer */
bytesRead += recv(clientSocketDescriptor, (char *)&msg, sizeof(msg), 0); b = recv(clientSocketDescriptor, (char *)&msg, sizeof(msg) - 1, 0);
msg[b] = '\0';
bytesRead += b;
if (msg[0] == '\0') if (msg[0] == '\0')
goto client_message_loop; goto client_message_loop;
writeToFile(logFileName, msg); snprintf(msgf, sizeof(msgf), "Client: %s", msg);
writeToFile(logFileName, msgf);
if (linesInFile(logFileName) > LOG_LENGTH) if (linesInFile(logFileName) > LOG_LENGTH)
linePos++; linePos++;
@@ -91,7 +96,7 @@ setupServer(int port)
fprintf(stderr, "Error binding socket to local address!\n"); fprintf(stderr, "Error binding socket to local address!\n");
exit(0); exit(0);
} }
writeToFile(logFileName, "Waiting for a client to connect..."); writeToFile(logFileName, "SYSTEM: Waiting for a client to connect...");
// listen for up to 5 requests at a time // listen for up to 5 requests at a time
listen(serverSocketDescriptor, 5); listen(serverSocketDescriptor, 5);
// receive a request from client using accept // receive a request from client using accept
@@ -105,7 +110,7 @@ setupServer(int port)
aaa->newSockAddrSize = newSockAddrSize; aaa->newSockAddrSize = newSockAddrSize;
int rc = pthread_create(&client_wait_thread, nullptr, waitForClient, aaa); int rc = pthread_create(&client_wait_thread, nullptr, waitForClient, aaa);
pthread_detach(client_wait_thread); pthread_detach(client_wait_thread);
writeToFile(logFileName, "Server started successfully"); writeToFile(logFileName, "SYSTEM: Server started successfully.");
gettimeofday(&start1, NULL); gettimeofday(&start1, NULL);
return 0; return 0;