1.14k likes | 1.54k Views
Client/Server Distributed Systems. 240-322, Semester 1, 2005-2006. Objectives introduce sockets look at iterative servers (using TCP) look at clients (using TCP). 7. Sockets (1) Chapter 4, Brown. Overview. 1. Socket Definition 2. Socket Data Structures
E N D
Client/Server Distributed Systems 240-322, Semester 1, 2005-2006 • Objectives • introduce sockets • look at iterative servers (using TCP) • look at clients (using TCP) 7. Sockets (1)Chapter 4, Brown
Overview 1. Socket Definition 2. Socket Data Structures 3. Connection-oriented Services 4. Iterative Servers 5. Clients 6. Clients of System Services 7. More Details
1. Socket Definition • A socket is an ‘end-point’ for communication at the transport layer. • for TCP, a socket is like a telephone • for UDP, a socket is like a mailbox • There is a socket at each end of the communication: • one used by the client, one used by the server
2. Socket Data Structures • There are several kinds of data structure for sockets, depending on their use: • as end-points between processes on the same machine • very rarely used • as end-points between networked processes
Networked Socket Data Structures struct sockaddr_in { short sin_family; /* AF_INET in IPv4*/ u_short sin_port; /* port no. */ struct in_addr sin_addr; /* IP address */ char sin_zero[8]; /* padding of O’s */};struct in_addr { u_long s_addr; /* nastier in SunOS */}; • Details in /usr/include/netinet/in.h continued
Due to the two possible uses of sockets, most functions require that sockets be cast into a ‘generic’ socket data type: struct sockaddr { u_short sa_family; char sa_data[14];} • see /usr/include/sys/socket.h
Socket Types • Many socket functions also require a socket type: • SOCK_STREAM • for connection-oriented transport, e.g. TCP • SOCK_DGRAM • for connectionless transport, e.g. UDP • SOCK_RAW • others
3. Connection-oriented Servers • We will use TCP for connection-oriented communication. • Two basic kinds of server: • iterative: the server deals with one client at once • concurrent: the server can deal with multiple clients at once
Basic client-server Operation Server dealt with by my tcp_serv_sock() Create socket with socket() Client Initialise serveraddress details Initialise socket address data structure Bind server address to socket with bind() Create socket with socket() Listen for connections with listen() Connect to server’sport with connect() blocks until connection from a client data (request) read() write() process request data (reply) read() write()
3.1. tcp_serv_sock() • My function which hides the first 4 stages of the server’s operation: • contains calls to socket(), bind() and listen() • The binding of the server’s address to the socket (stage 3) requires a sockaddr_in struct to be initialised.
sin_family sin_port sin_addr sin_zero[8] Initialising a sockaddr_in struct for a server serv_addr AF_INET htons(port) htonl(INADDR_ANY) pad with 0’s continued
Notes • The port number must be > 1023 if the server is user-defined. • INADDR_ANY means that the socket will work with all the network interfaces (i.e. network cards) on the machine. • nothing to do with client addresses
tcp_serv_sock() int tcp_serv_sock(int port){ int sockfd; struct sockaddr_in serv_addr; /* create a TCP socket */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Could not open a socket"); exit(1); } : continued
/* initialise socket address data struct */ memset(&serv_addr, 0, sizeof(serv_addr)); /* bzero((char *)&serv_addr, sizeof(serv_addr)); */ serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(port); : continued
/* bind socket to address */ if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { fprintf(stderr, "Could not bind socket to address\n"); exit(1); } /* Listen for incoming connections */ if (listen(sockfd, 5) < 0) { fprintf(stderr, "listen error\n"); exit(1); } return sockfd;}
3.1.1. socket() • Create a socket:#include <sys/types.h>#include <sys/socket.h>int socket(int family, int type, int protocol); • Return a socket descriptor if ok, -1 on error. continued
family is one of: • AF_INET, AF_UNIX, and others • type is one of: • SOCK_STREAM, SOCK_DGRAM, and others • If protocol is 0 it means: • “let the system choose the protocol based on the supplied family and type information”
3.1.2. bind() • Bind an address to a socket:#include <sys/types.h>#include <sys/socket.h>int bind(int sockfd, struct sockaddr *myaddr, int addrlen); • Returns 0 if ok, -1 on error. • For more info. use “man 2 bind”
3.1.3. listen() • Listen for incoming connections:#include <sys/types.h>#include <sys/socket.h>int listen(int sockfd, int backlog); • Returns 0 if ok, -1 on error. continued
backlog is how many connection requests from clients can be queued by the system while it waits for the server to execute an accept() call.
4. An Iterative Server • An iterative server which uses a TCP connection can now be outlined: • it sets up its socket using tcp_serv_sock() • it uses accept() to accept a connection • it calls the user-defined do_something() to do the server task • when do_something() is finished, it can accept another connection
Outline Code #define PORT 1666int main(){ int rdsock, cdsock; : rdsock = tcp_serv_sock(PORT); while(1) { cdsock = accept(rdsock, ...); if (cdsock < 0) fprintf(stderr, “accept failed\n”); else {do_something(cdsock, ...); close(cdsock); } } return 0;}
4.0.1. accept() • Accept a client connection:#include <sys/types.h>#include <sys/socket.h>int accept(int sockfd, struct sockaddr *peer, int *addrlen); • Returns a socket descriptor if ok, -1 on error. continued
accept() removes the first connection request from the listen queue attached to rdsock. • accept() returns a new socket (cdsock) for 1-1 communication between the server and client. • rdsock = rendezvous socket descriptorcdsock = connection socket descriptor continued
peer is usually a pointer to a sockaddr_in struct (cast to sockaddr *) • peer is assigned details about the accepted client • The value in addrlen changes: • its input value is the size of the sockaddr_in struct • its output value is the actual size of the peer details
Code Fragment struct sockaddr_in client;int client_len; :client_len = sizeof(client);cdsock = accept(rdsock, (struct sockaddr *)&client, &client_len); :
4.0.2. do_something() • The server-specific functionality: • e.g. “hangman” game code • It will use read() and write() to communicate with the client through cdsock. • The client and server must agree on the order of their read() and write()’s. continued
The communication possible with read() and write()’s is a stream of characters (bytes): • the client and server must agree on how a ‘message’ is represented by a sequence of characters (bytes) • e.g. use a ‘$’ or ‘\n’ to end a ‘message’
4.1. Example: A Daytime Server • The daytime server returns the current time on the server when requested by a client. • It runs on takasila, listening at port 1666 • 1666 is not used in /etc/services • It expects no input from the client. It outputs the time and then closes the connection.
day_serv.c #include <stdio.h>#include <string.h>/* #include <bstring.h> */#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <time.h> /* time funcs */#include <unistd.h>#define PORT 1666int tcp_serv_sock(int port); : continued
int main(){ int rdsock, cdsock, client_len; struct sockaddr_in client; time_t now; char *now_str; rdsock = tcp_serv_sock(PORT); while(1) { client_len = sizeof(client); cdsock = accept(rdsock, (struct sockaddr *)&client, &client_len); : continued
if (cdsock < 0) fprintf(stderr, “accept failed\n”); else {time(&now); now_str = ctime(&now); write(cdsock, now_str, strlen(now_str)); close(cdsock); } } return 0;} application specific
Running day_serv.c Sun OS also requires:-lnsl -lsocket on takasila $ gcc -Wall -o day_serv day_serv.c$ ./day_serv &[1] 1701$ netstat -a | grep 1666$ ...$ kill -9 1701$ netstat -a | grep 1666/* port will not be released immediately */
Using day_serv • Use telnet to open a connection:$ telnet takasila 1666Trying 172.30.0.82...Connected to takasila.coe.psu.ac.th.Escape character is '^]'.Wed May 4 11:15:34 2005Connection closed by foreign host.$ I did this from fivedots
4.2. Example: An Echo Server • The echo server reads the lines sent by the client, and echoes them back, converted into upper case. • A line ends with ‘\n’, and no line is longer than 128 characters. • An empty line terminates the connection. • The server runs on takasila, listening at port 1667.
echo_serv.c #include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <unistd.h>#include <ctype.h> /* for toupper() */#define PORT 1667#define BUFSIZE 128int tcp_serv_sock(int port); : continued
int main(){ int rdsock, cdsock, client_len; struct sockaddr_in client; rdsock = tcp_serv_sock(PORT); while(1) { client_len = sizeof(client); cdsock = accept(rdsock, (struct sockaddr *)&client, &client_len); : continued
if (cdsock < 0) fprintf(stderr, “accept failed\n”); else {echo_upper(cdsock); close(cdsock); } } return 0;} application specific part continued
void echo_upper(int sd){ char buf[BUFSIZE]; int n; while (((n = read(sd, buf, sizeof(buf)) != 0) && (!end_input(buf, n))) if (n < 0) fprintf(stderr, “echo read error\n”); else {/* buf[n] = ‘\0’; printf(“n: %d, buf: \”%s\”\n”, n, buf);*/ touppers(buf); if (write(sd, buf, n) < 0) fprintf(stderr, “echo write error\n”); }} continued
void touppers(char buf[]){ int i = 0; while (buf[i] != ‘\n’) { buf[i] = toupper(buf[i]); i++; }}
int end_input(char buf[], int len)/* Check if buf[] contains the characters meaning end-of-input for this server. Return 1 if true.*/{ if ((len == 2) && (buf[0] == ‘\015’) && (buf[1] == ‘\n’)) return 1; if ((len == 1) && (buf[0] == ‘\n’)) return 1; return 0;}
Three Input Termination Cases • The client socket can close due to an error: • read() returns < 0 • The client socket can be closed by the client: • read() returns 0 • The client can send a newline only: • test with end_input() • check for telnet output (and other programs) which includes a carriage return (‘\015’) before the newline.
Running echo_serv.c on takasila $ gcc -Wall -o echo_serv echo_serv.c$ ./echo_serv &[1] 1944$ netstat -a | grep 1667$ ...$ kill -9 1944$ netstat -a | grep 1667/* port will not be released immediately */
Using echo_serv • Use telnet to open a connection:$ telnet takasila 1667Trying 172.30.0.82...Connected to takasila.coe.psu.ac.th.Escape character is '^]'.helloHELLOgoodbyeGOODBYEConnection closed by foreign host.$ on fivedots I typed a newline.
4.3. Problems with read() / write() • Over a network, read() / write() may not be able to read / write all the requested chars • due to bandwidth restrictions • due to network load e.g. n1 = read(sd, buf, 1023);n1 assigned 1000 continued
Must call read() / write() again until the requested number of characters have been obtained. • Sometimes reading in the server should stop when a special character is read (e.g. a ‘\n’). continued
One problem: • the code cannot rely on a ‘termination’ character (e.g. ‘\n’) being the last character • another separate message may follow it in the socket queue • reading N characters can read all of the first message and some of the next one queue sd a b c \n a b c d e \n read(sd, buf, 5); buf is assigned "a b c \n a" continued
The problems continues: • a message may be split into smaller packets at the IP level • e.g. the message sent as "abcde\n" arrives as two packets "abc" and "de\n" • the read() call only gets the first packet, without a '\n' queue sd a b c d e \n read(sd, buf, 6); buf is assigned "a b c"
4.3.1. readline() • User-defined function that reads a stream of characters upto, and including, a ‘\n’. • It adds a ‘\0’ like fgets(), so that string functions can use the line directly. • readline() uses read() to read one character (byte) at a time • very slow
int readline(int sd, char *buf, int maxlen){ int n, rch; char ch; for (n=1; n < maxlen; n++) { if ((rch = read(sd, &ch, 1)) == 1) { *buf = ch; buf++; if (ch == ‘\n’) break; } else if (rch == 0) { if (n == 1) return 0; /* EOF, no data read */ else break; /* EOF, some data read */ } else return -1; /* error */ } *buf = ‘\0’; return n;}