Idioma:
English
En esta página
GUI
Código cliente
Código servidor
Gracias
Un buen ejercicio parece ser la fabricación de un programa 'chat' - por mas sencillo que sea - para conocer las intricacias de manejar los canales de comunicación. Este ejemplo muestra un ejemplo de como realizar una aplicación 'chat' entre dos ventanas. Para hacer el ejemplo mas (y para aprender mas), hice el cliente en Python, y el servidor en C (en realidad C++, pero sin el uso de clases).
El problema principal fue el requerimiento que la atención a la recepción de datos tenía que implementarse asincronicamente - es decir, sin un 'lazo de espera', ya que la aplicación final de este código es un programa de control.
Hay una versión disponible que utiliza sockets en lugar de FIFOs.
El diagrama a la izquerda muestra los elementos de gtk+ utilizados
para construir la interfaz de usuario:
El rectángulo verde es el contenedor (GtkVBox) para toda la pantalla, mienteas que el GtkLabel y el GtkButton están ubicados en un GtkHBox (rectángulo azul). |
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# client.py
#
# Copyright 2014 John Coppens <john@jcoppens.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
#
import gtk
import pygtk
import sys
import gobject, glib
import os, os.path
FIFO_ROOT = "./fifo"
class Client():
""" The Client class takes care of our communications by FIFO.
"""
def __init__(self, fifo_root):
""" Create the FIFO
"""
self.fifo_root = fifo_root
# First check if the client socket is left over from a previous session
if os.path.exists(self.fifo_root + '.client'):
os.remove(self.fifo_root + '.client')
# Then create a new one
os.mkfifo(self.fifo_root + '.client')
# Then check if the server has created a fifo
if os.path.exists(self.fifo_root + '.server'):
self.out_fd = os.open(self.fifo_root + '.server',
os.O_WRONLY | os.O_APPEND)
self.out_f = os.fdopen(self.out_fd, "w")
print "opening client for read"
self.in_fd = os.open(self.fifo_root + '.client',
os.O_RDONLY | os.O_NONBLOCK, 0o644)
self.in_f = os.fdopen(self.in_fd)
print "client open"
else:
print "Server is not running!"
sys.exit(1)
self.show_status_cb = None
self.show_data_cb = None
glib.io_add_watch(self.in_fd, glib.IO_IN, self.process_input)
def process_input(self, port, cond):
""" This function is called asynchronously when data is received.
"""
msg = self.in_f.readline() # Get the message from the FIFO
self.show_data(msg) # And send it to MainWindow
return True # We want to stay alive
def send(self, msg):
self.out_f.write(msg)
self.out_f.flush()
def show_status(self, msg):
""" Show status changes from this class, either on the terminal,
or else by calling the callback installed by set_status_show().
"""
if self.show_status_cb == None:
print "Status: ", msg
else:
self.show_status_cb(msg)
def show_data(self, msg):
""" Show received messages, either on the terminal,
or else by calling the callback installed by set_data_show().
"""
if self.show_data_cb == None:
print "Data: ", msg
else:
self.show_data_cb(msg)
def set_status_show(self, show_status_cb):
self.show_status_cb = show_status_cb
def set_data_show(self, show_data_cb):
self.show_data_cb = show_data_cb
class MainWindow(gtk.Window):
def __init__(self):
gtk.Window.__init__(self);
self.connect("delete-event", self.destroy);
self.set_size_request(400, 300)
# Make the upper window, which the received text.
txtview = gtk.TextView()
self.txtbff = gtk.TextBuffer()
self.txttagtable = self.txtbff.get_tag_table()
txtview.set_buffer(self.txtbff)
txtview.set_can_focus(False)
texttag_red = gtk.TextTag("red")
texttag_red.set_property("foreground", "red")
self.txttagtable.add(texttag_red)
texttag_black = gtk.TextTag("black")
texttag_black.set_property("foreground", "black")
self.txttagtable.add(texttag_black)
# This is where the text to be transmitted is typed.
# Pressing the 'Enter' key transmits the string.
entry = gtk.Entry()
entry.connect("activate", self.entry_activated);
# And the button to exit the chat program
button = gtk.Button("Quit")
button.set_border_width(4)
button.set_can_focus(False)
button.connect("clicked", self.quit_clicked)
# Left of the button is a label, which can be used to
# show status messages.
hbox = gtk.HBox()
self.statlbl = gtk.Label("")
hbox.pack_start(self.statlbl, expand = True, fill = True)
hbox.pack_start(button, expand = False)
# Pack everything into a VBox, then that into the window.
vbox = gtk.VBox()
vbox.set_spacing(4)
vbox.pack_start(txtview)
vbox.pack_start(entry, expand = False)
vbox.pack_start(hbox, expand = False)
self.add(vbox)
entry.grab_focus()
self.show_all()
def destroy(self, window, data):
""" Called when the MainWindow is closed - this stops the
GTK+ main loop, and terminates the program
"""
gtk.main_quit()
def entry_activated(self, widget):
msg = widget.get_text() + "\n"
self.client.send(msg)
self.show_data(msg, color = 'black')
widget.set_text("")
def show_status(self, msg):
self.statlbl.set_text(msg)
def show_data(self, msg, color = 'red'):
self.txtbff.insert_with_tags_by_name(self.txtbff.get_end_iter(), msg, color)
def set_client(self, client):
""" The client is the communication system which wants to show
things here, and get strings to transmit from the GtkEntry
"""
self.client = client
# We connect callbacks to get news from the client
client.set_status_show(self.show_status)
client.set_data_show(self.show_data)
def quit_clicked(self, widget):
gtk.main_quit()
def run(self):
gtk.main()
def main():
main = MainWindow() # Prepare the main window,
client = Client(FIFO_ROOT) # and the communications package
main.set_client(client) # Announce the client to the main window
main.run()
return 0
if __name__ == '__main__':
main()
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* main.cc
* Copyright (C) 2014 John Coppens <john@jcoppens.com>
*
* socket_test is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* socket_test is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <gtk/gtk.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "config.h"
#include "fifocom.h"
#ifdef ENABLE_NLS
# include <libintl.h>
#endif
// For testing propose use the local (not installed) ui file
// #define UI_FILE PACKAGE_DATA_DIR"/ui/socket_test.ui"
#define UI_FILE "src/socket_test.ui"
#define FIFO_ROOT "fifo"
GtkWidget *MainWindow;
GtkTextBuffer *txtbuffer;
// Note: For some strange reason the 'extern "C"' is necessary to define
// handlers for the gtk_builder_connect_signals() call - and only if
// g++ is used!
extern "C" gboolean
quit_button_clicked_cb(GtkButton *btn)
{
gtk_main_quit();
}
extern "C" void
on_main_window_delete_event(GtkWidget *w)
{
gtk_main_quit();
}
extern "C" void
tx_entry_activate(GtkEntry *entry)
{
write_chars(channel, (char *) gtk_entry_get_text(entry));
write_chars(channel, (char *) "\n");
gtk_entry_set_text(entry, "");
}
void
add_to_textbuffer(char *message)
{
GtkTextIter iter;
gtk_text_buffer_get_end_iter(txtbuffer, &iter);
gtk_text_buffer_insert(txtbuffer, &iter, message, strlen(message));
}
int
main (int argc, char *argv[])
{
GtkWidget *txtview;
GError *error = NULL;
int listenfd;
gtk_init(&argc, &argv);
GtkBuilder *builder = gtk_builder_new();
gtk_builder_add_from_file(builder, UI_FILE, &error);
gtk_builder_connect_signals(builder, NULL);
// Get references to some of the widgets we use frequently
MainWindow = GTK_WIDGET(gtk_builder_get_object(builder, "main_window"));
txtview = GTK_WIDGET(gtk_builder_get_object(builder, "textview1"));
MainWindow = GTK_WIDGET(gtk_builder_get_object(builder, "tx_entry"));
// 'Manually' create a textbuffer and connect it to the textview
txtbuffer = gtk_text_buffer_new(NULL);
gtk_text_view_set_buffer(GTK_TEXT_VIEW(txtview), txtbuffer);
g_object_unref(G_OBJECT(builder)); // Don't need the builder anymore
gtk_widget_show_all(MainWindow); // Queue drawing all widgets
start_comm(FIFO_ROOT, add_to_textbuffer);
gtk_main();
close_comm();
return 0;
}
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* socketcom.h
* Copyright (C) 2014 Unknown <root@on6jc>
*
* socket_test is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* socket_test is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _SOCKETCOM_H_
#define _SOCKETCOM_H_
typedef void (*read_callback)(char *);
#include <gtk/gtk.h>
extern GIOChannel *channel;
gboolean read_watch(GIOChannel *source, GIOCondition cond, gpointer data);
gboolean write_chars(GIOChannel *dest, char *message);
void start_comm(const char *fifo_root, read_callback callback);
void close_comm();
#endif // _SOCKETCOM_H_
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* socketcom.cc
* Copyright (C) 2014 Unknown <root@on6jc>
*
* socket_test is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* socket_test is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glib.h>
#include "fifocom.h"
GIOChannel *channel;
char *client_fn, *server_fn;
gboolean
read_watch(GIOChannel *source, GIOCondition cond, gpointer data)
{
char bff[100];
GError *error = NULL;
gsize bytes_read;
GIOStatus ret;
printf("read_watch called\n");
ret = g_io_channel_read_chars(source, bff, sizeof(bff), &bytes_read, &error);
if (ret == G_IO_STATUS_ERROR)
g_error("Error reading: %s\n", error->message);
else {
bff[bytes_read] = 0;
((read_callback) data)(bff);
printf("Got: %s\n", bff);
}
return TRUE;
}
gboolean
write_chars(GIOChannel *dest, char *message)
{
int out_fd;
printf("Trying to write %s\n", message);
out_fd = open(client_fn, O_WRONLY);
write(out_fd, message, strlen(message));
close(out_fd);
return TRUE;
}
void
start_comm(const char *fifo_root, read_callback callback)
{
int in_fd, ch_id;
server_fn = g_strdup_printf("%s.server", fifo_root);
client_fn = g_strdup_printf("%s.client", fifo_root);
if (g_file_test(server_fn, G_FILE_TEST_EXISTS))
unlink(server_fn); // g_remove wasn't declared???
// Then create a new one
mkfifo(server_fn, 0666);
// Open the file for reading
in_fd = open(server_fn, O_RDONLY | O_NONBLOCK);
channel = g_io_channel_unix_new(in_fd);
ch_id = g_io_add_watch(channel, G_IO_IN, (GIOFunc) read_watch,
(gpointer) callback);
printf("IOchannel ID: %d\n", ch_id);
}
void
close_comm()
{
GError *error = NULL;
g_io_channel_shutdown(channel, TRUE, &error);
}
Y a aquellos en Pygments por la rutina de colorización de código.
Anjuta fue utilizado para escribir y comprobar al código C, y Geany para la edición del código Python.
6243(c) John Coppens ON6JC/LW3HAZ | correo |