66.48 Seminario de Redes Trabajo Práctico Nº2 Programación de una aplicación distribuida usando la API de Berkeley Sockets Grupo: MOG 2º Cuatrimestre 2004 66.48 Seminario de Redes MOG Objetivo Familiarizarse con la API (Application Programming Interface) de los Berkeley Sockets y hacer uso de las system calls. Realizar simples aplicaciones distribuidas que pongan en evidencia el funcionamiento de las mismas, haciendo hincapié en que el medio de transmisión es no confiable. Introducción Luego de haber aprendido cómo funcionan los Protocolos TCP/IP, es necesario analizar la interfaz que existe entre los programas de aplicación y el software de protocolo, considerando el hecho de que los estándares no especifican exactamente cómo interactúan, y por ende es que la arquitectura de interfaz no está estandarizada. Es más, como el software de protocolo residen en el sistema operativo de una pc, los detalles de la interfaz dependen del mismo. Una ejemplo de interfaz entre los programas de aplicación y los protocolos TCP/IP, es la abstracción denominada Socket. Dando un definición concreta, podría decirse que en UNIX y otros Sistemas Operativos, es un objeto (entidad que consiste de datos y procedimientos para manipular los mismos) de software que conecta una aplicación con el protocolo de red. El Socket es el endpoint de una comunicación entre dos programas corriendo en la red. Si pensamos al Socket como una generalización del mecanismo de acceso a archivos de UNIX, los programas de aplicación requieren que el Sistema Operativo cree un Socket cuando se necesita. El SO devuelve un entero que utiliza el programa para hacer referencia al Socket recientemente creado. A diferencia con el acceso a archivos, puede crear Sockets sin enlazarlos a direcciones de destino específicas. La aplicación puede elegir abastecer una dirección de destino cada vez que utiliza el Socket (es decir, cuando se envían Datagramas UDP) o elegir enlazar la dirección de destino a un Socket y evadir la especificación de destino repetidamente (es el caso cuando se realiza una conexión TCP). Los Sockets hacen ejecuciones iguales a los archivos de UNIX, de manera que pueden utilizarse con operaciones tales como read y write. Un vez que una aplicación crea un Socket y una conexión TCP del Socket a un destino externo, el programa puede hacer uso de write para mandar un flujo de datos a través de la conexión (el programa de aplicación en el otro extremo puede utilizar read para recibirlo). Si tomamos un programa cliente que intenta establecer una comunicación con un programa servidor, sería de la siguiente forma: Normalmente, el servidor, que corre en una PC, posee un Socket al cual se le asocia un número de Puerto. El servidor “escucha” el Socket esperando un pedido de conexión de un cliente. El cliente conoce el hostname de la PC donde está corriendo el servidor y el número de puerto donde se está ejecutando el servicio. Así, el cliente le hace un pedido de conexión al servidor en la PC donde corre y al puerto que tiene asociado. Página 1 66.48 Seminario de Redes MOG Creación de un Socket El system call socket crea sockets cuando se lo pide. Toma tres argumentos enteros y devuelve un resultado entero. resultado = socket(pf, tipo, protocolo) pf : especifica familia de protocolo que se va a utilizar, es decir, especifica cómo interpretar la dirección cuando se suministra (para TCP/IP es PF_INET). tipo: especifica el tipo de comunicación que se desea. SOCK_STREAM es para servicio de entrega confiable de flujo (TCP), SOCK_DGRAM es para entrega de datagramas sin conexión (UDP). protocolo: se utiliza para acomodar los diversos protocolos dentro de una familia. Especifica el protcolo en caso que el modelo de red soporte diferentes tipos de modelos de Stream y Datagrama. Como TCP/IP sólo tiene uno para cada uno, se setea en 0. Un ejemplo con TCP sería: s = socket(PF_INET, SOCK_STREAM, 0) Finalización del Socket Cuando un proceso termina de utilizar un socket, se llama a: close(socket) El argumento socket especifica al descriptor (entero) de un socket para que cierre. Especificación de una dirección local Inicialmente, un socket se crea sin ninguna asociación hacia direcciones locales o de destino. Para TCP/IP significa que ningún número de puerto de protocolo local se ha asignado y que ningún puerto de destino o dirección IP se ha especificado. En muchos casos, los programas de aplicación no se preocupan por las direcciones locales que utilizan, ni están dispuestos a permitir que el software de protocolo elija una para ellos. Sin embargo los procesos del servidor que operan en un puerto bien conocido deben ser capaces de especificar dicho puerto para el sistema. Una vez que se ha creado un socket, el servidor utiliza una llamada del sistema bind para establecer una dirección local para ello. bind(socket, localaddr, addrlen) Página 2 66.48 Seminario de Redes MOG socket: descriptor de enteros del socket que ha de ser enlazado localaddr: estructura que especifica la dirección local a la que el socket deberá enlazarse addrlen: entero que especifica la longitud de las direcciones medidas en octetos. Estructura de las direcciones (sockaddr): struct sockaddr { u_short sa_family; char sa_data[14]; }; 0 /* address family */ /* up to 14 bytes of direct address */ 16 31 Familia de Direcciones Octetos de Dirección 0-1 Octetos de Dirección 2-5 Octetos de Dirección 6-9 Octetos de Dirección 10-13 Familia de Direcciones (16 bits) identifica el conjunto de protocolos al que pertenece la dirección. Seguido de una dirección de hasta 14 octetos. El valor de este campo determina el formato de los octetos restantes. El valor 2 significa que posee una dirección de TCP/IP, y la dirección de socket se conoce como sockaddr_in. Esto incluye una dirección del IP y un número de puerto de protocolo. Formato exacto para TCP/IP: struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; donde in_addr es: typedef struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; } S_un; } in_addr; 0 16 Familia de Direcciones (2) S_un S_un_b Address of the host formatted as four u_chars. S_un_w Address of the host formatted as two u_shorts. S_addr Address of the host formatted as a u_long. 31 Puerto de Protocolo Dirección IP CERO CERO Página 3 66.48 Seminario de Redes MOG Si bien la estructura se podría llenar con valores arbitrarios; en el caso de que, por ejemplo, quien llame podría pedir un puerto de protocolo local que ya está en uso por otro programa, o si pidiese una dirección IP no válida entonces bind falla y devuelve un código de error. Conexión de socket (para TCP) Inicialmente, un socket se crea en el unconnected state (estado no conectado), lo que significa que el socket no está asociado con ningún destino externo. La llamada de sistema connect (conectar) enlaza un destino permanente a un socket, colocándolo en el connected state (estado conectado). Una aplicación debe llamar a connect para establecer una conexión antes de que pueda transferir datos a través de un socket de flujo confiable. Los sockets utilizados con servicios de datagrama sin conexión necesitan no estar conectados antes de usarse, pero haciéndolo así, es posible transferir datos sin especificar el destino en cada ocasión. connect(socket, destaddr, addrlen) socket: descriptor entero del socket que ha de conectarse destaddr: es una estructura de dirección de socket en la que se especifica la dirección de destino a la que deberá enlazarse el socket. addrlen: longitud de la dirección de destino medida en octetos. En el caso de servicio sin conexión, connect no hace nada más que almacenar localmente la dirección de destino. Envío de datos Hay cinco system calls posibles, una vez que se ha establecido el socket, write(socket, buffer, length) buffer: contiene la dirección de datos que se han de mandar length: número de octetos que se han de mandar send(socket, message, length, flags) message: da la dirección de datos a ser mandados length: especifica el número de octetos que se va a enviar flags: controla la transmisión Las llamadas de sistema sendto y sendmsg permiten que quien llama envíe un mensaje a través de un socket no conectado, pues ambos requieren que quien llama especifique un destino. Sendto, que toma la dirección de destino como argumento, tiene la forma Página 4 66.48 Seminario de Redes MOG sendto(socket, message, length, flags, destaddr, addrlen) Los dos argumentos finales especifican una dirección de destino y dan la longitud de la misma. destaddr utiliza la estructura sockadrr_in mencionada anteriormente. Se puede utilizar sendmsg en casos en los que la larga lista de argumentos requeridos para sendto haga que el programa sea ineficaz o difícil de leer. sendmsg(socket, messagestruct, flags) messagestruct: es una estructura que contiene información acerca del mensaje, longitud del mismo, dirección de destino y su longitud. APUNTADOR A SOCKETADOR TAMAÑO DE SOCKETADOR APUNTADOR DE LISTA IOVEC LONGITUD DE LISTA IOVEC APUNTADOR DE LISTA DE DERECHOS DE ACCESO LONGITUD DE LISTA DE DERECHOS DE ACCESO A su vez está writev que no describiremos y conforma la quinta system call. Recepción de datos Ofrece cinco llamadas de sistema, análogas a las cinco operaciones de salida, para utilizar en la recepción de datos a través de un socket. read sólo se puede usar cuando un socket se conecta. read(descriptor, buffer, length) descriptor: entero de un socket del que se puede leer los datos buffer: especifica la dirección de memoria donde se almacenan los datos length: especifica el número máximo de octetos que pueden leerse La llamada recvfrom permite la entrada de un socket no conectado. recvfrom(socket, buffer, length, flags, fromaddr, addrlen) Los dos argumentos adicionales: fromaddr y addrlen son apuntadores de estructura de dirección de socket y un entero. El sistema operativo utiliza fromaddr para registrar las direcciones de quien envía el mensaje y utiliza el fromlen para registrar la longitud de la dirección de quien lo envía. recvmsg, es análoga a la operación de salida sendmsg. Opera como recvfrom, pero requiere menos argumentos. Página 5 66.48 Seminario de Redes MOG recvmsg(socket, messagestruct, flags) donde el argumento messagestruct da la dirección de una estructura que sostiene la dirección para el mensaje que entra así como también las ubicaciones para la dirección de quien envía. La forma es la misma que en la de sendmsg. Para completar las cinco system calls están readv y recv. Obtención de direcciones de socket Un proceso llama a getpeername para determinar la dirección de la pareja (el extremo remoto) al que se conecta el socket. getpeername(socket, destaddr, addrlen) destaddr: es un apuntador de estructura de tipo sockaddr que recibirá la dirección de socket. addrlen: es un apuntador de entero que recibirá la longitud de la dirección. Sólo trabaja con socket conectados. getsockname devuelve la dirección local asociada con un socket getsockname(socket, localaddr, addrlen) localaddr: apuntador a una estructura de tipo sockaddr que contendrá la dirección; addrlen: apuntador a entero que contendrá la longitud de dirección. Especificación de una longitud de cola para un servidor Un servidor crea un socket, lo enlaza a un puerto de protocolo bien conocido y espera las peticiones. Si el servidor emplea entrega de flujo confiable, o si la computación de una respuesta lleva cantidades no triviales de tiempo, puede suceder que una nueva petición llegue antes de que el servidor termine de responder a un petición anterior. Para ello existe una llamada, que deja en cola la peticiones hasta poder procesarlas: listen(socket, qlength) Pone al socket en modo pasivo listo para aceptar las conexiones. También informa al sistema operativo de poner en cola las peticiones posteriores. qlength: especifica la longitud de la cola de espera para ese socket. Si la cola está llena, el sistema rechaza la conexión descartando la petición. Sólo para el servicio de entrega de flujo confiable. Servidor aceptando conexiones Página 6 66.48 Seminario de Redes MOG Recordemos que el socket no está conectado a un destino exterior específico. De hecho, ese destino exterior debe ser un wildcard (comodín), lo cual permite que el socket reciba peticiones de conexión de cualquier cliente. Una vez establecido un socket, el servidor espera la conexión. Para ello se utiliza accept. Una llamada accept se bloquea hasta que la petición de conexión llega. newsock = accept(socket, addr, addrlen) Cuando llega una petición, el sistema llena un argumento addr con la dirección del cliente que ha colocado la petición y configura addrlen a la longitud de la dirección. Por último, crea un nuevo socket que tiene su destino conectado hacia el cliente que pide, y devuelve el nuevo descriptor de socket a quien llama. Cuando llega una petición de conexión la llamada accept reaparece. El servidor puede manejar las peticiones de manera concurrente (después de que la llamada accept reapareció, el servidor maestro crea un esclavo para manejar la petición. El proceso esclavo hereda una copia del nuevo socket, de modo que puede proceder a servir la petición. Cuando finaliza, el esclavo cierra el socket y termina. El proceso maestro cierra su copia de un nuevo socket y después de iniciar al esclavo. Después llama a accept para obtener la siguiente petición de conexión.) o iterativa (el servidor mismo maneja la petición, cierra el nuevo socket y, después, llama a accept para obtener la siguiente petición). Obtención y especificación de nombre de host El sistema UNIX mantiene un nombre de host interno. Para las máquinas de Internet, el nombre interno suele elegirse como el nombre de dominio para la interfaz principal de la red de la máquina. gethostname permite a los procesos del usuario que accedan al nombre de host, y la llamada sethostname permite a los procesos privilegiados definir el nombre de host. gethostname(name, length) name: da la dirección de un arreglo de octetos en la que se ha de almacenar el nombre length: entero que especifica la longitud del arreglo name. sethostname(name, length) Los argumentos son análogos. Página 7 66.48 Seminario de Redes MOG Desarrollo Debido a que la escasez de tiempo presentada por las diversas circunstancias que afectaron al normal desarrollo del seminario nos replanteamos el objetivo de programar una aplicación distribuida más compleja con protocolos propietarios y más funcionalidades. En cambio, optamos por una aplicación de mayor simplicidad a modo de “API demo”. La función principal de la aplicación es la de realizar un “file transfer” desde un cliente hacia un servidor por dos métodos distintos: o conexión TCP: se encarga de garantizar el transporte. o conexión UDP: a través un simple protocolo propietario STOP&WAIT. Funcionamiento y Sintaxis de Uso El funcionamiento básico de la aplicación es la siguiente. Primero debe ejecutarse en modo servidor especificando el transporte, puerto y nombre de archivo a escribir los datos entrantes. Segundo, debe ejecutarse el cliente de manera que aparee la configuración del servidor. Una vez terminada la transferencia del archivo, ambos programas se cierran automáticamente ya que poseen un mecanismo para darse cuenta cuando finalizó la misma. El archivo ejecutable que se adjuntará junto con el código fuente se llama mogsocket y su sintaxis es la siguiente: Modo Servidor ./mogsocket [udp|tcp] server port filename Modo Cliente ./mogsocket [udp|tcp] client IP port filename chunksize [udp|tcp]: protocolo de transporte a elegir. server/client: modo en que funcionará el programa. IP: (sólo en modo cliente) dirección IP del servidor al que me quiero conectar. port: puerto en que escuchará o mandará datagramas dependiendo del rol de la aplicación. filename: nombre del archivo a leer(cliente) o escribir(servidor). chunksize: (sólo en modo cliente) tamaño de escritura en el socket. En el caso de transporte UDP será el tamaño del datagrama. Análisis de Capturas A la hora de realizar las capturas estas se prefirieron hacer sobre Internet o sobre una Internet simulada mediante un “traffic shaper”, ya que sobre una LAN no se evidencian, en general, las características del protocolo IP. Esta consideración es muy importante y generalmente olvidada por gran parte de los programadores, ya que sus aplicaciones suelen correr sin problemas en un entorno de LAN. Sin embargo, cuando lo hacen sobre Internet surgen problemas no previstos como la pérdida, duplicación, pérdida de orden, gran variación de latencia y etc. de los datagramas que causan fallas en aplicación o peor aún su total inutilización debido a loop infinitos o sentencias blocantes. Página 8 66.48 Seminario de Redes MOG Captura con Bw 100Kbps en TCP Esta prueba se realizó con un “traffic shaper” para contrastar contra la anterior captura, exceptuando que no hay perdida de paquetes aleatoria, sólo por overflow de colas(en el “traffic shaper”) o buffers(en la aplicación). Los comandos utilizados para simular un medio de transmisión de 100Kbps con una cola de 5KBytes fueron los siguientes. ipfw pipe 1 config bw 100Kbit/s queue 5KBytes ipfw pipe 2 config bw 100Kbit/s queue 5KBytes ipfw add 101 pipe 1 ip from 192.168.2.84 to 192.168.2.99 in ipfw add 102 pipe 2 ip from 192.168.2.99 to 192.168.2.84 out Las primeras dos líneas definen los pipes en ambos sentidos de transmisión y las segundas dos relacionan los pipes con pares de direcciones IP. Se ejecutaron las siguientes líneas de comando. 192.168.2.99 – TTY01 #tcpdump -i rl0 -s 0 tcp port 1024 192.168.2.99 – TTY02 #./mogsocket tcp client 192.168.2.84 1024 posta02 1000 192.168.2.84#./mogsocket server 1024 arch4 Tramas No. Time 1 0.000000 Source 192.168.2.99 2 0.000985 192.168.2.84 3 0.017882 192.168.2.99 4 0.097887 192.168.2.99 5 0.099451 192.168.2.84 6 0.217916 192.168.2.99 7 0.218898 192.168.2.84 8 0.337923 192.168.2.99 9 0.338861 192.168.2.84 10 0.457915 192.168.2.99 11 0.458857 192.168.2.84 12 0.577918 192.168.2.99 13 0.578809 192.168.2.84 14 0.697924 192.168.2.99 15 0.698878 192.168.2.84 16 0.817933 192.168.2.99 17 0.819002 192.168.2.84 18 0.937924 192.168.2.99 19 0.938762 192.168.2.84 20 1.057922 192.168.2.99 21 1.058751 192.168.2.84 22 1.137915 192.168.2.99 Destination 192.168.2.84 Protocol TCP Info 1028 > 1024 [SYN] Seq=0 Ack=0 Win=65535 Len=0 MSS=1460 WS=1 TSV=17075044 TSER=0 192.168.2.99 TCP 1024 > 1028 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=1333473 TSER=17075044 WS=0 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=1 Ack=1 Win=66608 Len=0 TSV=17075045 TSER=1333473 192.168.2.84 TCP 1028 > 1024 [PSH, ACK] Seq=1 Ack=1 Win=66608 Len=1000 TSV=17075045 TSER=1333473 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=1001 Win=7000 Len=0 TSV=1333483 TSER=17075045 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=1001 Ack=1 Win=66608 Len=1448 TSV=17075045 TSER=1333473 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=2449 Win=10136 Len=0 TSV=1333495 TSER=17075045 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=2449 Ack=1 Win=66608 Len=1448 TSV=17075046 TSER=1333473 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=3897 Win=13032 Len=0 TSV=1333507 TSER=17075046 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=3897 Ack=1 Win=66608 Len=1448 TSV=17075046 TSER=1333473 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=5345 Win=15928 Len=0 TSV=1333519 TSER=17075046 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=5345 Ack=1 Win=66608 Len=1448 TSV=17075054 TSER=1333483 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=6793 Win=18824 Len=0 TSV=1333536 TSER=17075054 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=6793 Ack=1 Win=66608 Len=1448 TSV=17075067 TSER=1333495 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=8241 Win=21720 Len=0 TSV=1333548 TSER=17075067 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=8241 Ack=1 Win=66608 Len=1448 TSV=17075090 TSER=1333519 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=9689 Win=24616 Len=0 TSV=1333560 TSER=17075090 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=9689 Ack=1 Win=66608 Len=1448 TSV=17075103 TSER=1333536 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=11137 Win=27512 Len=0 TSV=1333571 TSER=17075103 192.168.2.84 TCP 1028 > 1024 [ACK] Seq=11137 Ack=1 Win=66608 Len=1448 TSV=17075103 TSER=1333536 192.168.2.99 TCP 1024 > 1028 [ACK] Seq=1 Ack=12585 Win=30408 Len=0 TSV=1333583 TSER=17075103 192.168.2.84 TCP 1028 > 1024 [FIN, PSH, ACK] Seq=12585 Ack=1 Página 9 66.48 Seminario de Redes MOG 23 1.139602 192.168.2.84 192.168.2.99 TCP 24 1.147851 192.168.2.99 192.168.2.84 TCP Win=66608 Len=979 TSV=17075114 TSER=1333548 1024 > 1028 [FIN, ACK] Seq=1 Ack=13565 Win=33304 Len=0 TSV=1333591 TSER=17075114 1028 > 1024 [ACK] Seq=13565 Ack=2 Win=66608 Len=0 TSV=17075158 TSER=1333591 Las tramas 1, 2 y 3 son el conocido three-way handshake de TCP para establecer la conexión. Puede verse que presenta opciones como: o MSS: Maximum Segment Size es el máximo tamaño que puede mandar y por ende recibir sin que haya fragmentación IP o TimeStamp Option: que consta de dos campos timestamp value y echo reply (tsval y tser) o WS: Window Scale es un multiplicador (bit shift para el caso) para poder ir más allá de los 65535 bytes de tamaño que darían los 16 bits. Como se puede observar la transferencia se realizó sin problemas. Resumiendo en números: Nodo A 192.168.2.84 port: 1024 Nodo B 192.168.2.99 port: 1028 Total Packets 24 Total Bytes 15163 A-> B Packets 11 A-> B Bytes: 734 B->A Packets: 13 B->A Bytes 14429 De estos números se puede verificar el ancho de banda restringido por el “traffic shaper” que da: 14429 ⋅ 8 BwB −> A estimado = 100,5Kbps 1,147851 Captura con Bw 100Kbps y Packet Loss Rate 0,5 en TCP Como la Internet de hoy en día a mejorado en confiabilidad debido al crecimiento tecnológico, tuvimos que realizar la prueba con un “traffic shaper”. Para lograr dicho objetivo se utilizó una máquina con un sistema operativo FreeBSD versión 4.5 con el kernel compilado con la opción DUMMYNET que es la que permite la funcionalidad de traffic shaping. Los comandos utilizados para simular un medio de transmisión de 100Kbps con una cola de 5KBytes y un packet loss rate de 0,5 (pierde un 50% de los paquetes) fueron los siguientes. ipfw pipe 1 config bw 100Kbit/s queue 5KBytes plr 0.5 ipfw pipe 2 config bw 100Kbit/s queue 5KBytes plr 0.5 ipfw add 101 pipe 1 ip from 192.168.2.84 to 192.168.2.99 in ipfw add 102 pipe 2 ip from 192.168.2.99 to 192.168.2.84 out Página 10 66.48 Seminario de Redes MOG Las primeras dos líneas definen los pipes en ambos sentidos de transmisión y las segundas dos relacionan los pipes con pares de direcciones IP. Se ejecutaron las siguientes líneas de comando. 192.168.2.99 – TTY01 #tcpdump -i rl0 -s 0 tcp port 1024 192.168.2.99 – TTY02 #./mogsocket tcp client 192.168.2.84 1024 arch1 1000 192.168.2.84#./mogsocket server 1024 save1 No. Time 1 0.000000 Source 192.168.2.99 Destination 192.168.2.84 2 0.001468 192.168.2.84 192.168.2.99 3 0.009842 192.168.2.99 192.168.2.84 4 0.139787 192.168.2.99 192.168.2.84 5 0.140776 192.168.2.84 192.168.2.99 6 1.119821 192.168.2.99 192.168.2.84 7 1.120821 192.168.2.84 192.168.2.99 8 3.119816 192.168.2.99 192.168.2.84 9 3.136586 192.168.2.84 192.168.2.99 10 31.120239 192.168.2.99 192.168.2.84 11 31.122358 192.168.2.84 192.168.2.99 12 31.240221 192.168.2.99 192.168.2.84 13 31.241438 192.168.2.84 192.168.2.99 14 46.230474 192.168.2.99 192.168.2.84 15 46.231505 192.168.2.84 192.168.2.99 16 94.231172 192.168.2.99 192.168.2.84 17 94.232399 192.168.2.84 192.168.2.99 18 158.232153 192.168.2.99 192.168.2.84 19 158.233765 192.168.2.84 192.168.2.99 20 158.362142 192.168.2.99 192.168.2.84 21 158.363269 192.168.2.84 192.168.2.99 22 158.422117 192.168.2.99 192.168.2.84 23 158.423811 192.168.2.84 192.168.2.99 24 158.637643 192.168.2.84 192.168.2.99 25 158.732123 192.168.2.99 192.168.2.84 26 159.632159 192.168.2.99 192.168.2.84 27 159.632670 192.168.2.84 192.168.2.99 Protocol Info TCP 1034 > 1024 [SYN] Seq=0 Ack=0 Win=65535 Len=0 MSS=1460 WS=1 TSV=17693853 TSER=0 TCP 1024 > 1034 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=1952157 TSER=17693853 WS=0 TCP 1034 > 1024 [ACK] Seq=1 Ack=1 Win=66608 Len=0 TSV=17693854 TSER=1952157 TCP 1034 > 1024 [ACK] Seq=1 Ack=1 Win=66608 Len=1448 TSV=17693855 TSER=1952157 TCP 1024 > 1034 [ACK] Seq=1 Ack=1449 Win=8688 Len=0 TSV=1952171 TSER=17693855 TCP [TCP Out-Of-Order] 1034 > 1024 [ACK] Seq=1 Ack=1 Win=66608 Len=1448 TSV=17693954 TSER=1952157 TCP [TCP Dup ACK 5#1] 1024 > 1034 [ACK] Seq=1 Ack=1449 Win=8688 Len=0 TSV=1952271 TSER=17693954 TCP [TCP Out-Of-Order] 1034 > 1024 [ACK] Seq=1 Ack=1 Win=66608 Len=1448 TSV=17694154 TSER=1952157 TCP [TCP Dup ACK 5#2] 1024 > 1034 [ACK] Seq=1 Ack=1449 Win=8688 Len=0 TSV=1952468 TSER=17694154 TCP [TCP Out-Of-Order] 1034 > 1024 [ACK] Seq=1 Ack=1 Win=66608 Len=1448 TSV=17696954 TSER=1952157 TCP [TCP Dup ACK 5#3] 1024 > 1034 [ACK] Seq=1 Ack=1449 Win=8688 Len=0 TSV=1955268 TSER=17696954 TCP 1034 > 1024 [ACK] Seq=1449 Ack=1 Win=66608 Len=1448 TSV=17696965 TSER=1955268 TCP 1024 > 1034 [ACK] Seq=1 Ack=2897 Win=11584 Len=0 TSV=1955280 TSER=17696965 TCP [TCP Out-Of-Order] 1034 > 1024 [ACK] Seq=1449 Ack=1 Win=66608 Len=1448 TSV=17698465 TSER=1955268 TCP [TCP Dup ACK 13#1] 1024 > 1034 [ACK] Seq=1 Ack=2897 Win=11584 Len=0 TSV=1956777 TSER=17698465 TCP [TCP Out-Of-Order] 1034 > 1024 [ACK] Seq=1449 Ack=1 Win=66608 Len=1448 TSV=17703265 TSER=1955268 TCP [TCP Dup ACK 13#2] 1024 > 1034 [ACK] Seq=1 Ack=2897 Win=11584 Len=0 TSV=1961581 TSER=17703265 TCP [TCP Out-Of-Order] 1034 > 1024 [ACK] Seq=1449 Ack=1 Win=66608 Len=1448 TSV=17709665 TSER=1955268 TCP [TCP Dup ACK 13#3] 1024 > 1034 [ACK] Seq=1 Ack=2897 Win=11584 Len=0 TSV=1967975 TSER=17709665 TCP 1034 > 1024 [ACK] Seq=2897 Ack=1 Win=66608 Len=1448 TSV=17709677 TSER=1967975 TCP 1024 > 1034 [ACK] Seq=1 Ack=4345 Win=14480 Len=0 TSV=1967988 TSER=17709677 TCP 1034 > 1024 [FIN, PSH, ACK] Seq=4345 Ack=1 Win=66608 Len=676 TSV=17709677 TSER=1967975 TCP 1024 > 1034 [FIN, ACK] Seq=1 Ack=5022 Win=17376 Len=0 TSV=1967994 TSER=17709677 TCP [TCP Retransmission] 1024 > 1034 [FIN, ACK] Seq=1 Ack=5022 Win=17376 Len=0 TSV=1968015 TSER=17709677 TCP 1034 > 1024 [ACK] Seq=5022 Ack=2 Win=66608 Len=0 TSV=17709726 TSER=1968015 TCP [TCP Dup ACK 25#1] 1034 > 1024 [ACK] Seq=5022 Ack=2 Win=66608 Len=0 TSV=17709816 TSER=1968015 TCP 1024 > 1034 [RST] Seq=2 Ack=1680971182 Win=0 Len=0 Se puede observar la retransmisión debido a la pérdida de tramas. También como curiosidad nos sorprendió que el cliente ya había ejecutado todos los writes y por ende retornó el prompt de shell. Sin embargo, por las capturas podemos observar que el kernel intentaba seguir transmitiendo los datos. Probablemente porque tenía baja prioridad ante otros procesos no tomaba algunos acknowledges que le llegan y por eso retransmitía. Página 11 66.48 Seminario de Redes MOG Captura con Bw 100Kbps y Packet Loss Rate 0,5 en UDP Con el mismo argumento de la prueba anterior optamos por utilizar un “traffic shaper”. Para eso utilizamos la misma configuración de pipes descripta anteriormente. Se ejecutaron las siguientes líneas de comando. 192.168.2.99 – TTY01 #tcpdump -i rl0 -s 0 udp port 1024 192.168.2.99 – TTY02 #./mogsocket udp server 1024 save1 192.168.2.84#./mogsocket udp client 192.168.2.99 1024 arch1 1000 Tramas No. Î Î Í Î Î Î Î Î Í Î Î Í Î Î Î Î Î Í Î Î Î Î Î Í Î Í Î Î Î Î Î Î Î Î Î Î 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Time 0.000000 2.006712 2.093615 2.097853 4.057558 6.072149 8.083996 10.096995 10.180524 10.181497 12.161879 12.254782 12.256070 14.233200 16.257978 18.229401 20.242467 20.324155 20.325670 22.335772 24.308828 26.323614 28.344918 28.424324 28.426255 28.430859 28.431701 30.399381 32.420959 34.403851 36.406986 38.420341 40.433268 42.396343 44.409189 46.412499 Source 192.168.2.84 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 192.168.2.84 Destination 192.168.2.99 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 192.168.2.99 Protocol UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP Info Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: 32770 32770 1024 32770 32770 32770 32770 32770 1024 32770 32770 1024 32770 32770 32770 32770 32770 1024 32770 32770 32770 32770 32770 1024 32770 1024 32770 32770 32770 32770 32770 32770 32770 32770 32770 32770 Dst.port: 1024 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Dst.port: 1024 Seq# 1000 1000 1000 2000 2000 2000 2000 2000 2000 3000 3000 3000 4000 4000 4000 4000 4000 4000 5000 5000 5000 5000 5000 5000 5020 5020 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 Type data data ACK data data data data data ACK data data ACK data data data data data ACK data data data data data ACK data ACK EOF EOF EOF EOF EOF EOF EOF EOF EOF EOF En rojo se marcaron las retransmisiones. Se puede observar que hay 23 retransmisiones, sin embargo las últimas se deben a que la aplicación server recibió el “EOF notification”, mandó su correspondiente ACK que no llegó y la aplicación se cerró. Entonces el cliente se queda enviando “EOF notification” pero la otra aplicación ya no está corriendo. Por eso lo único que se puede asegurar, descartando las últimas 8 retransmisiones, es que 23 − 8 PLRestimado = 0,54 . Lo cual es aproximadamente el parámetro configurado. 36 − 8 Luego se verificó la integridad del archivo y concordaban byte a byte. Página 12 66.48 Seminario de Redes MOG Captura con Bw 100Kbps en UDP Esta prueba se realizó con un “traffic shaper” para contrastar contra la anterior captura, exceptuando que no hay perdida de paquetes aleatoria, sólo por overflow de colas(en el “traffic shaper”) o buffers(en la aplicación). Los comandos utilizados para simular un medio de transmisión de 100Kbps con una cola de 5KBytes fueron los siguientes. ipfw pipe 1 config bw 100Kbit/s queue 5KBytes ipfw pipe 2 config bw 100Kbit/s queue 5KBytes ipfw add 101 pipe 1 ip from 192.168.2.84 to 192.168.2.99 in ipfw add 102 pipe 2 ip from 192.168.2.99 to 192.168.2.84 out Las primeras dos líneas definen los pipes en ambos sentidos de transmisión y las segundas dos relacionan los pipes con pares de direcciones IP. Se ejecutaron las siguientes líneas de comando. 192.168.2.99 – TTY01 #tcpdump -i rl0 -s 0 udp port 1024 192.168.2.99 – TTY02 #./mogsocket udp server 1024 save1 192.168.2.84#./mogsocket udp client 192.168.2.99 1024 arch1 1000 Tramas No. Î Í Î Í Î Í Î Í Î Í Î Í Î Í 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Time 0.000000 0.089223 0.090586 0.167412 0.169108 0.247390 0.249075 0.335263 0.336241 0.427728 0.430191 0.432383 0.433975 0.436186 Source 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 Destination 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 192.168.2.99 192.168.2.84 Protocol UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP UDP Info Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: Src.port: 32770 1024 32770 1024 32770 1024 32770 1024 32770 1024 32770 1024 32770 1024 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 32770 Dst.port: 1024 Dst.port: 32770 Seq# 1000 1000 2000 2000 3000 3000 4000 4000 5000 5000 5020 5020 -1 -1 Type data ACK data ACK data ACK data ACK data ACK data ACK EOF EOF Como se puede ver la transferencia se realizó sin problemas. Resumiendo en números: Nodo A 192.168.2.84 port: 32770 Nodo B 192.168.2.99 port: 1024 Total Packets 14 Total Bytes 5776 A-> B Packets 7 A-> B Bytes: 5356 B->A Packets: 7 B->A Bytes 420 De estos números se puede verificar el ancho de banda restringido por el “traffic shaper” que da: Página 13 66.48 Seminario de Redes MOG BwA −> B estimado = 5356 ⋅ 8 98, 7 Kbps 0,433975 Transferencia via Internet Se procedió luego de haber hecho pruebas sobre la LAN con traffic shaping a realizar transferencia entre dos computadoras que estaban conectadas a internet. Ambas de la red de fibertel y una atrás de un NAPT. Nos pareció oportuno no mostrar un dump de alrededor de 300 líneas para corroborar que la transferencia se realizó exitosamente. La transferencia se hizo probando ambos capas de transporte: TCP y UDP UDP Tamaño del archivo: 215.209 Bytes Velocidad promedio: 55,5Kbps (extraída del Summary de Ethereal) Tiempo: 34,36 segundos (tiempo hasta la última trama en Ethereal) TCP Tamaño del archivo: 215.209 Bytes Velocidad promedio: 43,9Kbps (extraída del Summary de Ethereal) Tiempo: 48,02 segundos (tiempo hasta la última trama en Ethereal) Para verificar la correcta integridad de la información, se mandó el archivo por e-mail y luego se le realizó el comando diff de Unix. Problemas surgidos A la hora de desarrollar el programa nos topamos con distintos problemas como en todo proyecto aunque éste sea de pequeña dimensión. Sin embargo, el problema más interesante con que nos encontramos surgió al pasar de un entorno de LAN a uno de Internet. El problema era que el buffer de entrada al socket se desbordaba ya que el ancho de banda en Internet era notablemente más chico que la tasa de escritura al socket. Por ende se perdían los datos. Este fenómeno se evidenciaba al transferir archivos mayores a 4KBytes y su tamaño al llegar al otro extremo era aleatorio debido a que las condiciones de Internet son muy variables. Esto se solucionó incluyendo en el código la sentencia select para esperar que el buffer se vacíe y no desborde. Luego de realizado lo antedicho, no hubo problemas de índole similar. Conclusiones La realización de una aplicación distribuida nos permitió tomar conciencia de la facilidad que brindan las API’s, en este caso la de los Berkeley Sockets, que se encargan de manera transparente al programador de realizar tareas complejas como ser la transmisión de datos garantizada con sus temporizadores y algoritmos de retransmisión y control de congestión, que además incluye negociar con dispositivos, accesos a recursos compartidos etc. Por otro lado, cuando se opta por una capa de transporte más simple como ser UDP, ahí entra en juego la habilidad e inteligencia del programador para poner en marcha un protocolo Página 14 66.48 Seminario de Redes MOG de comunicaciones que realice las tareas pertinentes requeridas por la aplicación sin producir bloqueos ante cualquier situación inesperada o fuera de estándares. Es decir, que se da más libertad al programador a costa de una mayor complejidad algorítmica para realizar una tarea determinada. Este hecho se puede observar en nuestro código mogsocket.c ya que hay mayor cantidad de líneas para poder implementar un simple ARQ STOP&WAIT para llevar a cabo la tarea de “file transfer” en comparación con el cliente y servidor TCP. Página 15 66.48 Seminario de Redes MOG Apéndice Codigo fuente del archivo mogsocket.c #include #include #include #include #include #include #include #include #include #define #define #define #define struct struct struct struct struct <stdio.h> <stdlib.h> <sys/types.h> <sys/socket.h> <string.h> <netdb.h> <netinet/in.h> <sys/time.h> <unistd.h> NBUFFER 65536 FALSE 0 TRUE 1 h_addr h_addr_list[0] /*for backward compatibility*/ sockaddr_in sockin; sockaddr client; /*MODO SERVER: datos del cliente entrante*/ sockaddr_in serverendpoint; /*MODO CLIENT: a donde me voy a conectar*/ sockaddr_in fsin; hostent *hp, *gethostbyname(); /* variables para el select() */ fd_set wfds,rfds; struct timeval tv; int retval; socklen_t client_length; /*descriptores de sockets*/ int listener, s; /*variables generales*/ int inbytes=-1; int outbytes; int port; char buffer[NBUFFER]; int chunksize; FILE *archivo; long n; long udp_seq; /*Sequence number del protocolo STOP&WAIT*/ int alen; char char char char IS_TCP=FALSE; IS_UDP=FALSE; IS_CLIENT=FALSE; IS_SERVER=FALSE; int main(int argc, char *argv[]) { /*Verifica que la cantidad de argumentos este bien*/ if (argc<5) { printf("USO: %s [udp|tcp] server port [filename]\n",argv[0]); printf("o\n"); printf("USO: %s [udp|tcp] client IP port filename chunksize\n",argv[0]); exit(0); } /*Determina si el transporte el tcp o udp*/ if (strcmp(argv[1],"tcp")==0) IS_TCP=TRUE; else if (strcmp(argv[1],"udp")==0) IS_UDP=TRUE; else { printf("Error en el argumento 1. Escriba tcp o udp\n"); exit(1); } /*Determina si es cliente o servidor*/ if (strcmp(argv[2],"client")==0) IS_CLIENT=TRUE; else if (strcmp(argv[2],"server")==0) IS_SERVER=TRUE; else { printf("Error en el argumento 2. Escriba client o server\n"); exit(2); } if (IS_CLIENT&&(argc!=7)) { printf("USO: %s [udp|tcp] client IP port filename chunksize\n",argv[0]); exit(0); } /*Convertir el string en un entero*/ if (IS_SERVER) Página 16 66.48 Seminario de Redes MOG port = strtol(argv[3],NULL,10); if (IS_CLIENT) { port = strtol(argv[4],NULL,10); chunksize = strtol(argv[6],NULL,10); } /*Help de linea de Comando*/ if ( (IS_TCP||IS_UDP)&&(IS_SERVER||IS_CLIENT) ) { printf("MOG socket Software - 2004 - Seminario de Redes\n"); if (IS_CLIENT) printf("%s de %s a la IP %s en el puerto %d \n",IS_CLIENT?"CLIENTE":"SERVER", IS_TCP?"TCP":"UDP",argv[3],port); else printf("%s de %s en el puerto %d\n",IS_CLIENT?"CLIENTE":"SERVER", IS_TCP?"TCP":"UDP",port); } /*Parte del TCP server*/ if ( IS_TCP&&IS_SERVER ) { listener = socket (PF_INET,SOCK_STREAM,0); if (listener < 0) { printf("No se pudo crear el socket.\n\n"); exit(0); } memset(&sockin,0,sizeof(sockin)); sockin.sin_family = AF_INET; sockin.sin_addr.s_addr = INADDR_ANY; sockin.sin_port = htons(port); if (bind(listener,(struct sockaddr *)&sockin,sizeof(sockin)) < 0) { printf("Error al hacer Bind\n"); exit(1); } if (listen(listener,5) < 0) { printf("No puedo escuchar al puerto"); exit(2); } s = accept(listener,&client,&client_length); archivo = fopen(argv[4],"w+"); /*abre archivo para escritura*/ if (archivo==NULL) { printf("\nNo se pudo abrir el archivo %s\n",argv[4]); exit(2); } printf("Archivo Abierto exitosamente\n"); n=0; memset(buffer,0,sizeof(buffer)); while (1) { printf("socket descriptor: %d inbytes=%d\t",s,inbytes); if (inbytes==0) { close(s); exit(1); } inbytes = read(s,buffer,sizeof(buffer)); n+=inbytes; /*write(s,buffer,inbytes);*/ fwrite(buffer,1,inbytes,archivo); printf("Writen till byte %d\n",n); } fclose(archivo); }/*fin de TCP SERVER*/ /*Parte del TCP client*/ if ( IS_TCP&&IS_CLIENT ) { s=socket(PF_INET,SOCK_STREAM,0); /* Creo el socket TCP y s es el descriptor */ if (s<1) printf("No hay socket disponible\n"); else printf("Se creo el socket\n"); memset(&serverendpoint,0,sizeof(serverendpoint)); serverendpoint.sin_family = AF_INET; serverendpoint.sin_port = htons(port); hp = gethostbyname(argv[3]); bcopy( hp->h_addr,(char *)&serverendpoint.sin_addr, hp->h_length); /* set address */ /* Ahora efectuo la coneccion */ switch (connect(s,(struct sockaddr *)&serverendpoint,sizeof(serverendpoint))) { case -1: printf("Error al conectarse\n\n"); Página 17 66.48 Seminario de Redes case 0: } MOG exit(1); break; printf("Esta conectado ( ^C para salir)\n\n"); break; archivo = fopen(argv[5],"rb"); /*abre archivo para lectura*/ if (archivo==NULL) { printf("\nNo se pudo abrir el archivo %s\n",argv[5]); exit(2); } printf("Archivo Abierto exitosamente\n"); do { /*variables del select*/ tv.tv_sec = 1; tv.tv_usec = 0; FD_ZERO(&wfds); FD_SET(s, &wfds); retval = select((s+1), NULL, &wfds, NULL, &tv); if (FD_ISSET(s, &wfds)) { printf("Write till byte %d.\n",ftell(archivo)); outbytes = fread(buffer,1,chunksize,archivo); write(s,buffer,outbytes); } else printf("Unable to send data within 1 second.\n"); }while(!feof(archivo)); }/*fin de TCP CLIENT*/ /*Parte del UDP server*/ if ( IS_UDP&&IS_SERVER ) { memset(&sockin,0,sizeof(sockin)); sockin.sin_family = AF_INET; sockin.sin_addr.s_addr = INADDR_ANY; s = socket (PF_INET,SOCK_DGRAM,0);/* Creo el socket UDP y s es el descriptor*/ if (s <0) { printf("Error al crear el socket\n"); exit(1); } sockin.sin_port = htons(port); if ( bind(s, (struct sockaddr *)&sockin, sizeof(sockin)) < 0) { printf("No fue posible hacer bind al puerto %d\n", port); exit(2); } archivo = fopen(argv[4],"w+"); /*abre archivo para escritura*/ if (archivo==NULL) { printf("\nNo se pudo abrir el archivo %s\n",argv[4]); exit(3); } printf("Archivo Abierto exitosamente\n"); n=0; while (1) { alen = sizeof(sockin); inbytes= recvfrom(s, buffer, sizeof(buffer), 0, (struct sockaddr *)&fsin,&alen); if ( inbytes< 0 ) { printf("Error al hacer recvfrom"); exit(3); } else { memcpy(&n, buffer, sizeof(n)); /*copia el seq number recibido*/ if ((n==ftell(archivo))&&(n!=0)) printf("Duplicate Packet\n"); else fwrite(buffer+sizeof(udp_seq),1,(inbytes-sizeof(udp_seq)),archivo); printf("Writen till byte %d\n",n); printf("Bytes recibidos en el Payload UDP: %d\n",inbytes); /*Acknowledge*/ memcpy(&n, buffer, sizeof(n)); /*copia el seq number recibido*/ if (n==-1) { udp_seq = -1; /*le confirma el fin de archivo*/ } else Página 18 66.48 Seminario de Redes MOG udp_seq = ftell(archivo); memcpy(buffer,&udp_seq,sizeof(udp_seq)); sendto(s, buffer, sizeof(udp_seq), 0, (struct sockaddr *)&fsin, sizeof(fsin)); if (n==-1) { fclose(archivo); close(s); exit(1); } } } }/*fin de UDP SERVER*/ /*Parte del UDP client*/ if ( IS_UDP&&IS_CLIENT ) { s=socket(PF_INET,SOCK_DGRAM,0); /* Creo el socket UDP y s es el descriptor*/ if (s<0) printf("Error al crear el socket\n"); else printf("Se creo el socket\n"); memset(&serverendpoint,0,sizeof(serverendpoint)); serverendpoint.sin_family = AF_INET; serverendpoint.sin_port = htons(port); hp = gethostbyname(argv[3]); bcopy( hp->h_addr,(char *)&serverendpoint.sin_addr, hp->h_length); /* set address */ archivo = fopen(argv[5],"rb"); /*abre archivo para lectura*/ if (archivo==NULL) { printf("\nNo se pudo abrir el archivo %s\n",argv[5]); exit(2); } printf("Archivo Abierto exitosamente\n"); /*Armado del Payload de UDP*/ outbytes=0; outbytes = fread(buffer+(sizeof(udp_seq)),1,chunksize,archivo); udp_seq = ftell(archivo); memcpy(buffer,&udp_seq,sizeof(udp_seq)); outbytes +=sizeof(udp_seq); while(1) { if (0==sendto(s,buffer,outbytes,0,(struct sockaddr *)&serverendpoint,sizeof(serverendpoint))) printf("Error al Escribir \n"); /*variables del select*/ tv.tv_sec = 2; tv.tv_usec = 0; FD_ZERO(&rfds); FD_SET(s, &rfds); retval = select((s+1), &rfds, NULL, NULL, &tv); if (FD_ISSET(s, &rfds)) { alen = sizeof(sockin); inbytes = recvfrom(s, buffer, sizeof(buffer), 0, (struct sockaddr *)&fsin,&alen); memcpy(&n, buffer, sizeof(n)); /*copia el seq number recibido para verificar si debe retransmitir*/ printf("Remote Sequence number %d filepos %d\n",n, ftell(archivo)); if (n==-1) { fclose(archivo); close(s); printf("Archivo transferido exitosamente\n"); exit(1); } if (n==ftell(archivo)) { /*Armado del Payload de UDP*/ outbytes=0; if (!feof(archivo)) { outbytes = fread(buffer+(sizeof(udp_seq)),1,chunksize,archivo); udp_seq = ftell(archivo); } else udp_seq = -1; /*eof notification*/ memcpy(buffer,&udp_seq,sizeof(udp_seq)); outbytes +=sizeof(udp_seq); } } else { printf("Acknowledge expected within 2 second.\n"); printf("Remote Sequence number %d filepos %d\n",n, ftell(archivo)); } Página 19 66.48 Seminario de Redes MOG }; }/*fin de UDP CLIENT*/ exit(0); }/*fin de main*/ Página 20