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 FIFOs en lugar de sockets.
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 socket as skt
import sys
import gobject, glib
HOST = "localhost"
PORT = 5000
class Client():
""" The Client class takes care of our communications by socket.
"""
def __init__(self, host, port):
""" Create the socket, but do NOT connect yet. That way we
can still change some parameters of the
"""
self.host = host
self.port = port
self.socket = skt.socket(skt.AF_INET, skt.SOCK_STREAM)
# Construir el zocalo
self.show_status_cb = None
self.show_data_cb = None
self.data = '\n'
def connect(self):
""" Now really try to connect to the server.
I really need to add an exception handler in case the server
is not on-line.
"""
self.socket.connect((HOST, PORT))
self.socket.setblocking(False)
self.fd = self.socket.fileno() # Obtener el file descriptor
self.show_status("Connected to " + self.host)
glib.io_add_watch(self.fd, glib.IO_IN, self.process_input)
# Instalar la funcion en caso
# de datos recibidos
def process_input(self, skt, cond):
""" This function is called asynchronously when data is received.
"""
msg = self.socket.recv(100) # Recibir el mensage de la red
self.show_data(msg) # Enviar los datos a MainWindow
return True # Queremos quedar activos
def send(self, msg):
self.socket.send(msg)
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()
txtview.set_buffer(self.txtbff)
txtview.set_can_focus(False)
# 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):
self.client.send(widget.get_text() + "\n")
widget.set_text("")
def show_status(self, msg):
self.statlbl.set_text(msg)
def show_data(self, msg):
self.txtbff.insert(self.txtbff.get_end_iter(), msg)
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(HOST, PORT) # and the communications package
main.set_client(client) # Announce the client to the main window
client.connect() # and try to do the real connection
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 <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 <gtk/gtk.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "config.h"
#include "socketcom.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 TCP_HOST "127.0.0.1"
#define TCP_PORT 5000
GtkWidget *MainWindow,
*entry;
GtkTextBuffer *txtbuffer;
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)
{
network_write_chars(channel, (char *) gtk_entry_get_text(entry));
network_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);
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"));
txtbuffer = gtk_text_buffer_new(NULL);
gtk_text_view_set_buffer(GTK_TEXT_VIEW(txtview), txtbuffer);
g_object_unref(G_OBJECT(builder));
gtk_widget_show_all(MainWindow);
start_socket(TCP_HOST, TCP_PORT, add_to_textbuffer);
gtk_main();
close_socket(listenfd);
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 (*netread_callback)(char *);
#include <gtk/gtk.h>
extern GIOChannel *channel;
gboolean network_read(GIOChannel *source, GIOCondition cond,
gpointer data);
gboolean network_write_chars(GIOChannel *dest, char *message);
gboolean new_connection(GSocketService *service,
GSocketConnection *connection,
GObject *source_object,
gpointer user_data);
void start_socket(const char *host, int port,
netread_callback callback);
void close_socket(int listenfd);
#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 "socketcom.h"
GIOChannel *channel;
gboolean
network_read(GIOChannel *source,
GIOCondition cond,
gpointer data)
{
char bff[100];
GError *error = NULL;
gsize bytes_read;
GIOStatus ret;
printf("Channel mon installed\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;
((netread_callback) data)(bff);
printf("Got: %s\n", bff);
}
return TRUE;
}
gboolean
network_write_chars(GIOChannel *dest, char *message)
{
GError *error = NULL;
gsize bytes_written;
GIOStatus ret;
printf("Trying to write %s\n", message);
ret = g_io_channel_write_chars(dest, message, strlen(message),
&bytes_written, &error);
if (ret == G_IO_STATUS_ERROR)
g_error("Error writing: %s\n", error->message);
g_io_channel_flush(dest, &error); // Important! Else message stays
// in the buffer
return TRUE;
}
gboolean
new_connection(GSocketService *service,
GSocketConnection *connection,
GObject *source_object,
gpointer user_data)
{
g_object_ref(connection);
GSocketAddress *sockaddr;
GInetAddress *addr;
guint16 port;
sockaddr = g_socket_connection_get_remote_address(connection, NULL);
addr = g_inet_socket_address_get_address(G_INET_SOCKET_ADDRESS(sockaddr));
port = g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(sockaddr));
printf("New Connection from %s:%d\n", g_inet_address_to_string(addr), port);
GSocket *socket = g_socket_connection_get_socket(connection);
gint fd = g_socket_get_fd(socket);
channel = g_io_channel_unix_new(fd);
g_io_add_watch(channel, G_IO_IN, (GIOFunc) network_read, user_data);
return TRUE;
}
void
start_socket(const char *host, int port, netread_callback callback)
{
GSocketService *service;
GInetAddress *address;
GSocketAddress *socket_address;
service = g_socket_service_new();
address = g_inet_address_new_from_string(host);
socket_address = g_inet_socket_address_new(address, port);
g_socket_listener_add_address(
G_SOCKET_LISTENER(service),
socket_address,
G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_TCP,
NULL, NULL, NULL);
g_object_unref(socket_address);
g_object_unref(address);
g_signal_connect(service, "incoming", G_CALLBACK(new_connection),
(gpointer) callback);
g_socket_service_start(service);
}
void
close_socket(int listenfd)
{
close(listenfd);
}
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.
10247(c) John Coppens ON6JC/LW3HAZ | correo |