CS252-Slides-2015-to..

advertisement
CS252: Systems Programming
Ninghui Li
Based on slides by Prof. Gustavo Rodriguez-Rivera
Topic 16: Socket Programming & Project 5
Sockets API
They were introduced by UNIX BSD (Berkeley
Standard Distribution).
Sockets are a form of IPC (Inter-Process
Communications)
Sockets families
UNIX (AF_UNIX)
Between processes on the same host
IPv4 (AF_INET)
Using IPv4 to communicate
IPv6 (IF_INET6)
Sockets API
Sockets with IPv4 provide a standard API for
programming using TCP/IP.
A program that uses sockets can be easily
ported to other OS’s that implement sockets.
Socket types
Stream (think online chatting session, TCP): reliable
delivery (know delivery status), connection-oriented
(existence of a peer socket), no message boundary
Datagram (think SMS messages, UDP): no reliable
delivery, not connection-oriented, has message
boundary
TCP Connections
A connection is defined uniquely in the entire Internet by
five values:

<protocol, src-ip-addr, src-port, dest-ip-addr, dest-port>


Example: A runs an HTTP server in port 80
B connects to A’s HTTP server using source port 5000


C connects also to A’s HTTP server using source port 8000


The connection is <TCP, IPC, 8000, IPA, 80>
Another browser in B using port 6000 connects to A


The connection is <TCP, IPB, 5000, IPA, 80>
The connection is <TCP, IPB, 6000, IPA, 80>
Another browser in C using port 5000 connects to A

The connection is <TCP, IPC, 5000, IPA, 80>
The OS uses these values to know what data corresponds to
what application/socket.

