Socket Tutorial
- Prof. Chris GauthierDickey
COMP 3621: Computer Networking University of Denver
1
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
1
2
3
4
5
6
7
8
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
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
11
// definition of getaddrinfo() #include <netdb.h> int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result);
www.cs.du.edu) or an ip address (130.253.8.41) as a string (i.e., with quotes around it)
services) or a decimal port name (again, in string format)
SOCK_STREAM for TCP
UDP and TCP respectively
filled by the call of the function
12
13
#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
15
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
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
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
19
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
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
21
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
holds for simple strings of characters and such.
22
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
24
Select allows you to specify a timeout value down to the
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
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
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
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
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
29
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
31
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
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
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
34
35
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
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
37
38
39