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