EE 122: Sockets Kevin Lai September 11, 2002
Motivation Applications need Application Programming Interface (API) to use the network API: set of function types and data structures and constants Desirable characteristics Standardized allows programmer to learn once, write anywhere Flexible support multiple protocols Simple to use Complete allows program to use all functionality of a protocol protocols and APIs usually evolve in parallel laik@cs.berkeley.edu
Sockets Berkeley sockets is the most popular network API runs on Linux, FreeBSD, OS X, Windows fed/fed off of popularity of TCP/IP Supports TCP/IP, UNIX interprocess communication Similar to UNIX file I/O API Based on C, single threaded model does not require multiple threads Can build higher-level interfaces on top of sockets e.g., Remote Procedure Call (RPC) laik@cs.berkeley.edu
Types of Sockets Different types of sockets implements different service models Stream v.s. datagram Stream socket connection-oriented reliable, in order delivery e.g., ssh, http Datagram socket connectionless “best-effort” delivery, possibly lower delay e.g., IP Telephony laik@cs.berkeley.edu
Chat Client create stream socket connect to server while still connected: if user types data, send to server if receive data from server, print laik@cs.berkeley.edu
Naming and Addressing IP Address Host name Port number identifies a single host 32 bits (not a number!) written as dotted octets e.g., 0x0a000001 is 10.0.0.1 Host name variable length string maps to one or more IP address e.g., www.berkeley.edu Port number identifies an application on a host 16 bit number laik@cs.berkeley.edu
Presentation Different CPU architectures have different byte ordering why? Many errors for novice network programmers increasing memory addresses address A +1 address A little-endian high-order byte low-order byte 16-bit value big-endian low-order byte high-order byte laik@cs.berkeley.edu
Byte Ordering Solution uint16_t htons(uint16_t host16bitvalue); uint32_t htonl(uint32_t host32bitvalue); uint16_t ntohs(uint16_t net16bitvalue); uint32_t ntohs(uint32_t net32bitvalue); Use for all integers sent across network including port numbers, but not IP addresses Floating point numbers no widely used standard laik@cs.berkeley.edu
Initializing (1) allocate socket int chat_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); laik@cs.berkeley.edu
Initializing (2) Why would socket() fail? int chat_sock; if ((chat_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror("socket"); printf("Failed to create socket\n"); abort (); } Why would socket() fail? Handling errors that occur rarely usually consumes most of systems code exceptions (e.g., in java) helps this a somewhat laik@cs.berkeley.edu
Connecting (1) struct sockaddr_in sin; struct hostent *host = gethostbyname (argv[1]); unsigned int server_addr = *(unsigned long *) host->h_addr_list[0]; unsigned short server_port = atoi (argv[2]); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(server_addr); sin.sin_port = server_port; connect(chat_sock, (struct sockaddr *) &sin, sizeof(&sin)); laik@cs.berkeley.edu
Connecting (2) struct sockaddr_in sin; struct hostent *host = gethostbyname (argv[1]); unsigned int server_addr = *(unsigned long *) host->h_addr_list[0]; unsigned short server_port = atoi (argv[2]); memset (&sin, 0, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = server_addr; sin.sin_port = htons (server_port); if (connect(chat_sock, (struct sockaddr *) &sin, sizeof (sin)) < 0) { perror("connect"); printf("Cannot connect to server\n"); abort(); } laik@cs.berkeley.edu
Separating Data in a Stream Fixed length Record Fixed length record Variable length record Variable length record A B C 3 C 2 1 2 3 4 5 6 7 8 9 Use records to partition Tradeoffs of two variable schemes? Variable length record Variable length record C C 5 6 7 8 9 laik@cs.berkeley.edu
Receiving Packets (1) int received; char buffer[RECORD_LEN]; received = recv(chat_sock, buffer, RECORD_LEN, 0); process_packet(buffer, received); laik@cs.berkeley.edu
Receiving Packets (2) int receive_packets(char *buffer, int buffer_len, int *bytes_read) { int left = buffer_len - *bytes_read; received = recv(chat_sock, buffer + *bytes_read, left, 0); if (received < 0) { perror ("Read in read_client"); printf("recv in %s\n", __FUNCTION__); } if (received <= 0) { return close_connection(); *bytes_read += received; while (*bytes_read > RECORD_LEN) { process_packet(buffer, RECORD_LEN); *bytes_read -= RECORD_LEN; memmove(buffer, buffer + RECORD_LEN, *bytes_read); return 0; laik@cs.berkeley.edu
I/O Multiplexing (1) while (1) { if (receive_packets(buffer, buffer_len, &bytes_read) != 0) { break; } if (read_user(user_buffer, user_buffer_len, &user_bytes_read) != 0) { laik@cs.berkeley.edu
I/O Multiplexing (2): Non-blocking int opts = fcntl (chat_sock, F_GETFL); if (opts < 0) { perror ("fcntl(F_GETFL)"); abort (); } opts = (opts | O_NONBLOCK); if (fcntl (chat_sock, F_SETFL, opts) < 0) { perror ("fcntl(F_SETFL)"); while (1) { if (receive_packets(buffer, buffer_len, &bytes_read) != 0) { break; if (read_user(user_buffer, user_buffer_len, &user_bytes_read) != 0) { laik@cs.berkeley.edu
I/O Multiplexing using select() wait on multiple file descriptors/sockets and timeout application does not consume CPU cycles while waiting return when file descriptors/sockets are ready to be read or written or they have an error, or timeout exceeded advantages simple more efficient than polling disadvantages does not scale to large number of file descriptors/sockets more awkward to use than it needs to be laik@cs.berkeley.edu
I/O Multiplexing (3): select() // already set descriptors non-blocking fd_set read_set; while (1) { FD_ZERO (read_set); FD_SET (stdin, read_set); FD_SET (chat_sock, read_set); select_retval = select(MAX(stdin, chat_sock) + 1, &read_set, NULL, NULL, &time_out); if (select_retval < 0) { perror ("select"); abort (); } if (select_retval > 0) { if (FD_ISSET(chat_sock, read_set)) { if (receive_packets(buffer, buffer_len, &bytes_read) != 0) { break; if (FD_ISSET(stdin, read_set)) { if (read_user(user_buffer, user_buffer_len, &user_bytes_read) != 0) { laik@cs.berkeley.edu
Other I/O Models Signal driven Asynchronous application notified when I/O operation can be initiated achieves similar CPU efficiency as select() Asynchronous application notified when I/O operation is completed can achieve higher CPU efficiency than select()/signals on architectures that have DMA and available system bus bandwidth mainly useful for very high bandwidth I/O Both add significant complexity relative to select() must use locks to deal with being interrupted at arbitrary code locations sample complexity cost as threads laik@cs.berkeley.edu
Chat Server create stream socket while 1: if user connects, add to list of users if receive data from client, send to all other clients laik@cs.berkeley.edu
Summary Major sources of error for network programmers using sockets: byte ordering separating records in streams using select() misinterpreting the specification (not covered here) laik@cs.berkeley.edu