Streaming (or connection-oriented) sockets are probably the most commonly used type of communication transport protocol over TCP/IP that you will use. TCP sockets provide you with a reliable, nearly error-free data pipe between two endpoints, both of which can send and receive streams of bytes back and forth, without data being lost or duplicated. A good analogy would be to compare a TCP socket connection to a telephone call between devices?one calls another (the phone number being the server device's IP address, and its phone extension being the server device's IP port). Once the other device picks up, a conversation can proceed between the two devices, with both transmitting and receiving data. Finally, the call is completed, and both sides hang up. The connection thus made is known as a communications session (this is the same session described previously in the OSI model).
This type of connection between devices is also sometimes referred to as a client/server model. One device, known as the client, creates a socket, connects to the server, and then begins sending and receiving data. On the other side, the server creates a socket and listens for an incoming connection from the client. Once a connection is initiated, the server accepts the connection, and then starts to send and receive data to and from the incoming client. The data that the client and server send back and forth is completely up to you; however, several well-known communication protocols have already been established, such as HTTP or FTP.
Figure 1.2 shows the process for creating both client and server TCP socket connections and how data flows between both network endpoints.
The first step in establishing a network connection via Winsock is to create a socket. A socket is a data type, similar to a file handle, that identifies a unique descriptor that allows access to your network object. What the actual descriptor identifies is not specifically detailed in the Winsock specification; rather, it is determined by the specific Winsock implementation, so we don't really know what that value means. For our purposes, the actual contents of the descriptor are not important. What is important is the understanding that a socket is what you use to access your network connection.
To create a socket, you use the socket() function, which is defined as follows:
SOCKET socket (int af, int type, int protocol);
The af parameter specifies the protocol's address family, which determines what type of socket will be created. Pocket PC supports either the AF_INET or AF_IRDA socket types. If you wanted to create a socket for infrared communications, you would use AF_IRDA (see Chapter 5); otherwise, for normal TCP/IP usage, you would use AF_INET. The type parameter is the protocol's communication type, and can be either SOCK_STREAM or SOCK_DGRAM. To create a TCP connection-oriented socket, use SOCK_STREAM. When creating a connectionless UDP socket, use SOCK_DGRAM (see the section "Connectionless (UDP) Sockets"). You must use SOCK_STREAM if you are creating a socket to be used for infrared communications. The final parameter, protocol, specifies which protocol to use with the socket. If you want to specify the TCP protocol, you use the value IPPROTO_TCP. Conversely, IPPROTO_UDP specifies the UDP protocol.
When the function returns, you will receive either a new socket handle or the error INVALID_SOCKET. If you want to find out why you could not create a socket, use the WSAGetLastError() function described previously.
The following code shows how to create a connection-oriented socket:
// Create a connection-oriented socket SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Check to see if we have a valid socket if(s == INVALID_SOCKET) { int iSocketError = WSAGetLastError(); return FALSE; }
Once you have created a socket, you can use it to establish a connection to a server. This is done by using the connect() function call:
int connect (SOCKET s, const struct sockaddr *name, int namelen);
The first parameter, s, specifies the socket descriptor that was returned from the socket function. The name parameter is the socket address structure, SOCKADDR_IN, which identifies the server to which we are attempting to connect (see "TCP/IP Addresses"). The namelen parameter is the length of the buffer used for the name parameter.
If you are successful in establishing a connection to the server specified by the name parameter, the function will return a 0; otherwise, a SOCKET_ERROR will occur. To find out more information about why a connection could not be established, call WSAGetLastError(). Remember that you cannot call connect() on a socket that is already connected.
Once a connection has been established, the socket is ready to send and receive data. Note that if a connection is broken during the course of communications between client and server, your application will need to discard the old socket and create a new one if it needs to reestablish communications.
The following example shows how to connect with a server:
// First, get the host information HOSTENT *hostServer = gethostbyname("www.microsoft.com"); if(hostServer == NULL) { int iSocketError = WSAGetLastError(); return FALSE; } // Set up the target device address structure SOCKADDR_IN sinServer; memset(&sinServer, 0, sizeof(SOCKADDR_IN)); sinServer.sin_family = AF_INET; sinServer.sin_port = htons(80); sinServer.sin_addr = *((IN_ADDR *)hostServer>h_addr_list[0]); // Connect with a valid socket if(connect(s, (SOCKADDR *)&sinServer, sizeof(sinServer)) == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE; } // Do something with the socket closesocket(s);
Now that we have established a connection to a server, we are ready to send and receive data between the two network endpoints. On a connection-oriented socket, data can be transmitted in either direction, so both client and server can use the same methods to communicate data over the wire.
To transmit data on a connected socket, you use the send() function, which is defined as follows:
int send (SOCKET s, const char *buf, int len, int flags);
The s parameter is the same socket handle that we previously used with the connect function, and was originally created using the socket() function. The buf parameter is a pointer to a buffer that contains the data we want to send, and its length is specified in the len parameter. The final parameter, flags, is used to affect the way the data is sent, and can be 0 or MSG_DONTROUTE, which specifies that the data should not be routed. Typically, this parameter will be set to 0, as MSG_DONTROUTE is used only for testing or routing messages.
When the send function returns, it will return the number of actual bytes that were sent over the network, or a SOCKET_ERROR if there was some problem in transmitting the data.
To receive data on a socket, you use the recv() function:
int recv (SOCKET s, char *buf, int len, int flags);
Again, s indicates the socket on which we want to receive data. The second parameter, buf, is the buffer that will receive the data; and its size is specified by the len parameter. Finally, the flags parameter must be set to 0.
The return value for the recv() function is either the number of bytes received or 0, if the connection has been closed. You may also get a SOCKET_ERROR if an error has occurred.
Note that both the send() and recv() functions do not always read or write the exact amount of data you have requested. This is because TCP/IP allocates a limited amount of buffer space for both the outgoing and incoming data queues, and it typically fills up rather quickly. For example, if you request a 10MB file from a Web site, your incoming data queue will block (see the section "Socket Options") until you have read the data from the queue (using the recv() function). The same applies when transmitting, so you need to manually ensure that all your outgoing data has been sent. For example, to send a buffer over TCP:
// Send a request to the server char cBuffer[1024] = ""; int nBytesSent = 0; int nBytesIndex = 0; // Set up the buffer to send sprintf(cBuffer, "GET / HTTP/1.0\r\n\r\n"); int nBytesLeft = strlen(cBuffer); // Send the entire buffer while(nBytesLeft > 0) { nBytesSent = send(s, &cBuffer[nBytesIndex], nBytesLeft, 0); if(nBytesSent == SOCKET_ERROR) break; // See how many bytes are left. If we still need to send, loop nBytesLeft -= nBytesSent; nBytesIndex += nBytesSent; }
The following example shows how to use TCP sockets to create a basic client that will connect to a Web page, send a command, and receive the Web site's default HTML page. When it has completed, it will display its contents in a message box. The actual buffer that is returned from the request is shown in Figure 1.3.
// Initialize Winsock WSADATA wsaData; memset(&wsaData, 0, sizeof(WSADATA)); if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0) return FALSE; // Create a connection-oriented socket SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Check to see if we have a valid socket if(s == INVALID_SOCKET) { int iSocketError = WSAGetLastError(); return FALSE; } // Get the host information HOSTENT *hostServer = gethostbyname("www.microsoft.com"); if(hostServer == NULL) { int iSocketError = WSAGetLastError(); return FALSE; } // Set up the target device address structure SOCKADDR_IN sinServer; memset(&sinServer, 0, sizeof(SOCKADDR_IN)); sinServer.sin_family = AF_INET; sinServer.sin_port = htons(80); sinServer.sin_addr = *((IN_ADDR *)hostServer>h_addr_list[0]); // Connect if(connect(s, (SOCKADDR *)&sinServer, sizeof(sinServer)) == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE; } // Send a request to the server char cBuffer[1024] = ""; int nBytesSent = 0; int nBytesIndex = 0; // Set up the buffer to send sprintf(cBuffer, "GET / HTTP/1.0\r\n\r\n"); int nBytesLeft = strlen(cBuffer); // Send the entire buffer while(nBytesLeft > 0) { nBytesSent = send(s, &cBuffer[nBytesIndex], nBytesLeft, 0); if(nBytesSent == SOCKET_ERROR) break; // See how many bytes are left. If we still need to send, loop nBytesLeft -= nBytesSent; nBytesIndex += nBytesSent; } // Get the response TCHAR tchResponseBuffer[1024] = TEXT("\0"); char cResponseBuffer[1024] = ""; BOOL fBreak = FALSE; int nBytesReceived = 0; while(!fBreak) { nBytesReceived = recv(s, &cResponseBuffer[0], 1024, 0); if(nBytesReceived == SOCKET_ERROR) break; // Convert the data from ANSI to Unicode mbstowcs(tchResponseBuffer, cResponseBuffer, nBytesReceived); // Show the MessageBox MessageBox(NULL, tchResponseBuffer, TEXT("Web Output"), MB_OK); // Check to see if this is the end of the HTTP response by // looking for \r\n\r\n if(_tcsstr(tchResponseBuffer, TEXT("\r\n\r\n"))) fBreak = TRUE; // Clear the buffers memset(tchResponseBuffer, 0, 1024); memset(cResponseBuffer, 0, 1024); } closesocket(s); WSACleanup();
The only real difference between transferring data between a client and a server stream connection is how the connection is established (a client makes the connection, a server listens for the connection). Otherwise, both use send() and recv() to transfer data between the two. Now that we have looked at the client, let's examine how we can create an application that services incoming connection requests (made by a client's call to the connect() function). The first thing we need to do is create a socket, in the same way you would a client, by calling the socket() function.
Once we have created a socket, instead of connecting to a server, we need to put our new socket into a state in which it can listen for incoming connections. To do this, we need to bind the newly created socket with a local address. Create this association by using the bind() function:
int bind (SOCKET s, const struct sockaddr *addr, int namelen);
The first parameter, s, is the handle to a new socket created by the socket() function, which will be the socket on which you want to wait for connections. The addr parameter is a pointer to an address buffer, which is determined by the protocol you want to use, and specifies protocol-specific address information. If you want to use the standard TCP/IP protocol, then you will want to use a SOCKADDR_IN buffer (see the section "TCP/IP Addresses"). If you are using infrared, you will use SOCKADDR_IRDA instead (see Chapter 5). Finally, namelen is the size of the address structure being passed in the addr parameter.
If there are no errors, bind() will return 0; otherwise, a SOCKET_ERROR will occur.
For example, the following binds a TCP connection on port 80 to a socket for all IP addresses on the device:
SOCKADDR_IN sListener; memset(&sListener, 0, sizeof(SOCKADDR_IN)); // Set up the port to bind on sListener.sin_family = AF_INET; sListener.sin_port = htons(80); sListener.sin_addr.s_addr = htonl(INADDR_ANY); // Create a TCP socket SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(s == INVALID_SOCKET) return FALSE; // Bind to the socket if(bind(s, (SOCKADDR *)&sListener, sizeof(sListener)) == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE; }
You may notice that I used the IP address INADDR_ANY instead of a specific adapter's IP address. Using INADDR_ANY enables us to bind our socket to all available IP addresses on our device, so that incoming connections on any interface will be accepted by our socket.
Once the socket has been bound to some address (or addresses), we need to put the socket into listening mode. This will actually enable the socket to wait for incoming connections:
int listen (SOCKET s, int backlog);
The parameter s is the bound socket. The backlog parameter specifies the size of the queue for pending incoming connections, and typically is set to SOMAXCONN (on Pocket PC, this is currently limited to two connections). The backlog queue is used when there are several simultaneous incoming connections. When the queue is full, all other requests will be refused until a connection request is removed from the queue by the accept() function.
If there is an error, the listen() function will return SOCKET_ERROR; otherwise, it will return 0.
Finally, to get the socket of the incoming connection, we need to call the accept() function, which is defined as follows:
SOCKET accept (SOCKET s, struct sockaddr *addr, int *addrlen);
The first parameter is the socket that we have previously placed into listening mode. The next parameter, addr, is a buffer that receives either a SOCKADDR_IN or SOCKADDR_IRDA structure, depending on the protocol used by the socket, which contains information about the incoming connection. The last parameter, addrlen, indicates the size of the structure addr.
You might notice that the accept() function does not return immediately. This is because accept() is a blocking function, which means that it won't return until a client makes a connection or the listening socket is destroyed (you can also set a socket option to put it into nonblocking mode, which is discussed in the section "Socket Options"). When accept() finally returns, it will return either a new socket handle for the incoming client, or a SOCKET_ERROR. All further communications with the client should be done using this new socket handle, while the original socket continues to listen for more incoming connections.
The following example listens for incoming TCP server connections for a client that is requesting a Web page using HTTP, and returns a basic response to the request:
// Initialize Winsock WSADATA wsaData; memset(&wsaData, 0, sizeof(WSADATA)); if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0) return FALSE; // Create a connection-oriented socket SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Check to see if we have a valid socket if(s == INVALID_SOCKET) { int iSocketError = WSAGetLastError(); return FALSE; } SOCKADDR_IN sListener; memset(&sListener, 0, sizeof(SOCKADDR_IN)); // Setup the port to bind on sListener.sin_family = AF_INET; sListener.sin_port = htons(80); sListener.sin_addr.s_addr = htonl(INADDR_ANY); // Bind to the socket if(bind(s, (SOCKADDR *)&sListener, sizeof(sListener)) == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE; } // Listen for incoming connections if(listen(s, SOMAXCONN) == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE; } // Wait for a connection SOCKADDR_IN sIncomingAddr; memset(&sIncomingAddr, 0, sizeof(SOCKADDR_IN)); int iAddrLen = sizeof(SOCKADDR_IN); SOCKET sIncomingSocket = accept(s, (SOCKADDR *) &sIncomingAddr, &iAddrLen); if(sIncomingSocket == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE; } // We have an incoming socket request char cResponseBuffer[1024] = ""; int nBytesReceived = 0; // Get a basic request. In reality, we would want to check // the HTTP request to see if it's valid, but let's just // send a simple response. nBytesReceived = recv(sIncomingSocket, &cResponseBuffer[0], 1024, 0); if(nBytesReceived == SOCKET_ERROR) { int iSocketError = WSAGetLastError(); return FALSE; } // Send out a response char cBuffer[1024] = ""; int nBytesSent = 0; int nBytesIndex = 0; // Setup the buffer to send sprintf(cBuffer, &"HTTP/1.0 200 OK\r\n\r\nTest Response\r\n\r\n"); int nBytesLeft = strlen(cBuffer); // Send the entire buffer while(nBytesLeft > 0) { nBytesSent = send(sIncomingSocket, &cBuffer[nBytesIndex], nBytesLeft, 0); if(nBytesSent == SOCKET_ERROR) break; // See how many bytes are left. If we still need to send, loop nBytesLeft -= nBytesSent; nBytesIndex += nBytesSent; } // Close the sockets closesocket(sIncomingSocket); closesocket(s); WSACleanup();
Once you are finished using a socket, whether you are on a server or a client, you must release the device resources that are associated with that socket.
Before you actually close the socket, you should call the shutdown() function. You can destroy a socket by directly closing it, but you are better off calling shutdown() first because it ensures that all data in the TCP/IP transfer queue is sent or received before the socket is closed:
int shutdown (SOCKET s, int how);
The handle to the socket, s, is the first parameter we pass in to this function. The how parameter specifies how subsequent socket functions are processed on this socket, and can be set to SD_RECEIVE, SD_SEND, or SD_BOTH. Setting this to SD_RECEIVE will prevent any further recv function calls from being completed, and SD_SEND will prevent any further send calls. Obviously, SD_BOTH will stop sending and receiving for the socket (however, all data already queued will be processed).
If there are no errors, shutdown() will return 0. Once a socket has been shutdown(), you cannot use it again, except to close it with the closesocket() function:
int closesocket (SOCKET s);
The only parameter that closesocket() takes is the handle to the socket descriptor you want to close.