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