Sockets API
Most networking applications are written using
sockets: web browser/server, FTP client/server,
mail, p2p file sharing, finger, DNS etc.
Many protocols take the form of a conversation
between client and server using ASCII Text.
E.g., you can telnet to a WWW server, a mail
server, etc.
Using Socket API for TCP
Programming With Sockets
Client Side
int cs =socket(AF_INET, SOCK_STREAM, proto)
…
connect (cs, addr, sizeof(addr))
…
write(cs, buf, len)
read(cs, buf, len);
close(cs)
Programming With Sockets
Server Side
…
int masterSocket = socket(AF_INET, SOCK_STREAM, 0);
…
int error = bind( masterSocket, (struct sockaddr *)&serverIPAddress, sizeof(serverIPAddress) );
…
error = listen( masterSocket, QueueLength);
…
while ( 1 ) {
…
int slaveSocket = accept( masterSocket,
(struct sockaddr*)&clientIPAddress, (socklen_t*)&alen);
read(slaveSocket, buf, len);
write(slaveSocket, buf, len);
close(slaveSocket);
}
Socket Creation in C: socket
int s = socket(domain, type, protocol);
where
s: socket descriptor, an integer (like a file descriptor)
domain: integer, communication domain
e.g., AF_INET (IPv4 protocol)
type: communication type
SOCK_STREAM: reliable, 2-way, connection-based service
SOCK_DGRAM: unreliable, connectionless
protocol: e.g., TCP or UDP
use IPPROTO_TCP or IPPROTO_UDP, or getproctobyname(“tcp”) to
send/receive TCP or UDP packets
Client
create socket and connect to remote host
The client connects to the remote host
The connect function is used by a client program to establish communication with a
remote entity
int status = connect(sock, &servaddr, addrlen);
where
status:
return value, 0 if successful connect, -1 otherwise
sock:
client’s socket to be used in connection
servaddr:
server’s address structure
addrlen:
size (in bytes) of the servaddr structure
connect is blocking
Example code:
if(connect(hSocket, (struct sockaddr*) &Address, sizeof(Address)) == -1)
{
printf("\nCould not connect to host\n");
}
Socket Addresses
#include <netinet/in.h>
// All pointers to socket address structures are often cast to pointers
// to this type before use in various functions and system calls:
struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
Socket Addresses
// IPv4 AF_INET sockets:
struct sockaddr_in {
short sin_family; // e.g. AF_INET, AF_INET6
unsigned short sin_port; // e.g. htons(3490)
struct in_addr sin_addr; // see struct in_addr, below
char sin_zero[8];
// zero this if you want to
};
struct in_addr {
unsigned long s_addr; // 32 bit unsigned integer
};
Getting Internet Addresses
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
Getting Internet Addresses
in_addr_t inet_addr(const char* string)
returns the 32-bit IP address that corresponds to the A.B.C.D
format string. The IP address is in network-byte order.
If no error occurs, inet_addr returns an unsigned long value
containing a suitable binary representation of the Internet
address given. If the string in the cp parameter does not
contain a legitimate Internet address, for example if a portion
of an "a.b.c.d" address exceeds 255, then inet_addr returns
the value INADDR_NONE.
Getting Protocol Name
#include <netdb.h>
struct protoent *getprotobyname(const char *name);
struct protoent {
char *p_name; /* official protocol name */
char **p_aliases; /* alias list */
int p_proto; /* protocol number */
}
E.g.
struct protoent *ptrp = getprotobyname("tcp");
int sock = socket(PF_INET, SOCK_STREAM, ptrp->p_proto);
Byte Ordering
Network uses big endian
Intel CPU uses little endian
Translation between Network and
Host Byte Ordering
in_addr_t htonl(in_addr_t hostLong)
in_addr_t htons(in_port_t hostShort)
in_addr_t ntohl(in_addr_t networkLong)
in_addr_t ntohs(in_port_t networkShort)
each of these functions performs a conversion
between a host-format number and a networkformat number.
Using read/write and/or send/recv
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t write(int fd, const void *buf, size_t count);
send(…) with flags==0 equivalent to write(…)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t read(int fd, void *buf, size_t count);
recv(…) with flags==0 equivalent to read(…)
Client for Daytime Server
//------------------------------------------------------------------------
// Program:
client
//
// Purpose:
allocate a socket, connect to a server, and print all output
//
// Syntax:
client host port
//
//
host - name of a computer on which server is executing
//
port - protocol port number server is using
//
//-----------------------------------------------------------------------#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
Client for Daytime Server
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
void printUsage()
{
printf( "Usage: client <host> <port> <name>\n");
printf( "\n");
printf( "
host: host where the server is running.\n");
printf( "
port: port that the server is using.\n");
printf( "\n");
printf( "Examples:\n");
printf( "\n");
printf( "
client localhost 422422\n");
printf( "
client lore 1345\n");
printf( "\n");
}
Client for Daytime Server
int main(int argc, char **argv)
{
// Check command-line argument for protocol port and extract
// host and port number
if ( argc < 4 ) {
printUsage();
exit(1);
}
char * host = argv[1];
// Extract host name
int port = atoi(argv[2]); // Extract port number
char * name = argv[3];
// Initialize socket address structure
struct sockaddr_in socketAddress;
// Clear sockaddr structure
memset((char *)&socketAddress,0,sizeof(socketAddress));
Client for Daytime Server
socketAddress.sin_family = AF_INET; // Set family to Internet
if (port > 0) {
// Test for port legal value
socketAddress.sin_port = htons((u_short)port);
} else {
fprintf(stderr,"bad port number %s\n", argv[2]);
exit(1);
}
// Get host table entry for this host
struct hostent *ptrh = gethostbyname(host);
if ( ptrh == NULL ) {
fprintf(stderr, "invalid host: %s\n", host);
perror("gethostbyname");
exit(1);
}
// Copy the host ip address to socket address structure
memcpy(&socketAddress.sin_addr, ptrh->h_addr, ptrh->h_length);
Client for Daytime Server
// Get TCP transport protocol entry
struct protoent *ptrp = getprotobyname("tcp");
if ( ptrp == NULL ) {
fprintf(stderr, "cannot map \"tcp\" to protocol number");
perror("getprotobyname");
exit(1);
}
// Create a tcp socket
int sock = socket(PF_INET, SOCK_STREAM, ptrp->p_proto);
if (sock < 0) {
fprintf(stderr, "socket creation failed\n");
perror("socket");
exit(1);
}
Client for Daytime Server
// Connect the socket to the specified server
if (connect(sock, (struct sockaddr *)&socketAddress,
sizeof(socketAddress)) < 0) {
fprintf(stderr,"connect failed\n");
perror("connect");
exit(1);
}
// In this application we don't need to send anything.
// For your HTTP client you will need to send the request
// as specified in the handout using send().
int m = read(sock,buffer,100);
buffer[m]=0;
printf("buffer=%s\n", buffer);
write(sock, name, strlen(name));
write(sock,"\r\n",2);
Client for Daytime Server
// Receive reply
// Data received
char buf[1000];
int n = recv(sock, buf, sizeof(buf), 0);
while (n > 0) {
// Write n characters to stdout
write(1,buf,n);
// Continue receiving more
n = recv(sock, buf, sizeof(buf), 0);
}
close(sock); // Close the socket.
// Terminate the client program gracefully.
exit(0);
}
The bind function
associates a port for use by the socket
int status = bind(sock, &addrport, size)
where
status: return status, 0 if successful, -1 otherwise
sock:
socket being used
addrport: address structure
This C structure has four parts to it (next slide)
size:
the size (in bytes) of the addrport structure
Bind is non-blocking: returns immediately
Connection setup: listen & accept
The listen function prepares a bound socket to accept incoming
connections
int status = listen(sock, queuelen)
where
status:
return value, 0 if listening, -1 if error
sock:
socket being used
queuelen: number of active participants that can “wait” for a connection
listen is non-blocking: returns immediately
Example code:
if (listen(hServerSocket, 1) == -1) {
printf("\nCould not listen\n");
return -1;
}
Connection setup: listen & accept
Use the accept function to accept a connection request from a remote host
The function returns a socket corresponding to the accepted connection
int s = accept(sock, &cliaddr, &addrlen)
where
s:
new socket used for data-transfer
sock:
original socket being listened on (e.g., server)
cliaddr: address structure of the active participant (e.g., client)
The accept function updates/returns the sockaddr structure with the client's address
addrlen: size (in bytes) of the client sockaddr structure
The accept function updates/returns this value
accept is blocking: waits for connection before returning
Example code:
hSocket = accept(hServerSocket, (struct sockaddr *) &Address, (socklen_t *) &nAddressSize);
/* socklen_t is socket address length type, defined in sys/socket.h; in our example code it is
being cast from a pointer to an integer */
Sending / Receiving Data
Send data
int count = send(int sock, const void * msg, int len, unsigned int falgs);
Where:
count: number of bytes transmitted (-1 if error)
sock: socket being used
buf:
buffer to be transmitted
len:
length of buffer (in bytes) to transmit
flags: special options, usually just 0
Receive data
int count = recv(int sock, void *buf, int len, unsigned int flags);
Where:
count: number of bytes received (-1 if error)
sock: socket being used
buf:
stores received bytes
len:
length of buf
flags: special options, usually just 0
Example (Client/Server)
…
// write a message to the server
// after the client executed a write(), it will read
n = send(sock,buffer,strlen(buffer),0);
n = recv(newsock,buffer,255,0);
// do some processing
…
// do some processing
…
// send the result to the client
// read a message from the server
n = recv(sock,buffer,255,0);
CLIENT
n = send(newsock,resp_msg,strlen(resp_msg),0);
SERVER
close
When finished using a socket, the socket
should be closed:
status = close(s);
status: return value, 0 if successful, -1 if error
s: the file descriptor (socket being closed)
Daytime Server
const char * usage =
"
"daytime-server:
"
"Simple server program that shows how to use socket calls
"in the server side.
"
"To use it in one window type:
"
"
daytime-server <port>
"
"Where 1024 < port < 65536.
\n"
"
"In another window type:
"
"
telnet <host> <port>
"
"where <host> is the name of the machine where daytime-server
"is running. <port> is the port number you used when you run
"daytime-server.
"
"Then type your name and return. You will get a greeting and
"the time of the day.
"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n"
\n";
Daytime Server
#include
#include
#include
#include
#include
#include
#include
#include
#include
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<netdb.h>
<unistd.h>
<stdlib.h>
<string.h>
<stdio.h>
<time.h>
int QueueLength = 5;
Daytime Server
int
main( int argc, char ** argv )
{
// Print usage if not enough arguments
if ( argc < 2 ) {
fprintf( stderr, "%s", usage );
exit( -1 );
}
// Get the port from the arguments
int port = atoi( argv[1] );
// Set the IP address and port for this server
struct sockaddr_in serverIPAddress;
memset( &serverIPAddress, 0, sizeof(serverIPAddress) );
serverIPAddress.sin_family = AF_INET;
serverIPAddress.sin_addr.s_addr = INADDR_ANY;
serverIPAddress.sin_port = htons((u_short) port);
Daytime Server
// Allocate a socket
int masterSocket = socket(PF_INET, SOCK_STREAM, 0);
if ( masterSocket < 0) {
perror("socket");
exit( -1 );
}
// Set socket options to reuse port. Otherwise we will
// have to wait about 2 minutes before reusing the same port number
int optval = 1;
int err = setsockopt(masterSocket, SOL_SOCKET, SO_REUSEADDR,
(char *) &optval, sizeof( int ) );
// Bind the socket to the IP address and port
int error = bind( masterSocket,
(struct sockaddr *)&serverIPAddress,
sizeof(serverIPAddress) );
if ( error ) {
perror("bind");
exit( -1 );
}
Daytime Server
// Put socket in listening mode and set the
// size of the queue of unprocessed connections
error = listen( masterSocket, QueueLength);
if ( error ) {
perror("listen");
exit( -1 );
}
while ( 1 ) {
// Accept incoming connections
struct sockaddr_in clientIPAddress;
int alen = sizeof( clientIPAddress );
int slaveSocket = accept( masterSocket,
(struct sockaddr *)&clientIPAddress,
(socklen_t*)&alen);
Daytime Server
if ( slaveSocket < 0 ) {
perror( "accept" );
exit( -1 );
}
// Process request.
processTimeRequest( slaveSocket );
// Close socket
close( slaveSocket );
}
}
Daytime Server
void
processTimeRequest( int fd )
{
// Buffer used to store the name received from the client
const int MaxName = 1024;
char name[ MaxName + 1 ];
int nameLength = 0;
int n;
// Send prompt
const char * prompt = "\nType your name:";
write( fd, prompt, strlen( prompt ) );
// Currently character read
unsigned char newChar;
// Last character read
unsigned char lastChar = 0;
Daytime Server
//
// The client should send <name><cr><lf>
// Read the name of the client character by character until a
// <CR><LF> is found.
//
while ( nameLength < MaxName &&
( n = read( fd, &newChar, sizeof(newChar) ) ) > 0 ) {
if ( lastChar == '\015' && newChar == '\012' ) {
// Discard previous <CR> from name
nameLength--;
break;
}
name[ nameLength ] = newChar;
nameLength++;
lastChar = newChar;
}
// Add null character at the end of the string
name[ nameLength ] = 0;
printf( "name=%s\n", name );
Daytime Server
// Get time of day
time_t now;
time(&now);
char *timeString = ctime(&now);
// Send name and greetings
const char * hi = "\nHi ";
const char * timeIs = " the time is:\n";
write( fd, hi, strlen( hi ) );
write( fd, name, strlen( name ) );
write( fd, timeIs, strlen( timeIs ) );
// Send the time of day
write(fd, timeString, strlen(timeString));
// Send last newline
const char * newline="\n";
write(fd, newline, strlen(newline));
}
Summary
Types of Server Concurrency
Iterative Server
Fork Process After Request
Create New Thread After Request
Pool of Threads
Pool of Processes
Iterative Server
void iterativeServer( int masterSocket) {
while (1) {
int slaveSocket =accept(masterSocket,
&sockInfo, &alen);
if (slaveSocket >= 0) {
dispatchHTTP(slaveSocket);
}
}
}
Note: We assume that dispatchHTTP itself
closes slaveSocket.
Fork Process After Request
void forkServer( int masterSocket) {
while (1) {
int slaveSocket = accept(masterSocket,
&sockInfo, &alen);
if (slaveSocket >= 0) {
int ret = fork();
`
if (ret == 0) {
dispatchHTTP(slaveSocket);
exit(0);
}
close(slaveSocket);
}
}
}
Create Thread After Request
void createThreadForEachRequest(int masterSocket)
{
while (1) {
int slaveSocket = accept(masterSocket, &sockInfo, &alen);
if (slaveSocket >= 0) {
// When the thread ends resources are recycled
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,
PTHREAD_CREATE_DETACHED);
pthread_create(&thread, &attr,
dispatchHTTP, (void *) slaveSocket);
}
}
}
Pool of Threads
void poolOfThreads( int masterSocket ) {
for (int i=0; i<4; i++) {
pthread_create(&thread[i], NULL, loopthread,
masterSocket);
}
loopthread (masterSocket);
}
void *loopthread (int masterSocket) {
while (1) {
int slaveSocket = accept(masterSocket,
&sockInfo, &alen);
if (slaveSocket >= 0) {
dispatchHTTP(slaveSocket);
}
}
}
Pool of Processes
void poolOfProcesses( int masterSocket ) {
for (int i=0; i<4; i++) {
int pid = fork();
if (pid ==0) {
loopthread (masterSocket);
}
}
loopthread (masterSocket);
}
void *loopthread (int masterSocket) {
while (1) {
int slaveSocket = accept(masterSocket,
&sockInfo, &alen);
if (slaveSocket >= 0) {
dispatchHTTP(slaveSocket);
}
}
}
Notes:
In Pool of Threads and Pool of processes, sometimes the
OS does not allow multiple threads/processes to call
accept() on the same masterSocket.
In other cases it allows it but with some overhead.
To get around it, you can add a mutex_lock/mutex_unlock
around the accept call.
mutex_lock(&mutex);
int slaveSocket = accept(masterSocket,
&sockInfo, 0);
mutex_unlock(&mutex);
In the pool of processes, the mutex will have to be created
in shared memory.
Review
Client/server call sequence for socket
communication
Types of server communication
Download