Example 25

Example 25

This program is a simple OpenGL chat program that uses TCP:

usage: chat_tcp [-E] [-S] [-h hostname] [-p port] [-u username]

Run a server:

./chat_tcp -S &

Run a client:

./chat_tcp -u MyName

Server

ex_25/chat_tcp_server.png

Client

ex_25/chat_tcp_peter.png

Source Code

chat_tcp.cc:

// 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 <cerrno>
#include <cmath>
#include <ctime>
#include <cstdlib>
#include <cstring>

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#include <algorithm>
#include <list>
#include <queue>
#include <string>
#include <vector>

// 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: --------------------------------------------

Files