Originally published on my personal blog at: last post I covered the basics of how TCP/IP works and explained why I have decided to use UDP. In this post I’ll be covering how to create a socket and send/receive data using it. Let’s dive right in shall we?
Working with Winsock
As I described in my last post, we’ll be working with Winsock in this series. The code should port relatively easily to other platforms.
The very first thing we need to do when working with Winsock is to include the correct header. The header is WinSock2.h.
#include <WinSock2.h>
We also need to link to the Winsock2 library, you need to specify ws2_32.lib as an additional dependency on your linker input.
Starting up and Shutting down Winsock
When working with Winsock you are required to initiate the Winsock DLL for use in your process. This is achieved with a call to WSAStartup. We will be using Winsock 2.2, so we need to tell Windows that’s the version of Winsock we’d like to load.
bool Initialise() { ::WSADATA wsaData = { 0 }; int startupResult = ::WSAStartup(MAKEWORD(2,2), &wsaData); return startupResult == ERROR_SUCCESS; }
Once you are finished with Winsock you are also required to shut it down, this is achieved with a call to WSACleanup.
bool Shutdown() { int shutdownResult = ::WSACleanup(); return shutdownResult == ERROR_SUCCESS; }
Initialising a Socket
As Winsock is ready to be used, we can create a socket. We’ve decided to use UDP so we’ll be creating a UDP socket with a call to the socket function.
SOCKET socketHandle = ::socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if(socketHandle != INVALID_SOCKET) { // Socket was created successfully } else { // Socket creation failed }
As you can see, we are passing three arguments. The first argument represents the protocol family we wish to use, we’ll be using IPv4 which is represented by PF_INET, we could also use PF_INET6 if we were targeting IPv6. The second argument is the type of socket we wish to create, SOCK_DGRAM describes a datagram socket, the type required for UDP. The final argument represents the protocol we’ll be using which is UDP.
Binding a Socket
Now that we have a UDP socket, before we can use it we need to bind it to a port with a call to the bind function.
We’ll take our previous example of creating a socket and add the binding functionality.
SOCKET socketHandle = ::socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if(socketHandle != INVALID_SOCKET) { ::sockaddr_in inAddress = { 0 }; inAddress.sin_family = AF_INET; inAddress.sin_addr.s_addr = INADDR_ANY; inAddress.sin_port = htons(portNumber); int bindResult = ::bind(socketHandle, (SOCKADDR*) &inAddress, sizeof(inAddress)); if(bindResult != SOCKET_ERROR) { // Socket bound to port } else { // Failed to bind the socket to a port. // We should close the socket here usually. } } else { // Socket creation failed }
As you can see, once the socket has been created the first thing we need to do is create a sockaddr_in structure, this tells Winsock which port and address we’d like to bind to.
The sin_family variable describes which address family we’d like to use. sin_addr.s_addr tells Winsock which address we would like to use, in our case we don’t really mind so we’ll let Winsock pick for us. Next we need to specify the port number we’d like to bind to in the sin_port variable. The call to htons means Host to Network Short, network byte order is always big-endian, so any integers passed to Winsock need to be in big-endian format, the call to htons detects the host byte order and changes it to network order if required.
Finally we call the bind function and check that we bound successfully, only one socket can be bound to a given port at any one time.
Sending Packets
As we now have a bound socket we can send a packet. As there is no connection in UDP you need to specify the destination every time you want to send a packet. IPv4 addresses are typically represented as a group of 4 unsigned 8bit integers such as 72.14.204.104 (A google.com address if you were wondering). Winsock represents IP addresses as an unsigned 32bit integer, so we need to do a little fiddling to change the standard representation to something Winsock can understand. You can do this with the following operations:
(72 << 24) | (14 << 16) | (20 << 8) | 104
Now that we know how to do it, we’ll wrap it up into a helper function.
unsigned int CreateAddress(unsigned char a, unsigned char b, unsigned char c, unsigned char d) { unsigned int newAddress = (a << 24) | (b << 16) | (c << 8) | d; return newAddress; }
To actually send data, we make a call to the sendto function.
bool Send(SOCKET socketHandle, unsigned int destinationAddress, unsigned short destinationPort, const char* p_data, u32 dataSize) { if(socketHandle != INVALID_SOCKET) { ::sockaddr_in destination = { 0 }; destination.sin_family = AF_INET; destination.sin_port = htons(destinationAddress); destination.sin_addr.s_addr = htonl(destinationPort); int sentLength = ::sendto(socketHandle, p_data, dataSize, 0, (sockaddr*)&destination, sizeof(destination)); return sentLength != SOCKET_ERROR; } return false; }
We need to construct another sockaddr_in, but this time we’re specifying the destination of the packet. You’ll notice a call to htonl, this does the same thing as htons for longs. Once this is done, we can make the call to sendto, the arguments are the handle of the socket we wish to use to send the packet, a char* of the data we wish to send, the length of the data we wish the send, the flags to use for sending this, the destination address and the length of the destination address.
sendto returns the amount of data sent (which is always the full amount when using UDP) or SOCKET_ERROR when the data fails to be sent, it’s important to remember that it doesn’t tell you if the data actually reaches it’s destination, UDP offers no guaranteed delivery.
Receiving Packets
Next we’ll take a look at receiving packets, to read a packet we use the recvfrom function. You can receive packets from multiple different remote sockets and the recvfrom call will give you the remote address.
bool Receive(SOCKET socketHandle, unsigned int& outAddress, unsigned short& outPort, char* p_outData, u32& outSize, u32 dataBufferSize) { if(socketHandle != INVALID_SOCKET) { ::sockaddr_in fromAddress = { 0 }; int fromAddressLength = sizeof(fromAddress); int receivedLength = ::recvfrom(socketHandle, p_outData, dataBufferSize, 0, (sockaddr*) &fromAddress, &fromAddressLength); if(receivedLength != SOCKET_ERROR && receivedLength > 0) { outSize = receivedLength; outAddress = ntohl(fromAddress.sin_addr.s_addr); outPort = ntohs(fromAddress.sin_port); return true; } } return false; }
First we create another sockaddr_in structure which will be filled with details of the remote peer. We also need to specify a buffer we’d like to fill. recvfrom returns the amount of data received or SOCKET_ERROR when an error is encountered. You shouldn’t rely on the remote address to identify a connection, we’ll cover that in a future post.
There is one problem though, as the code currently stands it will block on the recvfrom function call until some data is received, as we’re currently running this on our main thread that’s not particularly suitable for our needs.
Blocking vs Non-Blocking
By default a socket is created in blocking mode, as discussed in the last section this means the code will block in the recvfrom call. Luckily, Winsock provides a method to stop the socket blocking and make it return immediately. The method we will be using is ioctlsocket , this allows us to set modes on the socket, in this case the mode we are interested in is FIONBIO.
You can enable/disable blocking as follows:
bool SetBlocking(SOCKET socketHandle, bool blocking) { if(socketHandle != INVALID_SOCKET) { DWORD nonBlocking = blocking ? 0 : 1; int setBlockingResult = ::ioctlsocket(socketHandle, FIONBIO, &nonBlocking); if(setBlockingResult == ERROR_SUCCESS) { return true; } } return false; }
Closing a Socket
Finally, we need to clean up after ourselves and close the socket. We can do this with the closesocket function.
void Close(SOCKET& socketHandle) { if(socketHandle != INVALID_SOCKET) { ::closesocket(socketHandle); socketHandle = INVALID_SOCKET; } }
Wrapping all of this up
Now that we’ve covered the basic of sockets, we want to wrap all of this up in an easy to use form. I’ve created a library which is a Visual Studio 2010 project that wraps all of this into an easy to use form. It’s licensed under the Microsoft Public Licence so should be suitable for most uses.
Example:
#include "NetLib.hpp" #include "socket/NetSocketWrapper.hpp" #include "socket/NetSocketAddress.hpp" int _tmain(int argc, _TCHAR* argv[]) { NNetLib::u16 localPort = 4566; NNetLib::u16 remotePort = 4567; if(argc > 2) { localPort = _wtoi(argv[1]); remotePort = _wtoi(argv[2]); } std::cout << "##########################" << std::endl; std::cout << "Local Port: " << localPort << std::endl; std::cout << "Remote Port: " << remotePort << std::endl; std::cout << "##########################" << std::endl << std::endl; // Initialise our network library, including Winsock if(NNetLib::StartNetLib()) { std::cout << "Network library initialised..." << std::endl; NNetLib::CSocketWrapper socket; // Create and bind our socket if(socket.Create(localPort)) { // Set the socket to non-blocking for this example socket.SetBlocking(false); std::cout << "Socket created..." << std::endl; NNetLib::u32 remoteAddress = NNetLib::CSocketAddress::CreateAddress(127, 0, 0, 1); NNetLib::CSocketAddress remoteSocket(remoteAddress, remotePort); while(true) { // Send some data const std::string dataToSend = "Welcome to the world of tomorrow!"; if(socket.Send(remoteSocket, dataToSend.c_str(), dataToSend.length())) { std::cout << "Sent " << dataToSend.length() << " bytes of data to port " << remoteSocket.GetPort() << std::endl; } // Receive data bool moreData = true; do { char dataOut[1024]; NNetLib::CSocketAddress receiverSocket; NNetLib::u32 receiveSize = 0; moreData = socket.Receive(receiverSocket, dataOut, receiveSize, sizeof(dataOut)); if(moreData) { std::cout << "Received " << receiveSize << " bytes of data from port " << receiverSocket.GetPort() << std::endl; } } while (moreData); // Dirty, but let's sleep in this example ::Sleep(1000); } // Close the socket socket.Close(); } } // Shutdown the network library, including winsock NNetLib::ShutdownNetLib(); return 0; }
.
In my next post we’ll be looking at how to serialise data.
Further Reading