Socket Tutorial Prof. Chris GauthierDickey COMP 3621: Computer - - PowerPoint PPT Presentation

socket tutorial
SMART_READER_LITE
LIVE PREVIEW

Socket Tutorial Prof. Chris GauthierDickey COMP 3621: Computer - - PowerPoint PPT Presentation

Socket Tutorial Prof. Chris GauthierDickey COMP 3621: Computer Networking University of Denver 1 Usage: You are free to use these slides, just give credit where credit is due (= Prof. Chris GauthierDickey, University of Denver, 2010 2 What


slide-1
SLIDE 1

Socket Tutorial

  • Prof. Chris GauthierDickey

COMP 3621: Computer Networking University of Denver

1

slide-2
SLIDE 2
  • Prof. Chris GauthierDickey, University of Denver, 2010

Usage:

You are free to use these slides, just give credit where credit is due (=

2

slide-3
SLIDE 3
  • Prof. Chris GauthierDickey, University of Denver, 2010

What are sockets?

When we broadly talk about sockets, we usually are referring to networking sockets In particular, they refer to Berkeley sockets which were created some time ago (and operate slightly differently than POSIX sockets!) Typically, we now use the POSIX sockets API Sockets are also just that: an API for network communication

3

slide-4
SLIDE 4
  • Prof. Chris GauthierDickey, University of Denver, 2010

Sockets introduced...

Conceptually, sockets are like a door [K&R ’09] But more like a magic portal (think StargateTM): We create one side and tell the OS where the

  • ther side opens--on another system!

The OSes handle the rest: communicating via TCP

  • r UDP to the other system and establishing a

connection (in the case of TCP)

4

slide-5
SLIDE 5
  • Prof. Chris GauthierDickey, University of Denver, 2010

Sockets introduced...

Like any door, we have to open and close it to send things through it We also have to know where this side opens (i.e., what network address so that the door can be two-way) We have to know where the other side opens (i.e., what the other address is) We also have to know how stable we want transport through door to be (i.e., TCP , UDP , or something else)

5

slide-6
SLIDE 6
  • Prof. Chris GauthierDickey, University of Denver, 2010

Main Socket Functions [client side]

As usual, these are in C: getaddrinfo(), freeaddrinfo(): gets and frees address structures socket(): creates a socket for communication connect(): for connection-oriented sockets (TCP) recv() & send() for TCP recvfrom() & sendto() for UDP

6

slide-7
SLIDE 7
  • Prof. Chris GauthierDickey, University of Denver, 2010

Client-side communication

First we have to determine which type of transport protocol we want UDP provides best effort delivery, with no guarantees TCP provides in-order, reliable, congestion-controlled

  • transport. TCP is ‘stream’ based.

7

slide-8
SLIDE 8
  • Prof. Chris GauthierDickey, University of Denver, 2010

Overview

Creating a networking application with sockets requires the following steps: Create the socket Resolve a name to get an IP address Connect/Bind/Accept if using TCP Call sendto/recvfrom for UDP or send/recv for TCP

8

slide-9
SLIDE 9
  • Prof. Chris GauthierDickey, University of Denver, 2010

Creating a socket

All network communication requires the creation of a socket The table lists the required parameters for both TCP and UDP sockets

#include <sys/socket.h> // we have to specify the family, the type // and the protocol when creating a socket int socket(int family, int type, int protocol); UDP TCP family type protocol AF_INET, AF_INET6 AF_INET, AF_INET6 SOCK_DGRAM SOCK_STREAM IPPROTO_UDP IPPROTO_TCP

Parameters for creating a socket

9

slide-10
SLIDE 10
  • Prof. Chris GauthierDickey, University of Denver, 2010

What does socket(...) return?

A call to socket(...) returns what we call a ‘socket file descriptor’, or -1 if there was an error (check errno) Think of it as a handle to some internal data structure the OS is using to provide networking with In Unix, socket file descriptors and regular file descriptors behave similarly The socket file descriptor will be used for every networking call so that the OS knows which network socket to use

10

slide-11
SLIDE 11
  • Prof. Chris GauthierDickey, University of Denver, 2010

Resolving host names

Note that to create a socket, we have to resolve the host name Older networking code uses gethostbyname() or gethostbyaddr(), but these are only good for IPv4 We will instead use getaddrinfo() and freeaddrinfo()

11

slide-12
SLIDE 12
  • Prof. Chris GauthierDickey, University of Denver, 2010

getaddrinfo()

The struct addrinfo is used for two purposes: to pass in hints so that the right type of address will be created to get the results of that call

// definition of getaddrinfo() #include <netdb.h> int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result);

  • hostname is either an actual host’s name (e.g.,

www.cs.du.edu) or an ip address (130.253.8.41) as a string (i.e., with quotes around it)

  • service is either the string service name (from /etc/

services) or a decimal port name (again, in string format)

  • hints should be filled with:
  • ai_flags: 0 or AI_CANONNAME
  • ai_family: AF_INET for IPv4 or AF_INET6 for IPv6
  • ai_socktype: SOCK_DGRAM for UDP or

SOCK_STREAM for TCP

  • ai_protocol: IPPROTO_UDP or IPPROTO_TCP for

UDP and TCP respectively

  • result is a struct addrinfo pointer to a pointer that gets

filled by the call of the function

12

slide-13
SLIDE 13
  • Prof. Chris GauthierDickey, University of Denver, 2010

freeaddrinfo() and gai_strerror()

The result you get from getaddrinfo *must* be freed using freeaddrinfo(), or you’ll have a memory leak Note that getaddrinfo returns a LIST of addresses (sometimes you have more than one result), which you can iterate over via its ai_next element. You just call freeaddrinfo() on the FIRST result from getaddrinfo() gai_strerror returns the string representation of the error returned by getaddrinfo

13

slide-14
SLIDE 14
  • Prof. Chris GauthierDickey, University of Denver, 2010

getaddrinfo() example

#include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> // the following creates a UDP address for IPv4, use AF_UNSPEC if you wish to get // both IPv4 and IPv6 addresses from getaddrinfo(), furthermore, if you are creating // a server, use NULL for the name, and pass AI_PASSIVE to hints.ai_flags struct addrinfo *getUDPAddressByName(const char *name, const char *port) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; struct addrinfo *result; int result = getaddrinfo(name, port, &hints, &result); if (result != 0) { fprintf(STDERR, “Error resolving host %s and port %s: %s\n”, name, port, gai_strerror(result)); return NULL; } return result; }

14

slide-15
SLIDE 15
  • Prof. Chris GauthierDickey, University of Denver, 2010

UDP client communication

Assume for now that the server exists and may be waiting for packets UDP is connectionless--we do not have to establish a connection to the server prior to sending data to it UDP is bufferless: we may have a buffer we write to, and which is passed to the OS, but the OS only (usually) temporarily buffers the data to write it to the socket and then throws the buffer away

15

slide-16
SLIDE 16
  • Prof. Chris GauthierDickey, University of Denver, 2010

UDP: sendto

sendto(...) is used to send a UDP packet to the specified destination (the ‘to’ field). buff is your buffer of data, nbytes is its length, flags are any sendto flags you need (see the man pages), to (the destination address) and addrlen are taken from the struct addrinfo returned by getaddrinfo()

#include <sys/socket.h> // returns the number of bytes sent or // -1 on error (must check errno) ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, struct sockaddr *to, socklen_t *addrlen);

16

slide-17
SLIDE 17
  • Prof. Chris GauthierDickey, University of Denver, 2010

UDP: recvfrom

We call recvfrom when we want to retrieve a message from the UDP socket sockfd is the valid socket from socket() buff is a buffer to retreive the message into nbytes is the size of buff in bytes flags are flags for the socket for reading (typically pass in 0) from is filled in by the call to recvfrom with the sender’s address addrlen is filled in by the call to recvfrom with the size of the sender’s address You can legally pass NULL into from and addrlen, and it won’t return the address info to you (obviously)

#include <sys/socket.h> // returns the number of bytes sent or // -1 on error (must check errno) ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);

17

slide-18
SLIDE 18
  • Prof. Chris GauthierDickey, University of Denver, 2010

UDP Server: bind()

To create a UDP server, you have to bind the socket to an address and port number. Clients then send messages (via sendto) to that address. socket: a valid socket created via socket() address: the address we’re binding to, which includes the port number--via getaddrinfo() address_len: the length of the address struct, also via getaddrinfo()

#include <sys/socket.h> // returns 0 if successful, -1 of there was // an error (must check errno) int bind(int socket, const struct sockaddr *address, socklen_t address_len);

18

slide-19
SLIDE 19
  • Prof. Chris GauthierDickey, University of Denver, 2010

Creating a UDP client

Create a socket, using an address from getaddrinfo() and a call to socket() Call sendto() with the address and port of the server Call recvfrom() to wait for a response from the server Validate that the address was the server’s address Call close() on the socket

19

slide-20
SLIDE 20
  • Prof. Chris GauthierDickey, University of Denver, 2010

Creating a UDP Server

Create a socket with an address and port using getaddrinfo() for the address, and socket() for the port Set the AI_PASSIVE flag on the ai_flags field of the hints parameter to

  • getaddrinfo. You can also set the hostname parameter to NULL so that

you bind to any of the server’s addresses. bind() the socket and address Wait for messages using recvfrom (keep the from address around for later) Respond to messages with sendto (and use the address in the from field when you received the message) close() when you’re done!

20

slide-21
SLIDE 21
  • Prof. Chris GauthierDickey, University of Denver, 2010

Preparing data to send

From a C perspective, the buffers you use for sendto() and recvfrom() are simply chunks of memory without meaning. The buffers are copied byte-by-byte to the OS buffer to be sent on This is fine most of the time, except when you’re trying to send integer data.

21

slide-22
SLIDE 22
  • Prof. Chris GauthierDickey, University of Denver, 2010

Integer conversions

In order to make networking compatible with different architectures, a set of functions was created in the socket API to convert from what we call network byte-order to host byte-order For example, Intel and PowerPC order the bytes of short integers and integers differently--we call this endian-ness. Intel is little-endian while PowerPC is big-endian. htonl() and htons() convert integers (32-bit values) and shorts (16-bit values) from host byte-ordering to network byte-ordering. ntohl() and ntohs() convert integers (32-bit values) and shorts (16-bit values) from network byte-ordering to host byte-ordering. Network byte-ordering is big-endian You MUST convert back and forth for each integer and short you read over the

  • internet. You DO NOT convert bytes, bytes always have the same order. The same

holds for simple strings of characters and such.

22

slide-23
SLIDE 23
  • Prof. Chris GauthierDickey, University of Denver, 2010

Non-blocking I/O

As you write networking code, the first thing you’ll notice is that sendto(), send(), recvfrom() and recv() block! The OS will not return from your function until the operation is complete, which is a problem with recvfrom() and recv() since you’re not sure if data will be available to read Imagine having to write code that periodically sends a packet, but in the meantime you’re waiting for data Again, calling recv() or recvfrom() will cause your process to halt until data is received You have many options: Use threads for reading/writing Use poll/select Use some kind of asynchronous I/O (preferable for high performance--see libev for a cross-platform solution

23

slide-24
SLIDE 24
  • Prof. Chris GauthierDickey, University of Denver, 2010

Why select()?

It’s on most platforms in one form or another and fairly easy to use and reason about. Neither select() nor poll() (an alternative to select) are the fastest by any means, but incredibly useful to understand To use select(), we have to use a data structure, called a file descriptor set, that we hand off to the OS. The OS then marks which file descriptors are ready for reading, writing, or has an exception. We test the set and attempt to read or write only on those marked

24

slide-25
SLIDE 25
  • Prof. Chris GauthierDickey, University of Denver, 2010

select() continued...

Select allows you to specify a timeout value down to the

  • microsecond. This in theory allows you to create a fairly high

resolution timer (think about how you could do that) The main problem with select() is that even after the OS tells you the socket is ready, it could still block! So you further have to ensure that all sockets are marked as non-blocking. The main side-effect of creating your socket as non- blocking is that your calls to read or write can return -1, with errno set to EAGAIN or EWOULDBLOCK. This just means the socket isn’t actually ready.

25

slide-26
SLIDE 26
  • Prof. Chris GauthierDickey, University of Denver, 2010

Creating a non-blocking socket

First, create a socket Then use fcntl() to set O_NONBLOCK on the flags of the socket You can use fcntl() to check the options on the file descriptor also It’s worth reading the fcntl() man pages to see what interesting things you can do with the socket!

#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> int flags = fcntl(sockfd, F_GETFL); if (flags == -1) { perror(“can’t get flags using fcntl()”); exit(EXIT_FAILURE); } int res = fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); if (flags == -1) { perror(“can’t set O_NONBLOCK using fcntl()”); exit(EXIT_FAILURE); }

26

slide-27
SLIDE 27
  • Prof. Chris GauthierDickey, University of Denver, 2010

Creating the FD_SET

The fd_set is the data structure that you manipulate with macros: FD_SET(int fd, fd_set *set) FD_CLR(int fd, fd_set *set) FD_ISSET(int fd, fd_set *set) FD_ZERO(fd_set *set)

#include <sys/select.h> // assume we have a socket called sockfd fd_set masterset; fd_set readset; fd_set writeset; fd_set exceptset; // zero them out FD_ZERO(&masterset); FD_ZERO(&readset); FD_ZERO(&writeset); FD_ZERO(&exceptset); // now we set each socket in the fd_set // we’re interested in FD_SET(sockfd, &masterset); FD_SET(sockfd, &readset); FD_SET(sockfd, &writeset); FD_SET(sockfd, &exceptset);

27

slide-28
SLIDE 28
  • Prof. Chris GauthierDickey, University of Denver, 2010

Using select()

select() requires a timeout value, which if we use NULL, will wait until at least one socket is ready for action Then we loop through all the sockets in the fd_sets and check to see if they were set by select() Finally, we reset the fd_set by copying the master set

  • ver them using a C struct

copy

#include <sys/select.h> struct timeval timeout; timeout.tv_sec = 2; timeout.tv_usec = 0; // note that maxfd must be the highest integer // value of the sockets you’ve created because // they map directly into the fd_set int res = select(maxfd+1, &readset, &writeset, &exceptset, &timeout); // In this case, select will block for 2 seconds // before returning. Setting timeout to 0 is in // essence making select a non-blocking call // You check each socket using FD_ISSET if (FD_ISSET(sockfd, &readset)) { // the socket has data and can be processed } if (FD_ISSET(sockfd, &writeset)) { // the socket is ready to be written } // copy the masterset over the readset, // writeset, and exceptset before you call // select again (since they’re modified by // select!) readset = masterset;

28

slide-29
SLIDE 29
  • Prof. Chris GauthierDickey, University of Denver, 2010

Creating a TCP client

Use getaddrinfo() to get the server’s address and port structure, make sure to specify SOCK_STREAM for the ai_socktype, AF_UNSPEC for ai_family, and leave ai_protocol = 0. Create a socket from the returned values. Make a call to connect()

29

slide-30
SLIDE 30
  • Prof. Chris GauthierDickey, University of Denver, 2010

Connect

Connect causes the 3- way TCP handshake to be initiated, this may take some time to complete (obviously) All TCP sockets must call connect() On the server side, the call to accept() completes the 3-way handshake

#include <sys/socket.h> // returns 0 if OK, -1 if an error int connect(int sockfd, const struct sockaddr *serveraddr, socklen_t addrlen);

30

slide-31
SLIDE 31
  • Prof. Chris GauthierDickey, University of Denver, 2010

Creating a TCP server with select()

Get an address and port to bind to: call getaddrinfo(), but for the source, use the empty string, and be sure to set ai_flags = AI_PASSIVE in the hints addrinfo struct that you pass to getaddrinfo(). Also set ai_family to AF_UNSPEC, ai_socktype to SOCK_STREAM, and ai_protocol to 0. Call socket to create a socket from this address--this socket is suitable for binding to. We often refer to this as the server socket.

31

slide-32
SLIDE 32
  • Prof. Chris GauthierDickey, University of Denver, 2010

TCP server cont...

Call bind() to bind to the socket so that you can listen on the port. Call listen() with a backlog of around 5 for light purposes Call accept() when your server socket is ready to be read--this means a new connection is available

#include <sys/socket.h> // serverfd is the socket that you bound to, // while backlog is how many outstanding // connections you can have before they start // to fail--ie, you must call accept to keep // the backlog from piling up! int listen(int serverfd, int backlog); // listen returns 0 on success, -1 on failure

32

slide-33
SLIDE 33
  • Prof. Chris GauthierDickey, University of Denver, 2010

accept()

Using select(), you can check to see if your server socket is ready to read. If so, you can call accept, and it likely won’t block (hahaha) Once accept() returns, you will have a valid socket that can be used for

  • communication. Note that it

also fills out the address for the client in case you need it.

#include <sys/socket.h> // accept returns the new file descriptor socket // of the connection that has been established. // You pass in a valid sockaddr and addrlen to be // filled out by the client. If -1 is returned, // an error has occurred int accept(int serverfd, struct sockaddr *cliaddr, socklen_t *addrlen);

33

slide-34
SLIDE 34
  • Prof. Chris GauthierDickey, University of Denver, 2010

TCP Server continued

With this new socket, you can read and write data. Note that you should update your max file descriptor value for future calls to select(). Using select(), you can check which of all the sockets you have is ready for reading and writing and you can process the data--of course blocking on any of these will cause your server to block, so be careful!

34

slide-35
SLIDE 35
  • Prof. Chris GauthierDickey, University of Denver, 2010

Reading and writing to a TCP socket

Recall that TCP is connection-oriented This means that every TCP connection has an associated socket at each end! Yes, the client has a socket they use to talk to the server The server has a socket for each client they talk to, plus the socket that listens for new connections

35

slide-36
SLIDE 36
  • Prof. Chris GauthierDickey, University of Denver, 2010

Reading and writing to a TCP socket (cont...)

For TCP , we use send() and recv() Note that we do not specify the address in either function because the socket is connected to the socket on the

  • ther end

Both of these return the number of bytes read or written, so it’s possible you may need to update your buffer to reflect the fact that not all of the data was consumed

#include <sys/socket.h> // Use the socket you are sending on for sockfd, // buffer should point to your allocated and // filled buffer, length should be the size of // the buffer (or number of bytes in the buffer // you are sending), and almost always flags is // set to 0 (technically it could be MSG_OOB or // MSG_DONTROUTE, but these are rarely used). // Note that this returns the number of bytes // sent, which may be less than what you wanted // to actually send! ssize_t send(int sockfd, const void *buffer, size_t length, int flags); // in this case, buffer is a space of length // bytes the OS can write to, flags again are // typically set to 0--this returns how many // bytes were actually written ssize_t recv(int sockfd, void *buffer, size_t length, int flags);

36

slide-37
SLIDE 37
  • Prof. Chris GauthierDickey, University of Denver, 2010

send() and recv()

When sending, you must make sure it returns the number of bytes you were expecting it to send (it may return -1 on error). This means you need to update your buffer and retry sending again later! When recv’ing, you want to make sure you check your protocol to read enough bytes from the socket to ensure that you have the complete packet! Streaming protocols require you to manage buffers in general!

37

slide-38
SLIDE 38
  • Prof. Chris GauthierDickey, University of Denver, 2010

Managing simultaneous clients

The key to using select() is to create a struct or class that holds onto a descriptor plus its send and receive buffers since you may only get a partial send or recv Each descriptor you get back from accept() should be set to non-blocking You must also keep track of the maximum file descriptor value seen so that select doesn’t need to check all descriptors in the fd_sets.

38

slide-39
SLIDE 39
  • Prof. Chris GauthierDickey, University of Denver, 2010

References

K&R ’09: James Kurose and Keith Ross, Computer Networking: A Top-Down Approach, 5th edition, Addison Wesley, 2009, ISBN 0-13-607967-9 SFR ’04: W. Richard Stevens, Bill Fenner and Andrew

  • M. Rudoff, UNIX Network Programming: The Sockets

Networking API, Volume 1, 3rd edition, Pearson Education, 2004, ISBN 0-13-141155-1

39