Tp 4 (Winsock App.) - Facultad de Ingeniería

Anuncio
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
Descargar