FACULTAD DE INGENIERÍA UNIVERSIDAD DE BUENOS AIRES 66.48 – Seminario de Redes de Computadora Trabajo Práctico N°4 Sockets Integrantes: - Santiago Boeri (79529) - Hernán Castagnola (79555) - Christian Picot (80297) - Tomás Shulman (84050) Profesores: - Marcelo Utard - Pablo Ronco Introducción ______________________________________________________________ 2 Orígenes _________________________________________________________________ 3 ¿Que es un socket?_________________________________________________________ 3 Dominios de comunicación __________________________________________________ 3 Tipos de Sockets ___________________________________________________________ 4 Llamadas al sistema (System Calls) ___________________________________________ 5 Comunicación Mediante Sockets _____________________________________________ 5 Windows Socket ___________________________________________________________ 7 Funciones Winsock ________________________________________________________ 8 Codigos __________________________________________________________________ 8 Servidor UDP __________________________________________________________ 8 Servidor TCP (concurrente) _____________________________________________ 11 Cliente TCP ___________________________________________________________ 16 Cliente UDP ___________________________________________________________ 19 Capturas ________________________________________________________________ 22 Introducción En una red de computadoras existen procesos que necesitan entenderse e intercambiar entre sí información. Es claro que deben estar de acuerdo en cómo iniciar, transmitir y terminar una comunicación. Para que dos programas que están siendo ejecutados puedan comunicarse y pasarse datos es necesario que se cumplan ciertos requisitos: • Que ambos procesos sea capaces de localizar físicamente a su End System destino. • A su vez que los End Systems sean capaces de multiplexar a nivel aplicativo la información que reciben y logren de esta manera que ambos procesos puedan recibir cualquier secuencia de octetos. Como es sabido los sistemas de comunicación no multiplexan procesos, sólo tráfico. Visto de este modo el programador debería lidiar con cuestiones tales como: • Manejar un protocolo de comunicaciones, que permita dicho intercambio de octetos. • Determinar la dirección del Protocolo de Red (Dirección IP, si se utiliza el Protocolo TCP/IP) que identifique a cada host. • Ligar un puerto del End System local con un puerto del End System destino para así poder identificar el proceso “remitente” con el remoto e intercambiar información. Para que los programadores puedan abocarse de lleno a la implementación de aplicaciones y dejar en manos de los sistemas operativos (S.O.) cuestiones referidas a las capas inferiores del modelo OSI los lenguajes de programación de hoy en día proveen una interfaz sencilla para hacer frente a estas requerimientos. Es así como los programadores hacen uso de la API (Application Programming Interface) de sockets. Existen una infinidad de APIs. Las APIs en sí son un conjunto de llamadas a bibliotecas que ofrecen acceso a ciertos servicios desde los procesos y representan un método para conseguir abstracción en la programación, entre los niveles inferiores y superiores del software. Los sockets son un nivel de abstracción que presenta el sistema operativo para realizar comunicaciones entre procesos en forma transparente mediante un conjunto de llamadas al sistema (system calls). Orígenes En los orígenes de Internet, las primeras computadoras en implementar sus protocolos fueron aquellas de la universidad de Berkeley. Dicha implementación tuvo lugar en una variante del sistema operativo Unix conocida como BSD Unix. Pronto se hizo evidente que los programadores necesitarían un medio sencillo y eficaz para escribir programas capaces de intercomunicarse entre sí. Esta necesidad dio origen a la primera especificación e implementación de sockets, también en Unix. Hoy día, los sockets están implementados como bibliotecas de programación para multitud de sistemas operativos, simplificando la tarea de los programadores. ¿Que es un socket? Socket designa un concepto abstracto por el cual dos procesos (independiente de donde estén siendo corridos) pueden intercambiarse cualquier flujo de datos. Un socket queda definido por una dirección IP, un protocolo y un número de puerto. Los sockets permiten implementar un Inter Process Comunication (IPC) clienteservidor1. La comunicación ha de ser iniciada por uno de los programas en ejecución denominado programa cliente mientras que el otro que es el que espera el comienzo de dicha comunicación se denomina programa servidor. La forma de referenciar un socket por los procesos implicados es mediante un descriptor (o handler) como el utilizado para referenciar archivos. Dominios de comunicación El dominio del socket especifica el conjunto de sockets que pueden establecer una comunicación con el mismo. Además, indica el formato de las direcciones que podrán tomar los sockets y los protocolos que soportarán dichos sockets. Si los procesos están en el mismo sistema, el dominio de comunicación será AF_UNIX. La estructura de una dirección en este dominio es: struct sockaddr__un{ short sun__family; /* en este caso AF_UNIX */ char sun__data[108]; /* dirección */ }; 1 Con esto no queremos decir que los Sockets sólo sirvan para dicha arquitectura. De hecho se pueden implementar arquitecturas P2P haciendo que ambos E.S. sean en un momento dado Clientes y en otro Servidores. Si los procesos están en distintos sistemas y estos se hallan unidos mediante una red TCP/IP, el dominio de comunicación será AF_INET. La estructura de una dirección en este dominio es: struct in__addr { u__long s__addr; }; struct sockaddr__in { short sin_family; /* en este caso AF_INET */ u__short sin_port; /* numero del puerto */ struct in__addr sin__addr; /* direcc Internet */ char sin_zero[8]; /* campo de 8 ceros */ } Tipos de Sockets Definen las propiedades de las comunicaciones en las que se vaya a ver envuelto un socket, esto es, el tipo de comunicación que se puede dar entre un cliente y su servidor. Tales características pueden (o no) ser: - Fiabilidad de transmisión (sockets TCP) - Mantenimiento del orden de los datos (sockets TCP). - No duplicación de los datos (sockets TCP) - El "Modo Conectado" en la comunicación chequeando la no duplicación de segmentos, realizando un control de flujos, secuencias y congestión. - Envío de mensajes urgentes. - El "Modo no Conectado" en la comunicación o la mera transmisión de datagramas sin las responsabilidades de hacer control de flujos, ni de secuencias, ni de congestión. Hay varios tipos de sockets, están las direcciones de Internet DARPA (sockets de Internet), nombres de ruta en un nodo local (sockets de Unix), direcciones CCITT X.25 (sockets X.25). Este trabajo está basado en el primero: Sockets de Internet pertenecientes al dominio AF_INET. Los tipos disponibles son los siguientes: * Tipo SOCK_DGRAM: sockets para comunicaciones en modo no conectado, orientado a mensaje con envío de datagramas de tamaño limitado por el Payload de la capa IP (hasta 64KB). En dominios Internet el protocolo del nivel de transporte sobre el que se basa es el UDP. * Tipo SOCK_STREAM: para comunicaciones fiables en modo conectado, orientado a bytestream, full duplex y con tamaño variable de los mensajes de datos (secuenciando no mensajes sino bytes). En dominios Internet subyace el protocolo TCP. * Tipo SOCK_RAW: permite el acceso a protocolos de más bajo nivel como el IP ( nivel de red ) * Tipo SOCK_SEQPACKET: tiene las características del SOCK_STREAM pero además el tamaño de los mensajes es fijo. Llamadas al sistema (System Calls) Mediante un conjunto de llamadas al sistema, el S.O. ofrece un grupo de servicios que facilitan al programador la tarea de establecer una comunicación a través de la red, utilizando en forma transparente los protocolos de capas inferiores (IP, TCP, UDP, etc). Las syscalls que se utilizan para establecer la comunicación serán diferentes para el cliente y para el servidor, pero ambos casos requerirán la creación de uno o más sockets. Un socket representa uno de los dos extremos de un canal de comunicación entre procesos. Cada uno de los procesos establece su propio socket para entablar la comunicación. En realidad, las llamadas al sistema son casi el único punto de entrada que tienen los procesos de espacio de usuario al kernel. Es el principal mecanismo de comunicación de las aplicaciones con el kernel. Las llamadas al sistema nos permiten el establecimiento de un socket en cada uno de los procesos y la comunicación transparente entre ellos a través de la red que comparten. Comunicación Mediante Sockets Vamos a explicar el proceso de comunicación servidor-cliente con conexión, modo utilizado por las aplicaciones tales como telnet o ftp. El servidor es el proceso que crea el socket, sólo asociandole su/s IP/s. Se queda escuchando y acepta las conexiones entrantes. El orden de las llamadas al sistema para la realización de esta función es: 1º) int socket ( int dominio, int tipo, int protocolo ) crea un socket sin nombre de un dominio, tipo y protocolo específico dominio : AF_INET, AF_UNIX tipo : SOCK__DGRAM, SOCK__STREAM protocolo : 0 ( protocolo por defecto ) 2º) int bind ( int dfServer, struct sockaddr* direccServer, int longDirecc ) nombra un socket: asocia el socket no nombrado de descriptor dfServer con la dirección del socket almacenado en direccServer. La dirección depende de si estamos en un dominio AF_UNIX o AF_INET. 3º) int listen ( int dfServer, int longCola ) especifica el máximo número de peticiones de conexión pendientes. 4º) int accept ( int dfServer, struct sockaddr* direccCliente, int* longDireccCli) escucha al socket nombrado “servidor dfServer” y espera hasta que se reciba la petición de la conexión de un cliente. Al ocurrir esta incidencia, crea un socket sin nombre con las mismas características que el socket servidor original, lo conecta al socket cliente y devuelve un descriptor de fichero que puede ser utilizado para la comunicación con el cliente 5º) OPCIONAL: una vez que acepta una conexion entrante puede abrir un hilo para atender al nuevo cliente mientras se queda ciclando para atender nuevos clientes y así abrir cuantos hilos requiera por cada cliente nuevo2. También se puede resolver la comunicación de modo “secuencial”: aceptando un único cliente por vez (no es lo ideal) 2 Este es el caso del programa servidor TCP implementado en este trabajo. El cliente es encargado de crear un socket con la dirección IP de su servidor y posteriormente enlazarlo con el socket servidor nombrado. O sea, es el proceso que demanda una conexión al servidor. La secuencia de llamadas al sistema es: 1º) int socket ( int dominio, int tipo, int protocolo ) crea un socket sin nombre de un dominio, tipo y protocolo específico dominio : AF_INET, AF_UNIX tipo : SOCK__DGRAM, SOCK__STREAM protocolo : 0 ( protocolo por defecto ) 2º) int connect ( int dfCliente, struct sockaddr* direccServer, int longDirecc ) intenta conectar con un socket servidor cuya dirección se encuentra incluida en la estructura apuntada por direccServer. El descriptor dfCliente se utilizará para comunicar con el socket servidor. El tipo de estructura dependerá del dominio en que nos encontremos 3º) Puede abrir un hilo para recibir datos y usar el hilo original para mandarlos o trabajar en forma alternada bloqueandose cada vez que espera recibir algo del servidor o enviarle. Hay prestar atención al tipo de transporte que se usa. En este caso al ser TCP pueden llegar datos en cualquier momento por lo que es conveniente ciclar el modulo de recepción para extraer toda la información que provenga del servidor. send() y recv() Estas dos funciones sirven para comunicarse a través de sockets de flujo o sockets de datagramas conectados. • La llamada al sistema send() es: int send(int sockfd, const void *msg, int len, int flags); · sockfd es el descriptor de socket al que se quiere enviar datos (puede ser el devuelto por socket(), o bien el devuelto por accept().) · msg es un puntero a los datos a enviar · len es la longitud de esos datos en bytes · flags lleva normalmente el valor 0, y sirve para establecer opciones. La llamada send() devuelve el número de bytes que se enviaron en realidad, lo que sirve para detectar errores. • La llamada al sistema recv() es similar en muchos aspectos: int recv(int sockfd, void *buf, int len, unsigned int flags); · sockfd es el descriptor del fichero del que se va a leer · buff es el buffer donde se va a depositar la información leída · len es la longitud máxima del buffer · flags nuevamente son indicaciones Como antes, recv() devuelve el número de bytes que se leyeron en realidad, o -1 en caso de error. Hay que tener en cuenta que esta función es bloqueante y hasta que no recibe datos Para el caso de que la comunicación no sea orientada a la conexión los pasos para inicializar ésta son los mismos que las comunicaciones con conexión salvo que los syscalls listen( ) y accept( ) del lado del servidor y connect( ) del lado del cliente no se utilizan. sendto() y recvfrom() Estas funciones sirven para el envío y recepción de datagramas sobre sockets UDP. Puesto que los sockets de datagramas no están conectados a una máquina remota, solo se necesita pasarle a las llamadas la dirección de destino: int sendto(int sockfd, const void *msg, int len, unsigned int flags,const struct sockaddr *to, int tolen); Como se puede ver, esta llamada es básicamente la misma que send()añadiendo dos ítems más de información: · to es un puntero a una estructura struct sockaddr, que contiene la dirección IP y el puerto de destino · tolen es la longitud de to, se le asigna el valor sizeof(struct sockaddr). Lo mismo que send(), sendto() devuelve el número de bytes que realmente se enviaron (que, igual que antes, podrían ser menos de los que se enviaron) La misma semejanza presentan recv() y recvfrom(). La sinopsis de recvfrom() es: int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen); Los campos from y fromlen son similares a to y tolen de sendto. close() y shutdown() Se utilizan para cerrar la conexión del descriptor de socket. Sólo hay que usar la función close() que cierra descriptores de fichero: close(sockfd); Esto impedirá más lecturas y escrituras al socket. Cualquiera que intente leer o escribir sobre el socket desde el extremo remoto recibirá un error. Si se desea un poco más de control sobre cómo se cierra el socket se puede usar la función shutdown() que permite cortar la comunicación en un sentido, o en los dos (como lo hace close()). int shutdown(int sockfd, int how); sockfd es el descriptor de socket que se desea desconectar, y how puede tener uno de los siguientes valores: 0 -- No se permite recibir más datos 1 -- No se permite enviar más datos 2 -- No se permite enviar ni recibir más datos (similar a close()) shutdown() devuelve 0 si tiene éxito, y -1 en caso de error Si se usa shutdown() en un datagram socket, simplemente inhabilitará el socket para posteriores llamadas a send() y recv() shutdown() no cierra realmente el descriptor de fichero, sólo cambia sus condiciones de uso. Para liberar un descriptor de socket es necesario usar close(). Windows Socket Como cada sistema operativo tiene su interfaz socket, en Microsoft Windows, la interfaz socket se llama WinSock3, o Windows Socket API. 3 WinSock es una interfase y no un protocolo. Si el cliente y el server usan el mismo protocolo (TCP/IP), se pueden comunicar inclusive si usan diferentes APIs Si el cliente y el server no usan el mismo protocolo, no se pueden comunicar inclusive si usan la misma API: Funciones Winsock Antes de usar WinSock, una aplicación llama a WSAStartup(). WSAStartup() requiere dos argumentos: El 1er argumento específica la versión del WinSock pedido. El 2do argumento devuelve información acerca de la versión actualmente usada de WinSock. Cuando una aplicación termina de usar un socket, debe llamar a WSACleanup() para desasignar toda la estructura de datos y los socket bindings. Codigos La práctica de UDP consiste de un servidor que recibe noticias desde la aplicación cliente que usan los redactores. La de TCP, en tanto, es un servidor concurrente que recibe a los lectores (cliente TCP) y les transmite las noticias Servidor UDP /* ** ** ** ** ** ** ** Servidor.c Recibe las noticias desde el cliente UDP y las guarda en un archivo noticias.txt para su posterior lectura por parte del servidor TCP. Admite que el cliente (redactor) adhiera noticias de ultimo momento a las ya existentes: anteponiendo un "+" al principio de la noticia; o tambien permite borrar el archivo y crearlo nuevamente con otras noticias anteponiendo en "*". ** ** ** ** ** ** ** ** ** ** */ Por default si no se antepone ni el "*" o el "+" se sobreentinde como anexación. Como el servidor TCP va a separar unas noticias de otras se recomienda que el redactor (cliente UDP) separe las noticas por medio de un "|". Se debe compilar con archivos objetos de la libreria de sockets de windows: \Lib\libwsock32.a \Lib\libws2_32.a Para compilar hay que ir a Project --> Project options --> Load Obj files --> wsock*.a Uso: 1) Poseer un archivo "noticias.txt" de donde se leeran las noticias 2) Correr el servidor que atiende al redactor de la siguiente manera: servidor.exe <Puerto UDP libre> #include <stdio.h> #include <winsock.h> //Permite el uso de SOCKETs #include <stdlib.h> #include <string.h> #define BUF_LENGTH 1000 #define PRINTERROR(s) \ fprintf(stderr,"\n%: %d\n", s, WSAGetLastError()) void DatagramServer(short nPort); /* ** Funcion de main: ** Comprueba que la cantidad de argumentos sea valida ** Inicializa el uso de Sockets ** Llama a la funcion que se encarga de recibir los datagramas ** ** Argumentos entrada: puerto por donde va a escuchar **/ void main(int argc, char **argv) { WORD wVersionRequested = MAKEWORD(1,1); // Se declara la version de sockets //requerida WSADATA wsaData; // Puntero a info acerca de la //implementacion de Sockets int nRet; short nPort; // Para que el valor char de port sea int if (argc != 2) { fprintf(stderr,"\nSyntax: dserver PortNumber\n"); return 1; } nPort = atoi(argv[1]); nRet = WSAStartup(wVersionRequested, &wsaData); // Inicializa el uso de sockets. Retorna //0 si OK if (wsaData.wVersion != wVersionRequested) { fprintf(stderr,"\n Wrong version\n"); return 1; } DatagramServer(nPort); WSACleanup(); // Se libera el uso de sockets return 0; } /* ** Funcion de DatagramServer: ** a) Inicializar el Socket de recepcion: asociarlo a todas ** las IP que hayan en el host, al port y ligarlo (bind) ** b)Ciclar eternamente esperando a que un cliente le hable ** al port. Una vez que recibe un datagrama por ese port determina ** si anexarla la noticia a las existentes o si crea el archivo ** de nuevo. ** c) Guardar en el archivo y cerrarlo ** d) Esperar nuevas comunicaciones al port de escucha. ** ** Argumentos: Port UDP libre por donde escuchara las noticias */ void DatagramServer(short nPort) { SOCKET theSocket; SOCKADDR_IN saClient, saServer; //saServer: para inicializar el servidor con las address //saClient: para recibir la direccion del cliente int nRet, nLen, desfase; //"desfase" se usa por si el cliente UDP no antepone el + //para anexar una noticia FILE *archivo; char modo[] = "wb"; char szBuf[BUF_LENGTH]; // Se usa de handler para manejar el archivo noticias.txt // Elegir el modo en que se crea el archivo //Buffer pra recibir las noticias //Familia de address, tipo de Socket (UDP), Protocolo theSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (theSocket == INVALID_SOCKET) { PRINTERROR("socket()"); return; } //Arma la estructura de Address saServer.sin_family = AF_INET; saServer.sin_addr.s_addr = INADDR_ANY; saServer.sin_port = htons(nPort); // Le pide que escuche por cualquiera de las IP //que dispone // Asocia el puerto por el cual va a escuchar // Une el socket a una IP local y un puerto asignados a la estructura de address nRet = bind(theSocket, (LPSOCKADDR)&saServer, sizeof(struct sockaddr)); if (nRet == SOCKET_ERROR) { PRINTERROR("bind()"); closesocket(theSocket); return; } nLen = sizeof(SOCKADDR); nRet = gethostname(szBuf, sizeof(szBuf)); if (nRet == SOCKET_ERROR) { PRINTERROR("gethostname()"); closesocket(theSocket); return; } printf("\nServidor %s preparado por puerto %d\n", szBuf, nPort); //El servidor queda ciclando "eternamente" para recibir todas las noticias necesarias while(1) { memset(szBuf, 0, sizeof(szBuf)); // Limpia el buffer nRet = recvfrom(theSocket, szBuf, sizeof(szBuf), 0,(LPSOCKADDR)&saClient, &nLen); //Socket ligado, Buffer de recepcion, cantidad de bytes q se pueden //almacenar en el buffer, Flags, direccion de la estructura de address //para cliete, tamaño de dicha estructura printf("\n%s", szBuf); desfase = 0; //Imprime en pantalla lo recibido // Por default si no manda ni el * ni el + al ppio //entonces el primer caracter debe ser grabado porque //es parte de una palabra. switch(szBuf[0]) { case '*': //Elegir el modo de escritura case '+': default: strcpy(modo, "wb"); //Nuevo archivo de noticias desfase = 1; break; desfase = 1; //Sigue el mismo archivo de noticias strcpy(modo, "ab"); } if((archivo = fopen("noticias.txt", modo)) == NULL) { fprintf(stderr,"\nError al abrir el archivo\n"); } else { szBuf[strlen(szBuf)] = '|'; // El servidor TCP que mande las noticias al lector //detecta el fin de una noticia y el comienzo de otra //por medio del "|" fputs(&szBuf[desfase], archivo); //Guarda en archivo fclose(archivo); //Cierra el archivo } } closesocket(theSocket); return; //Nunca pasa por aca } crear Socket asociar IP locales + port de escucha al Socket 1 recibir datagrama desde cualquier IP (se bloquea a la espera) guardar en archivo Servidor TCP (concurrente) // // // // // // // // // // // // Server.c Envia las noticias disponibles en un archivo de texto llamado noticias.txt Admite que el cliente visualize de a una las distintas noticias o termine la conexion. Mediante tecnicas de multiprogramación este programa de servidor permite múltiples conexiones simultáneas. Se debe compilar con archivos objetos de la libreria de sockets de windows: \Lib\libwsock32.a \Lib\libws2_32.a Uso: 1) Poseer un archivo "noticias.txt" de donde se leeran las noticias 2) Correr el servidor que atiende lectores concurrentes de la siguiente manera: servidor.exe <Puerto TPC libre> #include #include #include #include #include <stdio.h> <winsock.h> <ctype.h> <stdlib.h> <process.h> //Permite el uso de SOCKETs //Permite la concurrencia // Prototipeo de funciones void StreamServer(short nPort); void copiar(char nombre_origen[], char nombre_destino[]); void atender_cliente(SOCKET Socket_entrante); // Macro para mostrar errores #define PRINTERROR(s) \ fprintf(stderr,"\n%: %d\n", s, WSAGetLastError()) /* ** Funcion del main: ** a) Inicializar las librerías de API Socket ** b) Corroborar la cantidad de argumentos ** c) Llamar a la funcion encargada de controlar servidor TCP ** d) Cerrar libreria de API Socket ** ** Argumentos de entrada: puerto TCP de servidor */ int main(int argc, char **argv) { WORD wVersionRequested = MAKEWORD(1,1); // Obtiene la version que necesita WSADATA wsaData; //winsock para funcionar short nPort; // Valida que se haya ingresado la cantidad de parámetros necesarios if (argc != 2) { fprintf(stderr,"\nSintaxis: %s Puerto\n",argv[0]); return 0; } // Inicializa winsock y valida la version WSAStartup(wVersionRequested, &wsaData); if (wsaData.wVersion != wVersionRequested) { fprintf(stderr,"\n Version incorrecta\n"); return 0; } copiar("noticias.txt", "noticias.tmp"); nPort = atoi(argv[1]); StreamServer(nPort); // Llama a la funcion principal, que crea el servidor WSACleanup(); // Como ya no utiliza mas el socket lo desasocia y libera //los recursos solo cuando hay un error al momento de hacer //la configuracion pq sino queda ciclando } /* ** Se encarga de enviarle una bienvenida al cliente ofreciendole las opciones de continuar ** o terminar. Cuando llega al EOF del archivo de noticias o cuando el cliente decide ** terminar la lectura. La funcion SÍ chequea en cuanto a si se intenta ** llenar al buffer mas alla de sus limites con alguna noticia muy larga. ** ** Argumento de entrada: SOCKET que atendio la conexion */ void atender_cliente (SOCKET remoteSocket) { char opcion=13; // Para el ciclo de lectura. El cliente debe mandar un /n //o un /r (int:10 o int:13) char szBuf[256]; //Buffer para recibir y enviar. int nRet; FILE *fp_noticias_copia; fp_noticias_copia=fopen("noticias.tmp","rb"); int c; int n; //Abre la copia de noticias.txt //Para leer el archivo y copiarlo a szBuf //Contar la cantidad de caracteres a enviar if (remoteSocket == INVALID_SOCKET) { PRINTERROR("accept()"); closesocket(remoteSocket); return; } memset(szBuf, 0, sizeof(szBuf)); //limpia szbuf sprintf(szBuf, "Presione ENTER para recibir las noticias O cualquier otra tecla para salir\n\r"); nRet = send(remoteSocket, // Socket conectado szBuf, // Datos enviados strlen(szBuf), // Longitud de los datos enviados 0); // Flags printf("\nCliente leyendo..."); memset(szBuf, 0, sizeof(szBuf)); //limpia szbuf nRet = recv(remoteSocket, szBuf, // Socket conectado // Datos recibidos sizeof(szBuf), // Tamaño del buffer 0); // Flags if (nRet == INVALID_SOCKET) { PRINTERROR("recv()"); closesocket(remoteSocket); return; } opcion=szBuf[0]; // Por cada ciclo transmite una noticia while(opcion==13 || opcion==10) { n=0; // Almacena en szBuf la noticia levantada del archivo de texto //Si se pasa del buffer envia la noticia. Primero hay que chequear la ocupacion //del buffer y despues tomar la siguiente letra si entra en el buffer while ((n+1<sizeof(szBuf))&&((c=getc(fp_noticias_copia))!=EOF)&&(c!='|')) { szBuf[n]=c; n++; } // Si se llego al EOF, el servidor cierra la conexion con el cliente if (c == EOF) { //A la ultima noticia se le anexa advertencia de que ha terminado sprintf(&szBuf[n],"\n\rEsas fueron todas las noticias del dia\n\r"); nRet = send(remoteSocket, // Socket conectado szBuf, // Datos enviados strlen(szBuf), // Longitud de los datos enviados 0); // Flags opcion = 0; // Salir } else { // Envia la noticia al cliente (aun no se llego al final del archivo) nRet = send(remoteSocket, // Socket conectado szBuf, // Datos enviados strlen(szBuf), // Longitud de los datos enviados 0); // Flags memset(szBuf, 0, sizeof(szBuf)); //limpia szbuf // Recibe respuesta del cliente. //de seguir leyendo. No entra en //buffer y tuvo que mandar if(c == '|') { nRet = recv(remoteSocket, szBuf, sizeof(szBuf), 0); Espera hasta que llegue el comando este if en el caso en que se lleno el // // // // Socket conectado Datos recibidos Tamaño del buffer Flags opcion=szBuf[0]; if (nRet == INVALID_SOCKET) { PRINTERROR("recv()"); closesocket(remoteSocket); opcion = 0; } } } } // Cierra el archivo si esta abierto if(!fp_noticias_copia) { fclose(fp_noticias_copia); } // Cierra el socket que utilizo para aceptar la conexion con el cliente printf("\n\rEliminando socket de conexion...\r\n"); closesocket(remoteSocket); } /* ** ** Prepara un listenSocket a la espera de algun cliente. Cuando llega uno se lo acepta y se llama a atender_cliente() para que procese los requerimientos del cliente en forma ** paralela mientras espera nuevos clientes. ** ** Parametro de entrada: Puerto por donde escucha */ void StreamServer(short nPort) { SOCKET SOCKET listenSocket; remoteSocket; SOCKADDR_IN saServer; SOCKADDR_IN remote_addr; int long_address; char szBuf[256]; int nRet; // Prepara al servidor para escuchar por determinado IP:Port // Cuando llegan conexiones se aceptan en este Socket que //hereda la configuración del "listenSocket" //Usada para averiguar la IP remota //Usada como parametro en accept() //Para averiguar el nombre de Host local // Crea el stream TCP por el cual el servidor va a escuchar listenSocket = socket(AF_INET, // Familia de direcciones para TCP/IP SOCK_STREAM, // Tipo de servicio del socket (TCP) IPPROTO_TCP); // Socket que utiliza TCP if (listenSocket == INVALID_SOCKET) { PRINTERROR("Hubo un error al llamar a la funcion socket()"); return; } // Crea la estructura de direccion del servidor saServer.sin_family = AF_INET; saServer.sin_addr.s_addr = INADDR_ANY; // Deja que winsock asigne la direccion IP saServer.sin_port = htons(nPort); // Le asigna el puerto con el que se llamo a la //aplicacion // Asocia el socket a la direccion local creada en la estructura saServer printf("\nAsociando socket de Escucha al Servidor IP:Port..."); nRet = bind(listenSocket, // Socket (LPSOCKADDR)&saServer, // IP y puerto local sizeof(struct sockaddr)); // Tamaño de la estructura de direccion if (nRet == SOCKET_ERROR) { PRINTERROR("bind()"); closesocket(listenSocket); return; } nRet = gethostname(szBuf, sizeof(szBuf)); //Toma el nombre local de Host if (nRet == SOCKET_ERROR) { PRINTERROR("gethostname()"); closesocket(listenSocket); return; } printf("\nServidor %s escucha en el puerto %d\n", szBuf, nPort); nRet = listen(listenSocket, // Socket asociado SOMAXCONN); // Especifica el tamaño de la cola de espera para ese socket if (nRet == SOCKET_ERROR) { PRINTERROR("listen()"); closesocket(listenSocket); return; } // El servidor se bloquea hasta recibir solicitud de entrada. Cuando llega un cliente //se abre un hilo que atiende los requerimientos mientras que el proceso ppal siegue //ciclando para recibir nuevos clientes while(1) { printf("\nEsperando nuevo cliente..."); long_address = sizeof(remote_addr); // Se usa para pasar como parametro //el tamaño de la estructura de address remoteSocket = accept(listenSocket, (struct sockaddr*)&remote_addr, // Direccion de cliente (opcional:NULL) &long_address); // Tamaño direc. cliente (opcional: NULL) //Se podria haber usado getpeername( //remoteSocket, (struct sockaddr*) //&remote_addr, &addr_remoto); Para //averiguar la IP remota printf("\nNuevo cliente: %s\n", inet_ntoa(remote_addr.sin_addr)); //Transforma un long int en string con IP //Proceso paralelo atiende cliente _beginthread((void(*)(void*))atender_cliente, 0, (void*)remoteSocket); } } /* ** Funcion que copia el contenido de un archivo en otro para que solo se lea de la ** copia y el original quede libre para ser modificado ** ** Parámetros de entrada: string del nombre archivo original ** string del nombre archivo destino */ void copiar(char nombre_origen[], char nombre_destino[]) { int letra; FILE * origen; FILE * destino; origen = fopen(nombre_origen, "rb"); destino = fopen(nombre_destino, "w+b"); while((letra = getc(origen))!= EOF) { putc(letra, destino); } fclose(origen); fclose(destino); } abre copia de archivo crear Socket de escucha (listenSocket) SEND opciones asociar IP locales + port de escucha al Socket Enter: sigue leyendo otra tecla: sale RECV opcion LISTEN 1 ACCEPT en Socket de recepcion Envio Enter? RECV opcion NO arma string a enviar (se bloquea a la espera) crear thread SEND string NO EOF de archivo? manda string + aviso de fin de NEWS cierra archivo cierra SOCKET Cliente TCP // // // // // // // // // // // // // Cliente.c Simula un cliente telnet superbásico que manda tinygrams por cada letra tipeada. Es ideal para usar con el servidor TCP creado por nosotros porque este sólo espera recibir un caracter por vez. Sin embargo se ha probado contra servidores de SMTP y de HTTP y funciona correctamente. El éxito del cliente se debe a que separa la lectura de la escritura en 2 procesos complementarios pero paralelos. Se debe compilar con archivos objetos de la libreria de sockets de windows: \Lib\libwsock32.a \Lib\libws2_32.a #include <stdio.h> #include <winsock.h> #include <ctype.h> #include <stdlib.h> #include <process.h> #define BUFFER 1000 //Manejo de procesos paralelos: lectura y escritura // Prototipo de funciones void StreamClient(const char *host, const char *puerto); void Leer(SOCKET elSocket); int no_salir = 1; // Se usa esta variable global para indicar el //fin de la lectura y poder salir del programa // Macro para mostrar errores #define PRINTERROR(s) \ fprintf(stderr,"\n%: %d\n", s, WSAGetLastError()) /* ** Funcion del main: ** a) Inicializar las librerías de API Socket ** b) Corroborar la cantidad de argumentos ** c) Llamar a la funcion encargada de controlar cliente TCP ** d) Cerrar libreria de API Socket ** ** Argumentos de entrada: servidor (FQDNo IP) ** puerto TCP de servidor */ int main(int argc, char *argv[]) { WORD wVersionRequested = MAKEWORD(1,1); // Obtiene la version que necesita //winsock para funcionar WSADATA wsaData; // Estructura de informacion acerca de //la implementacion del API Socket int nRet; // Valida que se hayan ingresado la cantidad de argumentos necesarios if (argc != 3) { fprintf(stderr,"\nSintaxis: %s Servidor Puerto\n",argv[0]); return 0; } nRet = WSAStartup(wVersionRequested, &wsaData); // Inicializa winsock y valida la //version if (wsaData.wVersion != wVersionRequested) { fprintf(stderr,"\n Version incorrecta\n"); return 0; } StreamClient(argv[1], argv[2]);// Llama a la funcion principal, que crea el servidor WSACleanup(); // Como ya no utiliza mas el socket lo desasocia y //libera los recursos } /* ** Funcion de Leer: ** Una vez establecida la conexion con el servidor se encarga de hacer la ** recepcion de datos. En cuanto se termina la conexion setea la variable ** global no_salir de modo de avisarle al modulo de escritura dicha finali_ ** zación. ** Basicamente es el modulo de Recepcion o INPUT. ** Cicla constantemente esperando recibir segementos ya que se trata de un ** cliente TCP. ** ** Argumentos entrada: SOCKET de conexion ** variable global del tipo int para la comunicacion entre modulos I/O */ void Leer(SOCKET elSocket) { char szBuf[BUFFER] = {0}; int nRet = 1; // Mientras nRet sea distinto a 0 se queda //recibiendo bytes while(0<nRet && nRet<BUFFER) { fputs(szBuf, stdout);//Esta funcion se decidió ponerla antes y no al final del ciclo //pq el recv() puede devolver un valor erroneo cuando se decide //terminar la conexion. nRet = recv(elSocket, szBuf, sizeof(szBuf)-1, 0); szBuf[nRet] = '\0'; } no_salir = 0; // Asegura un null terminated string // Es el nexo entre la lectura y la escritura. Le avisa a esta //ultima el fin de la conexion } /* ** Funcion StreamClient: ** Prepara un Socket para entablar la conexion con un servidor TCP ** determinado en un port especificado. ** Hace el llamado al modulo de recepcion o INPUT que se procesa de ** manera paralela y ciclica ya que es de tipo TCP ** Hace el llamado al modulo de envio o OUTPUT que espera la señal ** de finalizacion seteada por INPUT para terminar el programa ** ** Parametros de entrada: servidor (FQDNo IP) ** Puerto por donde escucha */ void StreamClient(const char *host, const char *puerto) { SOCKET elSocket; struct hostent *phe; // Puntero a informacion de host SOCKADDR_IN saClient; // Crea la estructura de endpoint address del socket char szBuf[1] = {0}; // Buffer para la escritura. Como este cliente manda un byte //por vez pq asi lo necesita el servidor el tamaño buffer = 1 int nPort = atoi(puerto); // Crea el handler a socket a traves del cual se podra comunicar con el servidor elSocket = socket(PF_INET, // Familia de direcciones para TCP/IP SOCK_STREAM, // Tipo de servicio del socket (TCP) 0); if (elSocket == INVALID_SOCKET) { PRINTERROR("Hubo un error al llamar a la funcion socket()"); return; } saClient.sin_family = AF_INET; if(phe = gethostbyname(host)) //Devuelve una estructura del tipo hostent para el //anfitrión (host) dado name. Aquí, name es o un nombre //de anfitrión, o una dirección IPv4 en la notación //normal de puntos, o una dirección IPv6 en la notación //de dos puntos. //struct hostent {char *h_name; char **h_aliases; //int h_addrtype; int h_length;} memcpy(&saClient.sin_addr, phe->h_addr, phe->h_length); else if((saClient.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) //Convierte la dirección de Internet desde la notación //de números y puntos a la de datos binarios en orden de //bytes del ordenador local. Si la entrada no es válida, //devuelve INADDR_NONE (usualmente -1). exit(1); saClient.sin_port = htons(nPort); // Puerto del Servidor // Conecta el socket if (connect(elSocket, (struct sockaddr *)&saClient, sizeof(saClient)) == SOCKET_ERROR) { printf("No nos podemos conectar a %s:%d: %d\n", host, nPort, GetLastError()); exit(EXIT_FAILURE); } _beginthread((void(*)(void*))Leer, 0, (void*)elSocket); // Se tiene que //iniciar la recepcion //antes y una sola vez // Modulo de escritura. En cuanto el modulo de lectura setea la varialble no_salir en 0 //se termina el programa. while(no_salir) { (szBuf[0]=getchar()); send(elSocket, szBuf, 1, 0); } closesocket(elSocket); return; } no salir = 1; ... crear Socket dado el FQDN busca IP (completa estructura hostent) conecta Socket con IP servidor + port servidor nRet = 1; crea thread 0<nRet<tamaño BUFFER? (de lectura) NO imprime BUFFER no_salir ==1? cierra SOCKET getchar(); RECV noticias (nRet: #bytes recibidos) SEND char NO no_salir = 0; Cliente UDP /* Cliente para enviar datagramas UDP a cualquier servidor y puerto */ #include <windows.h> #include <windowsx.h> #include <stdio.h> #include <string.h> #include <winsock2.h> #define IDB_BUTTON 200 #define IDB_EDIT 100 #define IDB_EDIT1 101 #define IDB_EDIT2 102 #define MAX_ARRAY 255 #define PRINTERROR(s) \ MessageBox(NULL,s, "Error", 0); /* Prototipos de funciones */ LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM); void EnviarDatagrama(char *szServer, short nPort, char *campo); /* Variables globales */ char szClassName[ ] = "Cliente UDP de noticias"; char mensaje[MAX_ARRAY]; char campo[MAX_ARRAY]; char diripstring[MAX_ARRAY]; char puertostring[6]; /* Se crea la funcion para Windows*/ int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nFunsterStil) { HWND hwnd; /* MSG messages; /* WNDCLASSEX wincl; /* WORD wVersionRequested = WSADATA wsaData; int nRet; short nPort; Crea el handle para la nueava ventana */ Aqui Guarda los mensajes de la instancia*/ Estructura de la "clase" de la ventana */ MAKEWORD(1,1); /* Necesario para iniciar Winsock*/ /* Necesario para iniciar Winsock*/ /* Valor que devuelve la funcion q inicia Winsock*/ /* Puerto */ wincl.hInstance = hThisInstance; wincl.lpszClassName = szClassName; wincl.lpfnWndProc = WindowProcedure; wincl.style = CS_DBLCLKS; wincl.cbSize = sizeof (WNDCLASSEX); /* Funcion llamada por windows /* double-clicks */ /* Usar iconos y punteros default, datos de la ventana */ wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wincl.hCursor = LoadCursor (NULL, IDC_ARROW); wincl.lpszMenuName = NULL; wincl.cbClsExtra = 0; wincl.cbWndExtra = 0; /* Colores default del sistema */ wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND; /* Si no se registra la clase, salir */ if (!RegisterClassEx (&wincl)) return 0; /* Creacion del programa en windows*/ hwnd = CreateWindowEx ( 0, /* Extensiones */ szClassName, /* Nombre de la clase */ "Cliente UDP de noticias", /* Titulo */ WS_OVERLAPPEDWINDOW, /* Window Default */ CW_USEDEFAULT, /* Posicion definida por windows */ CW_USEDEFAULT, /* Posicion definida por windows */ 420, /* Ancho */ 315, /* Alto */ HWND_DESKTOP, /* El programa es hijo directo de windows */ NULL, /* Sin menu */ hThisInstance, /* handler de la instancia del programa*/ */ NULL ); /* Sin datos de la creacion de la ventana */ ShowWindow (hwnd, nFunsterStil); MessageBox( /* Mensaje de bienvenida con instrucciones */ NULL, "Con este programa usted podra enviar una noticia.\n\ Ingrese su noticia, Nombre o IP del servidor y puerto\n\ y pulse Enviar", "Instrucciones", 0); while (GetMessage (&messages, NULL, 0, 0)) { /* Traduce a mensaje de caracteres */ TranslateMessage(&messages); /* Envía mensaje a WindowProcedure */ DispatchMessage(&messages); } /* Ya se completo el formulario, tengo los datos que necesito */ /* Comienza el envio */ nPort = atoi(puertostring); nRet = WSAStartup(wVersionRequested, &wsaData); /* Se inicia Winsock */ if (wsaData.wVersion != wVersionRequested) { PRINTERROR("Error de version de Winsock"); return; } EnviarDatagrama(diripstring, nPort, campo); /* funcion que envia datagrama*/ if(strlen(campo)) /* Si hay mensaje para enviar, muestra el mensaje*/ MessageBox(NULL, campo, "Noticia Enviada",0 ); WSACleanup(); /* Funcion de limpieza de Winsock */ return messages.wParam; } /* Esta function se llama por la funcion DispatchMessage() */ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hButton1, hButton2; int ret; static HWND hWndStatic, hWndScroll, hWndEdit, hWndEdit2, hWndEdit3; static int iPos; switch (message) /* maneja el mensaje */ { case WM_DESTROY: PostQuitMessage (0); /* mensaje para salir */ break; case WM_CREATE: /* crea elementos para interactuar */ { hWndStatic = CreateWindow("static", "Programa para enviar noticias por protocolo UDP", WS_CHILD | WS_VISIBLE | WS_BORDER | SS_CENTER, 25, 20, 360, 20, hwnd, (HMENU) 1, ((LPCREATESTRUCT) lParam)->hInstance, NULL); hWndEdit = CreateWindow("edit", "Escriba aqui su mensaje", WS_CHILD | WS_VISIBLE | WS_BORDER |ES_AUTOVSCROLL | ES_MULTILINE, 25, 60, 360, 110, hwnd, (HMENU) IDB_EDIT,((LPCREATESTRUCT) lParam)->hInstance,NULL); hWndEdit2 = CreateWindow("edit", "Escriba aqui el servidor", WS_CHILD | WS_VISIBLE | WS_BORDER |ES_AUTOVSCROLL | ES_MULTILINE, 25, 180, 360, 20, hwnd, (HMENU) IDB_EDIT1,((LPCREATESTRUCT) lParam)->hInstance,NULL); hWndEdit3 = CreateWindow("edit", "Escriba aqui el Puerto", WS_CHILD | WS_VISIBLE | WS_BORDER |ES_AUTOVSCROLL | ES_MULTILINE, 25, 210, 360, 20, hwnd, (HMENU) IDB_EDIT2,((LPCREATESTRUCT) lParam)->hInstance,NULL); hButton1 = CreateWindow ("BUTTON","Enviar", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 250, 250, 50, 20, hwnd, (HMENU)IDB_BUTTON,((LPCREATESTRUCT) lParam)->hInstance,NULL); hButton2 = CreateWindow ("BUTTON","Cerrar", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 320, 250, 50, 20, hwnd, (HMENU)IDB_BUTTON,((LPCREATESTRUCT) lParam)->hInstance,NULL); } case WM_COMMAND: /* ejecuta comandos en base a las opciones */ { if (LOWORD(wParam) == IDB_BUTTON){ DestroyWindow (hwnd); } else if (LOWORD(wParam)==IDB_EDIT) GetWindowText(hWndEdit, campo, 255); else if (LOWORD(wParam)==IDB_EDIT1) GetWindowText(hWndEdit2, diripstring, 16); else if (LOWORD(wParam)==IDB_EDIT2) GetWindowText(hWndEdit3, puertostring, 6); }break; default: /* para los mensajes que no tienen sentido */ return DefWindowProc (hwnd, message, wParam, lParam); } return 0; } /* Funcion para enviar datagrama UDP */ void EnviarDatagrama(char *szServer, short nPort, char *campo) { LPHOSTENT lpHostEntry; /* donde se guardara la direccion */ lpHostEntry = gethostbyname(szServer); if (lpHostEntry == NULL) { PRINTERROR("Error en la resolucion de nombres"); return; } SOCKET elSocket; elSocket = socket(AF_INET, /* Address family */ SOCK_DGRAM, /* Socket type */ IPPROTO_UDP); /* Protocol */ if (elSocket == INVALID_SOCKET) { PRINTERROR("error al asignar al socket el tipo y familia"); return; } SOCKADDR_IN saServer; saServer.sin_family = AF_INET; saServer.sin_addr = *((LPIN_ADDR)*lpHostEntry->h_addr_list); saServer.sin_port = htons(nPort); char szBuf[256]; int nRet; strcpy(szBuf, campo); /* lleno el buffer para enviar*/ nRet = sendto(elSocket, szBuf, strlen(szBuf), 0, /* envio */ (LPSOCKADDR)&saServer,sizeof(struct sockaddr)); closesocket(elSocket); /* cierro el socket */ return; } Capturas La siguiente es una captura desde el lado del Servidor UDP El cliente UDP tiene el siguiente entorno gráfico para que el redactor pueda enviar noticias. Una vez presionado el botón de enviar se intercambia un sólo paquete ya que este protocolo de transporte no implementa mensajes de tipo SYN, ACK, FIN u otros que sí se dan en una comunicación con conexión. En esta captura se observa que UDP no chequea ni siquiera que esté operativo el E.S remoto. Tan sólo envía la noticia. UDP delega la responsabilidad que lleguen correctamente los paquetes al proceso aplicativo. Entonces es facultad del programador determinar si el entorno donde se vaya a ejecutar la comunicación entre el servidor y el cliente sea seguro o requiera de mecanismos que dificulten que se pierdan paquetes, que llegen en modo desordenado entre otros aspectos. El servidor UDP que hemos implementado no espera escuchar noticias de un host en particular pero cuenta con que el redactor de noticias y el servidor (donde se almacenen las noticias) estén en un ambiente seguro, posiblemente compartiendo la misma red. En la captura observamos que pese a que ambos E.S están en distintas redes el datagrama llegó al servidor sin problemas ya que la captura se hizo desde el lado del server. UDP empaqueta los datos tal cual se los entregan las capas superiores. Cada vez que la aplicación hace un syscall a la función send(), se genera un paquete UDP que contiene el dato que se le pasó a la función no importando su longitud. Aunque no lo parezca esto presenta una ventaja. No requiere de un buffer que almacene los datos y los reempaquete como sí hace TCP, esto es una ganancia en velocidad de procesamiento y capacidad computacional necesaria. Las siguientes son capturas desde el lado del servidor TCP En esta captura se observan tres comunicaciones entrantes desde una misma IP al servidor que escucha por el puerto 1720 TCP. Nuevamente como en el caso del server UDP, este servidor no espera una “visita” de un cliente en particular sino que está a la expectativa de cualquier lector ávido de noticias. Se implementó que la lectura fuera en TCP porque los lectores de esta “agencia de noticias” pueden llegar a estar en lugares remotos de Internet. Entonces para liberar al programador de cuestiones tales como establecer una comunicación con conexión y segura se deja en manos del kernel el manejo de esos detalles. Hablamos que esta comunicación debe ser con conexión para que se pueda establecer un “canal de diálgo” y puedan interactuar en modo full duplex cliente y servidor. Volviendo a la captura vemos que las comunicaciones que llegan al servidor son simultáneas. El server atiende a cada lector sin pasarlo a una lista de espera como daría en un servidor no concurrente. La secuencia es de esta captura es la siguiente. La primer conexión al puerto TCP 1720 del server es una que proviene del puerto 62775. Seguido, empiezan a intercambiarse mensajes y repentinamente llega otra conexión al servidor desde el puerto 62986. El servidor atiende a ambos juntos y cuando el 62775 llega al final de las noticias el servidor le manda un FIN. Al rato un nuevo cliente desde el puerto 62995 le pide establecer una conexión mientras sigue atendiendo al del puerto 62986. Apenas llega un lector el servidor este manda las instrucciones de uso del sistema: “Presione ENTER para recibir las noticias O cualquier otra tecla para salir” El cliente TCP cliquea <Enter> para continuar El servidor le envía la primera noticia (270 B) pero como el Buffer de lectura del archivo de noticias que está en el server es de 256 Bytes se la envía en dos partes seguidas. El cliente no tiene problema en que lleguen en varios momentos distintos los componentes de la primera noticia porque el módulo de recepción se ejecuta en un hilo paralelo al de envío, por ende no hay riesgo que el syscall a recv() bloquee el cliente TCP esperando recibir todo lo necesario. En esta captura el cliente manda el segundo segmento que compone la primera noticia. En la pantalla del lector se puede leer la noticia completa: “LA HABANA.de jefe de Estado y frente del país, en oficial del Partido El presidente Fidel Castro anunció hoy su renuncia a los cargos de comandante en jefe de Cuba, después de casi medio siglo al un artículo publicado en la edición digital de Granma, diario Comunista.” La manera que tiene el servidor TCP de distinguir una noticia de otra es mediante pipes “|”. O sea el server TCP lee del archivo de noticias creado por el servidor UDP una secuencia de bytes hasta el pipe siguiente o hasta el EOF y la interpreta como una noticia. En esta última captura observamos que un telnet también funciona para conectarse al servidor TCP. La diferencia con el cliente TCP implementado por nosotros es que el <Enter> del telnet es la secuencia \r\n (0d 0a) mientras que nuestro cliente TCP es \n. No obstante, el servidor está preparado para interpretar ambos casos. Otra diferencia notable es que el cliente TCP que hemos hecho está pensado para nuestro servidor TCP que sólo espera recibir un sólo byte de respuesta por vez. Se puede usar nuestro cliente TCP como un telnet común y silvestre. En la siguiente captura se conecta desde un telnet al servidor TCP y se recibe la primera noticia tambien en dos pedazos. Se sigue con la siguiente noticia también dividida en dos partes. Primera parte de la segunda noticia Segunda parte de la segunda noticia En esta última captura vemos que el lector presiona otra tecla distinta a <Enter> y el servidor TCP entiende que ya no quiere seguir leyendo las noticias. Por lo tanto el servidor envía el paquete de desconexión. El servidor comienza el mecanismo de FIN