Designing a Client/Server application

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