Designing a Client/Server application • Communication model: connection oriented/connectionless – Do we need a notion of a session? • Number of client sessions: Sequential vs. Concurrent server – IPC – synchronous vs. asynchronous • Naming: – IP address and port number • • • • Data format Byte order: big-endian/little-endian Socket options Service protocol – rules within a session – Naming a service: services identify themselves via a registered logical name or the server physical process address (machine name + port number) – Communication sequence – Data representation • Do I want to store communication state? – Stateful vs. stateless At a high level it depends on… • • • • • • • • Reliability: order of delivery, delivery guarantee, etc Error detection Transmission speed Data bandwidth required Service time Flow control Congestion control … E.g.: TCP • Optimized for accurate delivery rather than timely delivery • Reliable, ordered delivery – E.g. HTTP, FTP, Telnet – IP packets may be lost/duplicated/delivered out of order: TCP responds by requiring retransmission/ rearranging data/minimizing congestion • May be slow – Bad for real-time apps, high bandwidth apps, embedded systems, etc Connection establishment in TCP Sender waits for ACK before sending next packet Connection termination in TCP • 4-way handshake: – FIN/ACK on both sides – Connection may be half-open: terminating side may not send but must continue reading until the other side terminates as well! • 3-way handshake: – FIN/FIN&ACK/ACK TCP vs UDP • Ordered data transfer based on sequence number • Retransmission of non-ACK packages • Error-free data transfer (checksum in UDP optional) • Flow control limits rate of sender to guarantee reliable delivery: receiver hints sender via sliding window size • Congestion control: ACK and timers, estimated round-trip time (+variance) Comparison between different protocols IP UDP TCP Connexion oriented? No No Yes Self-contained? Si Si No Ack? No No Yes Timeout and retransmission? No No Yes Detecting duplicates? No No Yes Packet sequencing? No No Yes Congestion control? No No Yes Sequential servers • Serves client requests sequentially • Does not interleave requests from multiple client sessions • While attending to a client seesion all others must wait request Server Client reply TCP client/server Server process socket() Client process bind() listen() socket() Connexion accept() connect() Request write() Reply read() read() write() close() close() Proceso servidor socket() Proceso cliente TCP Server #include <sys/types.h> #include <sys/socket.h> void main(int argc, char *argv[]) { struct sockaddr_in server_addr, int sd, sc; int size, val; int size; int num[2], res; bind() listen() socket() Conexión connect() write() Petición Respuesta accept() read() read() write() close() close() client_addr; if ((sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))<0) printf (“SERVER: Error socket”); val = 1; setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(int)); bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(4200); // assign address to socket bind(sd, &server_addr, sizeof(server_addr)); Proceso servidor socket() Proceso cliente TCP Server listen(sd, 5); size = sizeof(client_addr); bind() listen() socket() Conexión connect() write() Petición Respuesta accept() read() read() write() close() close() while (1) { printf(“wait for connection\n"); sc = accept(sd, (struct sockaddr *)&client_addr, &size); read ( sc, (char *) num, 2 *sizeof(int)); // receive request res = num[0] + num[1]; // process request this } write(sc, &res, sizeof(int)); // send result close(sc); // close connection (sc) – // application specific!! session ends b/c the protocol says close (sd); exit(0); } /*end main */ Proceso servidor socket() Proceso cliente TCP Client #include <sys/types.h> #include <sys/socket.h> bind() listen() socket() Conexión connect() write() Petición Respuesta accept() read() read() write() close() close() void main(int argc, char **argv) // en argv[1] == servidor { int sd; struct sockaddr_in server_addr; struct hostent *hp; int num[2], res; if (argc != 2){ printf("Uso: cliente <direccion_servidor> \n"); exit(0); } sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); bzero((char *)&server_addr, sizeof(server_addr)); hp = gethostbyname (argv[1]); memcpy (&(server_addr.sin_addr), hp->h_addr, hp->h_length); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(4200); Proceso servidor socket() Proceso cliente TCP Client bind() listen() socket() Conexión connect() write() Petición Respuesta read() accept() read() write() // establish connection connect(sd, (struct sockaddr *) &server_addr, sizeof(server_addr)); close() num[0]=5; num[1]=2; write(sd, (char *) num, 2 *sizeof(int)); // send request read(sd, &res, sizeof(int)); // receive reply printf("Result is %d \n", res); close (sd); exit(0); } /* end main */ close() UDP client/server Server process socket() Client process bind() socket() Request sendto() recvfrom() Reply recvfrom() close() sendto() close() Proceso servidor socket() UDP Server Servidor UDP #include <sys/types.h> #include <sys/socket.h> Proceso cliente bind() socket() Petición sendto() recvfrom() Respuesta recvfrom() close() sendto() close() void main(void) { int num[2]; int s, res, clilen; struct sockaddr_in server_addr, client_addr; s = socket(AF_INET, SOCK_DGRAM, 0); bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(7200); bind(s, (struct sockaddr *)&server_addr, sizeof(server_addr)); Proceso servidor socket() UDP Server Servidor UDP clilen = sizeof(client_addr); Proceso cliente bind() socket() Petición sendto() recvfrom() close() while (1) { recvfrom(s, (char *) num, 2* sizeof(int), 0, (struct sockaddr *)&client_addr, &clilen); res = num[0] + num[1]; sendto(s, (char *)&res, sizeof(int), 0, (struct sockaddr *)&client_addr, } } /* end main */ recvfrom() Respuesta clilen); sendto() close() Proceso servidor socket() UDP Client void main(int argc, char *argv[]) { struct sockaddr_in server_addr, client_addr; struct hostent *hp; int s, num[2], res; Proceso cliente bind() socket() Petición sendto() recvfrom() Respuesta recvfrom() close() if (argc != 2){ printf("Use: client <server_address> \n"); exit(0); } s = socket(AF_INET, SOCK_DGRAM, 0); hp = gethostbyname (argv[1]); bzero((char *)&server_addr, sizeof(server_addr)); memcpy (&(server_addr.sin_addr), hp->h_addr, hp->h_length); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(7200); sendto() close() Proceso servidor socket() UDP Client bzero((char *)&client_addr, sizeof(client_addr)); client_addr.sin_family = AF_INET; client_addr.sin_addr.s_addr = INADDR_ANY; client_addr.sin_port = htons(0); Proceso cliente bind() socket() Petición sendto() recvfrom() close() bind (s, (struct sockaddr *)&client_addr, sizeof(client_addr)); num[0] = 2; num[1] = 5; sendto(s, (char *)num, 2 * sizeof(int), 0, (struct sockaddr *) &server_addr, sizeof(server_addr)); recvfrom(s, (char *)&res, sizeof(int), 0, NULL, NULL); printf("2 + 5 = %d\n", res); close(s); } recvfrom() Respuesta sendto() close() Possible issues • Error detection • Byte order when transferring data – What if client is little-endian and server big-endian? – Data format: • Data sent onto the network must be in Network Byte Order • Data received from the network must be in Host Byte Order – May decide to always run the value through a function to set it to network byte order! u_long u_short u_long u_short htonl(u_long htons(u_short ntohl(u_long ntohs(u_short hostlong) – host to network! hostshort) netlong) – network to host! netshort) Concurrent servers • Server creates a child process which will process the request and send the reply to the client • Multiple client sessions may be interleaved request Server Client Create child process reply Child process Concurrent server with stream sockets Server process socket() Client process bind() listen() socket() Connection connect() accept() Create child thread Request write() read() close() read() Reply write() close() Concurrent server with DGRAM sockets Server process socket() Client process bind() socket() Request sendto() recvfrom() Create child thread Reply recvfrom() close() sendto() close() Concurrent servers • May not only create a child thread… • but may instead create a process (via fork) and use any IPC mechanism Concurrent processes using fork Server creates socket s and associates an address Server: for(;;) { sd = accept(s, (struct sockaddr *)&cliente, &len); pid = fork(); /* child inherits socket descriptor */ if (pid == -1) printf(“Can’t create more children \n”); else if (pid == 0)/* child process */ { close (s); tratar_peticion(sd); close(sd); exit(0); } close(sd); /* parent */ } Concurrent processes using fork • Parent process does not wait for children to finish executing – Child processes remain zombie – To avoid that in UNIX System V one can call in parent process: signal(SIGCHLD, SIG_IGN); Concurrent servers with threads request server Client Create thread reply Work thread Concurrent processes using threads • Server creates socket s and associates an address • Server: pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); for(;;) { sd = accept(s, (struct sockaddr *)& &cliente, &len); pthread_create(&thid, &attr, tratar_peticion, &sd); } • The thread executes: void tratar_peticion(int * s) { int s_local; } s_local = *s; /* tratar la petición utilizando el descriptor de s_local */ close(s_local); pthread_exit(NULL); • Is this correct? Synchronization! • Parent and child processes have a race condition on sd returned by accept – Child may use sd in tratar_petición while a new sd is assigned in accept • Need synchronization with mutex and condition variables! Synchronization! • Main thread: for(;;) { sd = accept(s, (struct sockaddr *)& &cliente, &len); pthread_create(&thid, &attr, tratar_peticion, &sd); /* esperar a que el hijo copie el descriptor */ pthread_mutex_lock(&m); while(busy == TRUE) pthread_cond_wait(&m, &c); pthread_mutex_unlock(&m); busy = TRUE; } • Child thread: void tratar_peticion(int * s) { int s_local; pthread_mutex_lock(&m); s_local = *s; busy = FALSE; pthread_cond_signal(&c); pthread_mutex_unlock(&m); /* tratar la petición utilizando el descriptor s_local */ pthread_exit(NULL); } SW architecture • Usually consists of three layers/tiers: – Presentation: user interface issues – Application logic: isolates data processing in one location and maximizes reuse, modification in services does not affect presentation • Server needs to process client request, compute result and return it to client • Client needs to send service request and visualise result – Services - we need two types: • On server - those processing the request • Some IPC mechanism! • Must be able to manage data • May seem similar but different from MVC architecture! – View sends updates to controller; controller updates the model, view gets updated directly from model – Model = data +domain logic (+persistence, notification) – View = query model, render view – Controller = init model, wiring up events between controller and V/M Application Tasks User Interface Presentation Logic Application Logic Data Requests & Results Physical Data Management E.g.: Web apps • Break app into logical chunks (layers) • Layers may exist on same / different computer • 1-tier: aka an application on a PC, contains user interface, business logic, data storage. – Do not scale – Not on network! Can’t use them for web apps • 2-tier: web browser and web server – Separates user interface from data layer – Does not separate application layers: hard to reuse, specialize, modify • 3-tier: browser = client, middleware/app server = business logic, database server = data functions • If need to specialize functional layers: N-tier! – Scalable, cost-efficient – Apps more readable and reusable – No single point of failure! Robust. Client using Java DGRAM sockets import java.lang.* ; import java.io.* ; import java.net.* ; import java.util.* ; public class client{ public static void main ( String [] args) { byte bsend[] = new byte[100]; byte brecv[] = new byte[100]; InetAddress server_addr = null; DatagramSocket s = null; DatagramPacket in = null; DatagramPacket out = null; int res; int num[] = new int[2]; if (args.length != 1) { System.out.println("Uso: cliente <host>"); System.exit(0); } Client using Java DGRAM sockets try { // se crea el socket del cliente s = new DatagramSocket(); // direción del servidor server_addr = InetAddress.getByName(args[0]); num[0] = 2; num[1] = 5; // empaquetar los datos. ByteArrayOutputStream baos = new ByteArrayOutputStream() ; ObjectOutputStream dos = new ObjectOutputStream(baos); dos.writeObject(num); bsend = baos.toByteArray() ; // se obtiene el buffer (datagrama) // un único envio out = new DatagramPacket (bsend, bsend.length, server_addr, 2500); s.send(out); Client using Java DGRAM sockets // se recibe el datagrama de respuesta in = new DatagramPacket (brecv, 100); s.receive(in); // se obtiene el buffer brecv = in.getData(); // se desempaqueta ByteArrayInputStream bais = new ByteArrayInputStream(brecv) ; DataInputStream dis = new DataInputStream(bais); res = dis.readInt(); System.out.println("Datos recibidos " + res); } catch (Exception e) { System.err.println("<<<<<excepcion " + e.toString() ); e.printStackTrace() ; } } } Server using Java DGRAM sockets import import import import java.lang.* ; java.io.* ; java.net.* ; java.util.* ; public class servidor { public static void main ( String [] args) { DatagramSocket s = null; DatagramPacket in, out; InetAddress client_addr = null; int client_port; byte brecv[] = new byte[100]; byte bsend[] = new byte[100]; int num[], res; try { s = new DatagramSocket(2500); // port nr in = new DatagramPacket(brecv, 100); // paquete para recibir la solicitud Server using Java DRAM sockets while (true) { s.receive(in); //esperamos a recibir // obtener datos brecv = in.getData(); client_addr = in.getAddress(); client_port = in.getPort(); // desempaquetar los datos ByteArrayInputStream bais = new ByteArrayInputStream(brecv); ObjectInputStream dis = new ObjectInputStream(bais); num = (int[])dis.readObject(); res = num[0] + num[1]; Server using Java DGRAM sockets ByteArrayOutputStream baos = DataOutputStream dos = new ByteArrayOutputStream(); new DataOutputStream(baos); dos.writeInt(res); bsend = baos.toByteArray(); out = new DatagramPacket ( bsend, bsend.length,client_addr, client_port); s.send(out); } } catch(Exception e) { System.err.println("excepcion " + e.toString() ); e.printStackTrace() ; } } } Server using Java stream sockets import import import import java.lang.* ; java.io.* ; java.net.* ; java.util.* ; public class servidor { public static void main ( String [] args) { ServerSocket serverAddr = null; Socket sc = null; int num[] ; // petición int res; try { serverAddr = new ServerSocket(2500); } catch (Exception e){ System.err.println("Error creando socket"); } Server using Java stream sockets while (true){ try { sc = serverAddr.accept(); // esperando conexión InputStream istream = sc.getInputStream(); ObjectInput in = new ObjectInputStream(istream); num = (int[]) in.readObject(); res = num[0] + num[1]; DataOutputStream ostream = new DataOutputStream(sc.getOutputStream()); ostream.writeInt(res); ostream.flush(); sc.close(); } catch(Exception e) { System.err.println("excepcion " + e.toString() ); e.printStackTrace() ; } } } } Client using Java stream sockets import import import import java.lang.* ; java.io.* ; java.net.* ; java.util.* ; public class client { public static void main ( String [] args) { int res; int num[] = new int[2]; if (args.length != 1) { System.out.println("Uso: cliente <host>"); System.exit(0); } try { // se crea la conexión String host = args[0]; Socket sc = new Socket(host, 2500); // conexión Client using Java stream sockets OutputStream ostream = sc.getOutputStream(); ObjectOutput s = new ObjectOutputStream(ostream); DataInputStream istream = new DataInputStream(sc.getInputStream()); num[0] = 5; num[1] = 2; //prepara la petición s.writeObject(num); s.flush(); res = istream.readInt(); sc.close(); System.out.println("La suma es " + res); } catch (Exception e){ System.err.println("excepcion " + e.toString() ); e.printStackTrace() ; } } } Concurrent server using Java stream sockets while (true){ try { Socket cliente = serverAddr.accept(); new TratarPeticion(cliente).start(); } catch(Exception e) { System.err.println("excepcion " + e.toString() ); e.printStackTrace() ; } } } } Concurrent server using Java stream sockets class TratarPeticion extend Thread { private Socket sc; TratarPeticion(Socket s) { sc = s; } public void run() { // TODO: código del cliente }