Today we will discuss about Sockets programming paradigm, elements of Sockets applications, and the Sockets API. The Sockets API allows to develop applications that communicate over a network. The network can be a local private network or the public Internet. An important item about Sockets programming is that it's neither operating system specific nor language specific. Sockets applications can be written in the Ruby scripting language on a GNU/Linux host or in C on an embedded controller. This freedom and flexibility are the reasons that the BSD4.4 Sockets API is so popular.
Layered Model of Networking
Sockets programming uses the layered model of packet communication as shown in the figure below. At the top is the application layer, which is where the applications exist (those that utilize Sockets for communication). Below is the application layer defines the Sockets layer. This isn't actually a layer, but it is shown to illustrate where the API is located. The Sockets layer sits on top of the transport layer. The transport layer provides the transport protocols. Next is the network layer, which provides among other things routing over the Internet. This layer is occupied by the Internet Protocol, or IP. Finally, is the physical layer driver, which provides the means to introduce packets onto the physical network.
Sockets API Summary
The networking API for C provides a mixed set of functions for the development of client and server applications. Some functions are used by only server-side sockets, whereas others are used solely by client-side sockets (most are available to both).
Creating and Destroying Sockets
The first step of any Sockets-based application is to create a socket.The socket function provides the following prototype:
Code:
int socket( int domain, int type, int protocol );
Code:
myStreamSocket = socket( AF_INET, SOCK_STREAM, 0 ); myDgramSocket = socket( AF_INET, SOCK_DGRAM, 0 ); myRawSocket = socket( AF_INET, SOCK_RAW, IPPROTO_RAW );
When we've finished with a socket, we must close it. The close prototype is defined as follows:
Code:
int close( sock );
Socket Addresses
For socket communication over the Internet (domain AF_INET), we use the sock-addr_in structure for naming purposes.
Code:
struct sockaddr_in { int16_t sin_family; uint16_t sin_port; struct in_addr sin_addr; char sin_zero[8]; }; struct in_addr { uint32_t s_addr; };
Now let us take look at a quick example of addressing for both a client and a server. First, in this example we create the socket address (later to be bound to your server socket) that permits incoming connections on any interface and port 48000.
Code:
int servsock; struct sockaddr_in servaddr; servsock = socket( AF_INET, SOCK_STREAM, 0); memset( &servaddr, 0, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_port = htons( 48000 ); servaddr.sin_addr.s_addr = inet_addr( INADDR_ANY );
Code:
int clisock; struct sockaddr_in servaddr; clisock = socket(AF_INET, SOCK_STREAM, 0); memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(48000); servaddr.sin_addr.s_addr = inet_addr("192.168.1.1");
Socket Primitives
Now I will look at a number of other important server-side socket control primitives.
bind
The bind function provides a local naming capability to a socket. This can be used to name either client or server sockets, but it is used most often in the server case.
The bind function is provided by the following prototype:
Code:
int bind( int sock, struct sockaddr *addr, int addrLen );
Code:
err = bind( servsock, (struct sockaddr *)&servaddr,sizeof(servaddr));
Recall that a client application can also call bind to name the client socket. This isn't used often, because the Sockets API dynamically assigns a port to us.
listen
Before a server socket can accept incoming client connections, it must call the listen function to declare this willingness. The listen function is provided by the following function prototype:
Code:
int listen( int sock, int backlog );
accept
The accept call is the final call made by servers to accept incoming client connections. Before accept can be called, the server socket must be created, a name must be bound to it, and listen must be called. The accept function returns a socket descriptor for a client connection and is provided by the following function prototype:
Code:
int accept( int sock, struct sockaddr *addr, int *addrLen );
Code:
struct sockaddr_in cliaddr; int cliLen; cliLen = sizeof( struct sockaddr_in ); clisock = accept( servsock, (struct sockaddr *)cliaddr, &cliLen );
The alternate example is commonly found when the server application isn't interested in the client information. This one typically appears as follows:
Code:
cliSock = accept( servsock, (struct sockaddr *)NULL, NULL );
connect
The connect function is used by client Sockets applications to connect to a server. Clients must have created a socket and then defined an address structure containing the host and port number to which they want to connect. The connect function is provided by the following function prototype:
Code:
int connect( int sock, (struct sockaddr *)servaddr, int addrLen );
The following code shows a complete example of connect:
Code:
int clisock; struct sockaddr_in servaddr; clisock = socket( AF_INET, SOCK_STREAM, 0); memset( &servaddr, 0, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_port = htons( 48000 ); servaddr.sin_addr.s_addr = inet_addr( "192.168.1.1" ); connect( clisock, (struct sockaddr_in *)&servaddr, sizeof(servaddr) );
Sockets I/O
A variety of API functions exist to read data from a socket or write data to a socket. Two of the API functions (recv, send) are used exclusively by sockets that are connected (such as stream sockets), whereas an alternative pair (recvfrom, sendto) is used exclusively by sockets that are unconnected (such as datagram sockets).
Connected Socket Functions
The send and recv functions are used to send a message to the peer socket endpoint and to receive a message from the peer socket endpoint. These functions have the following prototypes:
Code:
int send( int sock, const void *msg, int len, unsigned int flags ); int recv( int sock, void *buf, int len, unsigned int flags );
Code:
strcpy( buf, "Hello\n"); send( sock, (void *)buf, strlen(buf), 0);
Code:
send( sock, (void *)buf, strlen(buf), MSG_DONTWAIT);
The recv function mirrors the send function in terms of an argument list. Instead of sending the data pointed to be msg, the recv function fills the buf argument with the bytes read from the socket. We must define the size of the buffer so that the network protocol stack doesn't overwrite the buffer, which is defined by the len argument. Finally, we can alter the behavior of the read call using the flags argument. The value returned by the recv function is the number of bytes now contained in the msg buffer, or -1 on error. An example of the recv function is as follows:
Code:
#define MAX_BUFFER_SIZE 50 char buffer[MAX_BUFFER_SIZE+1]; ... numBytes = recv( sock, buffer, MAX_BUFFER_SIZE, 0);
We can peek at the data that's available to read by using the MSG_PEEK flag. This performs a read, but it doesn't consume the data at the socket. This requires another recv to actually consume the available data. An example of this type of read is illustrated as follows:
Code:
numBytes = recv( sock, buffer, MAX_BUFFER_SIZE, MSG_PEEK);
Unconnected Socket Functions
The sendto and recvfrom functions are used to send a message to the peer socket endpoint and receive a message from the peer socket endpoint. These functions have the following prototypes:
Code:
int sendto( int sock, const void *msg, int len,unsigned int flags,const struct sockaddr *to, int tolen ); int recvfrom( int sock, void *buf, int len,unsigned int flags,struct sockaddr *from, int *fromlen );
Code:
struct sockaddr_in destaddr; int sock; char *buf; ... memset( &destaddr, 0, sizeof(destaddr) ); destaddr.sin_family = AF_INET; destaddr.sin_port = htons(581); destaddr.sin_addr.s_addr = inet_addr("192.168.1.1"); sendto( sock, buf, strlen(buf), 0,(struct sockaddr *)&destaddr, sizeof(destaddr) );
As with the send function, the number of characters queued for transmission is returned, or -1 if an error occurs.
The recvfrom function provides the ability for an unconnected socket to receive datagrams. The recvfrom function is again similar to the recv function, but an address structure and length are provided. The address structure is used to return the sender of the datagram to the function caller. This information can be used with the sendto function to return a response datagram to the original sender.
An example of the recvfrom function is shown in the following code:
Code:
#define MAX_LEN 100 struct sockaddr_in fromaddr; int sock, len, fromlen; char buf[MAX_LEN+1]; ... fromlen = sizeof(fromaddr); len = recvfrom( sock, buf, MAX_LEN, 0,(struct sockaddr *)&fromaddr, &fromlen );
Socket Options
Socket options permit an application to change some of the modifiable behaviors of sockets and the functions that manipulate them. For example, an application can modify the sizes of the send or receive socket buffers or the size of the maximum segment used by the TCP layer for a given socket.
The functions for setting or retrieving options for a given socket are provided by the following function prototypes:
Code:
int getsockopt( int sock, int level, int optname,void *optval, socklen_t *optlen ); int setsockopt( int sock, int level, int optname,const void *optval, socklen_t optlen );
The level argument can be :
- SOL_SOCKET for socket-layer options,
- IPPROTO_IP for IP layer options, and
- IPPROTO_TCP for TCP layer options.
Now let us take a look at an example for both setting and retrieving an option. In the first example, we retrieve the size of the send buffer for a socket.
Code:
int sock, size, len; ... getsockopt( sock, SOL_SOCKET, SO_SNDBUF, (void *)&size, (socklen_t *)&len ); printf( "Send buffer size is &d\n", size );
Code:
struct linger ling; int sock; ... ling.l_onoff = 1; /* Enable */ ling.l_linger = 10; /* 10 seconds */ setsockopt( sock, SOL_SOCKET, SO_LINGER,(void *)&ling, sizeof(struct linger) );
Other Miscellaneous Functions
Now it's time to look at a few miscellaneous functions from the Sockets API and the capabilities they provide. The three function prototypes discussed in this section are shown in the following code:
Code:
struct hostent *gethostbyname( const char *name ); int getsockname( int sock, struct sockaddr *name, socklen_t*namelen ); int getpeername( int sock, struct sockaddr *name, socklen_t*namelen );
An example of the gethostbyname function is shown below:
Code:
struct hostent *hptr; hptr = gethostbyname( "www.microsoft.com"); if (hptr == NULL) // can't resolve... else { printf("Binary address is %x\n", hptr-> h_addr_list[0]); }
Function getsockname permits an application to retrieve information about the local socket endpoint. This function, for example, can identify the dynamically assigned ephemeral port number for the local socket.
An example of its use is shown in the following code:
Code:
int sock; struct sockaddr localaddr; int laddrlen; // Socket for sock created and connected. ... getsockname( sock, (struct sockaddr_in *)&localaddr, &laddrlen ); printf( "local port is %d\n", ntohs(localaddr.sin_port) );
Code:
int sock; struct sockaddr remaddr; int raddrlen; // Socket for sock created and connected. ... getpeername( sock, (struct sockaddr_in *)&remaddr, &raddrlen ); printf( "remote port is %d\n", ntohs(remaddr.sin_port) );
0 comments:
Post a Comment