// Peter Bui // CSE 40166 Computer Graphics (Fall 2010) // Example 25: TCP chat client/server extern "C" { #define CCTOOLS_OPSYS_LINUX #include "debug.h" #include "link.h" #include "domain_name_cache.h" #include "username.h" #include "stringtools.h" } #include #include #include #include #include #ifdef __APPLE__ #include #else #include #endif #include #include #include #include #include // Constants ------------------------------------------------------------------- #define CHAT_LINE_MAX 1024 // Data Structures ------------------------------------------------------------- struct message { const char *author; const char *message; time_t timestamp; }; // Global variables ------------------------------------------------------------ static GLint WindowWidth = 640; static GLint WindowHeight = 480; static struct link *ClientLink = NULL; static char ClientUsername[USERNAME_MAX]; static struct link *ServerLink = NULL; static char *ChatHostname = (char *)"localhost"; static int ChatPort = 9999; static bool ChatServer = false; static bool ChatEcho = false; static int PollTableSize = 1024; static struct link_info *PollTable = NULL; static std::list< struct link * > Clients; static std::vector< struct message > Messages; static std::string CurrentMessage = ""; static std::queue< struct message > Outgoing; // Reshape callback ------------------------------------------------------------ void reshape(GLsizei nw, GLsizei nh) { WindowWidth = nw; WindowHeight = nh; glViewport(0, 0, WindowWidth, WindowHeight); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, WindowWidth, 0, WindowHeight); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } // Write text ------------------------------------------------------------------ void write_text(const char *text) { for (const char *c = text; *c; c++) glutBitmapCharacter(GLUT_BITMAP_8_BY_13, *c); } // Display callback ------------------------------------------------------------ void display() { glClear(GL_COLOR_BUFFER_BIT); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3f(1.0, 0.0, 0.0); glRectf(1.0, 26.0, WindowWidth, WindowHeight - 1); glColor3f(0.0, 0.0, 1.0); glRectf(1.0, 0.0, WindowWidth, 24.0); glColor3f(0.0, 1.0, 0.0); for (size_t i = 0; i < Messages.size(); i++) { double height = WindowHeight - (i+1)*12.0; glRasterPos2f(1.0, height); write_text(Messages[i].author); write_text(": "); write_text(Messages[i].message); if (height < 38.0) break; } glColor3f(1.0, 1.0, 1.0); glRasterPos2f(3.0, 8.0); write_text("> "); write_text(CurrentMessage.c_str()); write_text("_"); glutSwapBuffers(); } // Store Message --------------------------------------------------------------- void store_message(const char *author, const char *message, time_t timestamp) { struct message m; m.author = strdup(author); m.message = strdup(message); m.timestamp = timestamp; Messages.push_back(m); glutPostRedisplay(); } // Store Outgoing -------------------------------------------------------------- void store_outgoing(const char *author, const char *message, time_t timestamp) { struct message m; m.author = strdup(author); m.message = strdup(message); m.timestamp = timestamp; Outgoing.push(m); } // Keyboard callback ----------------------------------------------------------- void keyboard(unsigned char key, int x, int y) { int result; char buffer[CHAT_LINE_MAX]; if (isprint(key)) CurrentMessage.push_back(key); if (key == 8) { // Backspace key if (CurrentMessage.size()) CurrentMessage.erase(CurrentMessage.size() - 1); } else if (key == 13) { // Enter key if (CurrentMessage == "/quit" || CurrentMessage == "/exit") { exit(EXIT_SUCCESS); } else if (CurrentMessage == "/clear") { for (size_t i = 0; i < Messages.size(); i++) { free((void *)Messages[i].author); free((void *)Messages[i].message); } Messages.clear(); } else { if (ChatServer) { store_message(ClientUsername, CurrentMessage.c_str(), time(NULL)); store_outgoing(ClientUsername, CurrentMessage.c_str(), time(NULL)); } else { snprintf(buffer, CHAT_LINE_MAX, "\"%s\" \"%s\" %ld\n", ClientUsername, CurrentMessage.c_str(), time(NULL)); result = link_write(ClientLink, buffer, strlen(buffer), time(NULL) + 10); if (result < 0) { store_message("CLIENT", "unable to send message", time(NULL)); link_close(ClientLink); ClientLink = NULL; } if (ChatEcho) store_message(ClientUsername, CurrentMessage.c_str(), time(NULL)); } } CurrentMessage.clear(); } glutPostRedisplay(); } // Client connect -------------------------------------------------------------- void client_connect() { char address[LINK_ADDRESS_MAX]; time_t stoptime; int result; char buffer[CHAT_LINE_MAX]; result = domain_name_cache_lookup(ChatHostname, address); if (!result) fatal("could not lookup name"); stoptime = time(NULL) + 10; ClientLink = link_connect(address, ChatPort, stoptime); if (ClientLink) { link_tune(ClientLink, LINK_TUNE_INTERACTIVE); snprintf(buffer, CHAT_LINE_MAX, "connected to server at %s:%d", ChatHostname, ChatPort); store_message("CLIENT", buffer, time(NULL)); } } // Client read messages -------------------------------------------------------- void client_read_messages() { int result; char buffer[CHAT_LINE_MAX]; int argc; char **argv; if (link_usleep(ClientLink, 1000, 1, 0)) { result = link_readline(ClientLink, buffer, CHAT_LINE_MAX, time(NULL) + 1); if (result > 0) { string_split_quotes(buffer, &argc, &argv); if (argc == 3) store_message(argv[0], argv[1], atoi(argv[2])); free(argv); } else { link_close(ClientLink); ClientLink = NULL; store_message("CLIENT", "disconnected from server", time(NULL)); } } } // Server listen --------------------------------------------------------------- void server_listen() { char buffer[CHAT_LINE_MAX]; ServerLink = link_serve(ChatPort); if (!ServerLink) snprintf(buffer, CHAT_LINE_MAX, "could not listen on port %d: %s", ChatPort, strerror(errno)); else snprintf(buffer, CHAT_LINE_MAX, "listening on port %d", ChatPort); store_message("SERVER", buffer, time(NULL)); PollTable = (struct link_info*)malloc(sizeof(struct link_info *) * PollTableSize); } // Server add client ----------------------------------------------------------- void server_add_client() { struct link *client_link; char address[LINK_ADDRESS_MAX]; int port; char buffer[CHAT_LINE_MAX]; client_link = link_accept(ServerLink, time(NULL)); if (client_link) { link_tune(client_link, LINK_TUNE_INTERACTIVE); if (link_address_remote(client_link, address, &port)) { snprintf(buffer, CHAT_LINE_MAX, "added client from %s:%d", address, port); store_message("SERVER", buffer, time(NULL)); } Clients.push_back(client_link); } } // Server read message --------------------------------------------------------- void server_read_message(struct link *client_link) { char buffer[CHAT_LINE_MAX]; if (link_readline(client_link, buffer, CHAT_LINE_MAX, time(NULL) + 1)) { int argc; char **argv; string_split_quotes(buffer, &argc, &argv); if (argc == 3) { store_message(argv[0], argv[1], atoi(argv[2])); store_outgoing(argv[0], argv[1], atoi(argv[2])); } free(argv); } else { link_close(client_link); Clients.erase(std::find(Clients.begin(), Clients.end(), client_link)); } } // Server send outgoing -------------------------------------------------------- void server_send_outgoing() { char buffer[CHAT_LINE_MAX]; int result; while (!Outgoing.empty()) { struct message m = Outgoing.front(); for (std::list< struct link *>::iterator i = Clients.begin(); i != Clients.end(); i++) { snprintf(buffer, CHAT_LINE_MAX, "\"%s\" \"%s\" %ld\n", m.author, m.message, time(NULL)); result = link_write(*i, buffer, strlen(buffer), time(NULL) + 1); if (result != (int)strlen(buffer)) { link_close(*i); i = Clients.erase(i); } } free((char *)m.author); free((char *)m.message); Outgoing.pop(); } } // Server poll clients --------------------------------------------------------- void server_poll_clients() { int result; int n; PollTable[0].link = ServerLink; PollTable[0].events = LINK_READ; PollTable[0].revents = 0; n = 1; for (std::list< struct link *>::iterator i = Clients.begin(); i != Clients.end(); i++) { PollTable[n].link = (*i); PollTable[n].events = LINK_READ; PollTable[n].revents = 0; n++; } result = link_poll(PollTable, n, 10); if (result < 0) return; if (PollTable[0].revents) server_add_client(); for (int i = 1; i < n; i++) if (PollTable[i].revents) server_read_message(PollTable[i].link); server_send_outgoing(); } // Idle callback --------------------------------------------------------------- void idle() { if (ChatServer) { if (!ServerLink) server_listen(); else server_poll_clients(); } else { if (!ClientLink) client_connect(); else client_read_messages(); } } // Parse command line options -------------------------------------------------- void parse_command_line_options(int argc, char *argv[]) { int c; username_get(ClientUsername); while ((c = getopt(argc, argv, "SEh:p:u:")) >= 0) { switch (c) { case 'S': ChatServer = true; break; case 'E': ChatEcho = true; break; case 'h': ChatHostname = optarg; break; case 'p': ChatPort = atoi(optarg); break; case 'u': strncpy(ClientUsername, optarg, USERNAME_MAX); break; default: fprintf(stderr, "usage: chat_tcp [-E] [-S] [-h hostname] [-p port]\n"); exit(EXIT_FAILURE); } } } // Main execution -------------------------------------------------------------- int main(int argc, char *argv[]) { glutInit(&argc, argv); parse_command_line_options(argc, argv); glutInitWindowSize(WindowWidth, WindowHeight); glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); glutCreateWindow("chat (tcp)"); glutDisplayFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutIdleFunc(idle); glutMainLoop(); return (EXIT_SUCCESS); } // vim: set sts=4 sw=4 ts=8 ft=cpp: --------------------------------------------