La computación concurrente es la capacidad de simultanear en la ejecución de múltiples tareas interactivas. Un proceso es un programa en ejecución, que es gestionado por el Sistema Operativo y compite por los recursos del procesador. Los procesos tienen estado y memoria en ejecución reservada. El mecanismo por el cual un proceso crea otro proceso se denomina bifurcación (fork). Cuando un proceso se bifurca, se crea una copia exacta del proceso en ejecución, independiente del resto y no comparte el espacio de memoria con el proceso que los ha creado ni con otros procesos.
Un hilo, en sistemas operativos, es una característica que permite a una aplicación realizar varias tareas a la vez (concurrentemente). Los distintos hilos de ejecución comparten una serie de recursos tales como el espacio de memoria, o los archivos abiertos. Esta técnica permite simplificar el diseño de una aplicación que debe llevar a cabo distintas funciones simultáneamente. Un hilo es un tarea que se ejecuta en paralelo con otra tarea.
La comunicación entre el cliente y el servidor se hace posible gracias a estructuras abstractas denominadas sockets, mediante las cuales los programas pueden intercambiar flujo de datos e información. Este concepto está asociado al concepto de puerto.
Un puerto es una forma genérica de denominar a una interfaz a través de la cual los diferentes tipos de datos se pueden enviar y recibir. En el protocolo TCP/IP, son de tipo lógico, por ejemplo, los puertos que permiten la transmisión de datos entre diferentes computadores.
Existen dos tipos de sockets, orientados a conexión y no orientados a conexión. La diferencia fundamental es que en los sockets orientados a conexión (TCP) el protocolo garantiza que los datos serán entregados en su destino sin errores y en el mismo orden en que se transmitieron, mientras que en los sockets no orientados a conexión (o UDP) no se garantiza que el mensaje llegue a su destino. Parece claro que si el programa envía un mensaje y no hay nadie escuchando, ese mensaje se pierde. De todas formas, aunque haya alguien escuchando, el protocolo tampoco garantiza que el mensaje llegue. Lo único que garantiza es, que si llega, llega sin errores.
En una comunicación con sockets multihilo, como es el caso de la implementación que nos ocupa, cada conexión es gestionada por un proceso o hilo de ejecución independiente. Así, con cada cliente que conecte con el servidor, se expande un nuevo hilo del lado del servidor, que gestionará las peticiones del cliente asociado a él. Servidores concurrentes pueden ser multiproceso o multihilo. Estos son utilizados para servicios largos con mucha sincronización Cliente-Servidor.
A continuación dejo el código de ambas implementaciones, primero en C++ y después en Python.
Comandos
- ADD usuario:Introduce un usuario en el chat.
- LIST: Obtiene la lista de clientes conectados.
- END: Desconecta y sale de la sesión.
- TEXT: Envía un mensaje a todos los usuarios conectados.
- TEXT TO usuario: Envía un mensaje privado a un usuario.
En C++
Servidor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | #include <stdio.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #define SERVER_PORT 6543 #define SERVER_ADDRESS "127.0.0.1" #define MAXLINE 512 #define MAXCLIENTS 10 int buscarCliente(char*); void subCadena(char*, char*, int, int); struct vector { int socket; char usuario[MAXLINE-4]; int sign_in; }; struct vector vectorClientes[MAXCLIENTS]; int clientes = 0; int main(int argc, char *argv[]) { void* gestionaCliente(void* p); int socketfd, new_sd; socklen_t client_len; struct sockaddr_in server_addr, client_addr; int i, status, id; pthread_t hilos[MAXCLIENTS]; for(i=0;i<MAXCLIENTS;i++) strcpy(vectorClientes[i].usuario, " "); // Open TCP internet STREAM socket if ((socketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("server: Can't open stream socket"); // Bind local address to allow the client to connect bzero((char *) &server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SERVER_PORT); if (bind (socketfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) perror("server: can't bind local address"); listen(socketfd, 5); for (;;) { client_len = sizeof(client_addr); if((new_sd = accept(socketfd, (struct sockaddr *) &client_addr, &client_len)) < 0) { printf("Error aceptando peticiones\n"); exit(0); } else { id = clientes; vectorClientes[id].socket = new_sd; vectorClientes[id].sign_in = 0; fflush(stdout); clientes++; if ( (status = pthread_create(&hilos[id],NULL,gestionaCliente,(void *)&id)) ) { printf("Error al crear el hilo\n"); exit(0); } } } close(socketfd); // Close original socket return 0; } void subCadena(char *subCad, char *cad, int inicio, int cuantos) { int i,j=0; for(i=inicio;i<inicio+cuantos && cad[i]!='\0';i++) { subCad[j]=cad[i]; j++; } subCad[j]='\0'; } int buscarCliente(char* usuario) { int i; for(i=0;i<clientes;i++) { if(strcmp(vectorClientes[i].usuario,usuario ) == 0 && vectorClientes[i].sign_in == 1) return vectorClientes[i].socket; } return -1; } void* gestionaCliente(void* p) { int *ide, id; ide = (int* ) p; id = *ide; char buffer[MAXLINE], nombre[MAXLINE-4], temp[MAXLINE-8]; int i, longitud, destino; while(1) { recv(vectorClientes[id].socket,buffer,MAXLINE,0); printf("\nid%d\n", id); if(strstr(buffer, "ADD") && vectorClientes[id].sign_in == 0) { longitud = strlen(buffer); //Le quitamos el ADD subCadena(nombre, buffer, 4, longitud-4); strcpy(vectorClientes[id].usuario, nombre); //Se informa a todos menos a él mismo y al que se haya ido strcpy(buffer, "El usuario "); strcat(buffer, nombre); strcat(buffer, " ha entrado en el chat."); for(i = 0; i < clientes; i++) if (i != id && vectorClientes[i].sign_in == 1) send(vectorClientes[i].socket,buffer,MAXLINE,0); vectorClientes[id].sign_in = 1; } if(strstr(buffer, "LIST") && vectorClientes[id].sign_in == 1) { //Se envia al cliente todos los usuarios menos los que hayan abandonado la sesión for(i = 0; i < clientes; i++){ if(vectorClientes[i].sign_in == 1) send(vectorClientes[id].socket, vectorClientes[i].usuario, MAXLINE, 0); } } if(strstr(buffer, "END") && vectorClientes[id].sign_in == 1) { //Se informa a todos menos a él mismo y al que se haya ido strcpy(buffer, "El usuario "); strcat(buffer, vectorClientes[id].usuario); strcat(buffer, " ha abandonado en el chat."); bzero(vectorClientes[id].usuario, MAXLINE); for(i = 0; i < clientes; i++) if (i != id && vectorClientes[i].sign_in == 1) send(vectorClientes[i].socket,buffer,MAXLINE,0); vectorClientes[id].sign_in = 0; } if(strstr(buffer, "TEXT") && !strstr(buffer, "TEXT TO") && vectorClientes[id].sign_in == 1) { longitud = strlen(buffer); subCadena(temp, buffer, 5, longitud-5); //Se envía a todos menos a él mismo y al que se haya ido bzero(buffer, MAXLINE); strcat(buffer, vectorClientes[id].usuario); strcat(buffer, " dice: "); strcat(buffer, temp); for(i = 0; i < clientes; i++) if (i != id && vectorClientes[i].sign_in == 1) send(vectorClientes[i].socket,buffer,MAXLINE,0); } if(strstr(buffer, "TEXT TO") && vectorClientes[id].sign_in == 1) { //Le quitamos el TEXT TO subCadena(nombre, buffer, 8, MAXLINE-8); //Nos quedamos sólo con el nombre, quitando desde el primer espacio en blanco hasta el final strtok(nombre," "); //Se obtiene el socket destino destino = buscarCliente(nombre); longitud=strlen(nombre); strcpy(nombre, vectorClientes[id].usuario); strcat(nombre, " dice: "); //Recortamos el TEXT TO, el nombre, y los dos espacios hasta el mensaje(se suma solo uno (un espacio) //porque empieza a recorrer longitud(instruccion)+longitud(nombre) desde la pos. 0 de la cadena. subCadena(temp, buffer, 8+longitud+1, MAXLINE-(8+longitud+1)); strcat(nombre, temp); if(destino != -1) send(destino, nombre, MAXLINE, 0); } fflush(stdout); } close(vectorClientes[id].socket); } |
Cliente
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <pthread.h> #include <string.h> #define SERVER_PORT 6543 #define SERVER_ADDRESS "127.0.0.1" #define MAXLINE 512 void* recibir(void* p); void* enviar(void* p); int main() { struct sockaddr_in addr; int sd,status; pthread_t hilos[2]; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS); addr.sin_port = htons(SERVER_PORT); if((sd = socket (AF_INET, SOCK_STREAM, 0)) == -1) { printf("Error al crear el socket\n"); exit(0); } if(connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { printf("Error al conectar\n"); exit(0); } else { if ( (status = pthread_create(&hilos[0],NULL,recibir,(void*)&sd)) ) { printf("Error al crear hilo para recibir\n"); close(sd); exit(0); } if ( (status = pthread_create(&hilos[1],NULL,enviar,(void*)&sd)) ) { printf("Error al crear hilo para enviar\n"); close(sd); exit(0); } pthread_join(hilos[0],NULL); pthread_join(hilos[1],NULL); } return 1; } void* recibir(void* p) { int* id; char buffer[MAXLINE]; id = (int*) p; while(1) { recv(*id,buffer,MAXLINE,0); printf("%s\n",buffer); fflush(stdout); } } void* enviar(void* p) { int* id; char buffer[MAXLINE]; id = (int*) p; while(1) { printf("\tIntroduce el mensaje\n-> "); fgets(buffer , MAXLINE , stdin); strtok(buffer,"\n"); send(*id,buffer,MAXLINE,0); sleep(3); } } |
En Python
Servidor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #!/usr/bin/python # -*- coding: utf-8 -*- #Servidor import string import threading import socket clientes = {'nombre':[], 'socket':[]} class gestionaClientes(threading.Thread): def __init__(self, socket): threading.Thread.__init__(self) self.conn = socket self.conectado = False self.data = '' def run(self): while True: self.data = self.conn.recv( 1024 ) if 'ADD' in self.data: #Busca si ya ha sido insertado if(self.conectado == False): self.conectado = True clientes['nombre'].append(self.data[4:]) clientes['socket'].append(self.conn) for i in clientes['socket']: if i != self.conn: i.send(self.data[4:]+" ha entrado en el chat.") else: self.conn.send("Ya estás en el chat.") print clientes if ('LIST' in self.data) and (self.conectado == True): for i in clientes['nombre']: print "enviado a "+str(self.conn)+" "+str(i) self.conn.send(i+" ") print clientes if ('END' in self.data): if (self.conectado == True): for i in clientes['socket']: if i == self.conn: nombre = clientes['nombre'][clientes['socket'].index(i)] clientes['nombre'].remove(nombre) clientes['socket'].remove(i) self.conectado = False for i in clientes['socket']: if i != self.conn: i.send(nombre+" ha salido del chat.") self.conn.send(" ") else: self.conn.send(" ") if ('TEXT' in self.data and 'TEXT TO' not in self.data) and (self.conectado == True): for i in clientes['socket']: if i != self.conn: i.send(clientes['nombre'][clientes['socket'].index(self.conn)]+" dice: "+self.data[5:]) print clientes if ('TEXT TO' in self.data) and (self.conectado == True): palabras = self.data[8:].split() #busca si existe alguien con ese alias for i in clientes['nombre']: if i == palabras[0]: #Prepara el mensaje del palabras[0] mensaje = string.join(palabras, ' ') clientes['socket'][clientes['nombre'].index(i)].send(clientes['nombre'][clientes['socket'].index(self.conn)]+" dice: "+mensaje) self.conn.close() #creamos socket pasivo y escuchamos en el puerto 9000 s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) s.bind( ( socket.gethostname(), 9000 ) ) s.listen( 5 ) while(True): conn, addr = s.accept() gestionaClientes(conn).start() |
Cliente
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | #!/usr/bin/python # -*- coding: utf-8 -*- #Cliente from Tkinter import * import threading import socket class App: def __init__(self, master, socket): self.conn = socket self.frame = Frame(master) self.label = Label(self.frame, text="Cliente chat. Python+TkInter. David López") self.textarea = Text(self.frame, height=20, width=40) self.scroll = Scrollbar(self.frame, command=self.textarea.yview) self.textarea.configure(yscrollcommand=self.scroll.set) self.texto_enviar = StringVar() self.text_ent = Entry(self.frame, textvariable=self.texto_enviar) self.btn_enviar = Button(self.frame, text="Enviar", command=self.enviar) self.btn_salir = Button(self.frame, text="Salir", command=self.salir) self.frame.grid() self.label.grid(row=0, column=0, columnspan=3) self.textarea.grid(row=1, column=0, columnspan=2) self.scroll.grid(row=1, column=2, sticky=N+S) self.text_ent.grid(row=2, column=0, columnspan=2, sticky=W+E) self.btn_enviar.grid(row=3, column=0) self.btn_salir.grid(row=3, column=1) def enviar(self): self.conn.send(self.text_ent.get()) self.text_ent.delete(0, END) # Esta es una función de retrollamada. def salir(self): lee.parar() self.conn.send("END") self.frame.quit() def escribir(self, texto): self.textarea.insert(END, texto) class leer(threading.Thread): def __init__(self, socket): threading.Thread.__init__(self) self.mensaje = '' self.conn = socket self.stop = False def run(self): while (self.stop == False): self.mensaje = self.conn.recv( 1024 ) app.escribir(self.mensaje+'\n') self.conn.close() def parar(self): self.stop = True miSocket = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) miSocket.connect( (socket.gethostname(), 9000 ) ) lee = leer(miSocket) lee.start() root = Tk() root.title('Chat Python-TkInter - David López') app = App(root, miSocket) root.mainloop() |