Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
88 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Introduction
 Architecture
 On to WinSock
 The Basics
 Socket Types
 Error Checking
 The price you
 pay...

 Rock, Paper,
 Scissor, Shoot!

 How to use
 the demo

 A word on lag
 Conclusion

 Demo
 Printable version

 


The basics

To load the ws2_32.dll use this code:

// Must be done at the beginning of every WinSock program // used to store information about WinSock version WSADATA w; int error = WSAStartup (0x0202, &w); // Fill in w if (error) { // there was an error return; } if (w.wVersion != 0x0202) { // wrong WinSock version! WSACleanup (); // unload ws2_32.dll return; }

You may be wondering what 0x0202 means. It means version 2.2. If I wanted version 1.1, I'd change it to 0x0101. WSAStartup () fills in the WSADATA structure and loads the WinSock2 dynamic link library. WSACleanup() unloads the WinSock DLL.

To create a socket:

SOCKET s = socket (AF_INET, SOCK_STREAM, 0); // Create socket

That's all you need to create a socket, but you'll have to bind it to a port later when you want to actually use it. AF_INET is a constant defined somewhere in winsock2.h. If there is ever a function that requires you to tell it something about the address family (or int af), then just say AF_INET. SOCK_STREAM is a constant that tells Winsock that you want a stream (TCP/IP) socket. You can also have data gram (UDP) sockets, but they are unreliable. Leave the last parameter as 0, this will just select the correct protocol for you (which should be TCP/IP).

To actually assign a port to a socket (or bind a socket):

// Note that you should only bind server sockets, not client sockets // SOCKET s is a valid socket // WSAStartup has been called sockaddr_in addr; // the address structure for a TCP socket addr.sin_family = AF_INET; // Address family Internet addr.sin_port = htons (5001); // Assign port 5001 to this socket addr.sin_addr.s_addr = htonl (INADDR_ANY); // No destination if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) { // error WSACleanup (); // unload WinSock return; // quit }

This may look confusing, but it's not that bad. addr describes our socket by specifying the port. What about the IP address? We set that to INADDR_ANY, which allows it to be any IP address, since we don't really care about the IP address if we are just telling WinSock which port we want our side of the connection to be. Why do we use htons () and htonl ()? These will convert short and long, respectively, to the correct format for the network to understand. If we have the port number 7134 (which is a short), then we use htons (7134). We have to use htonl () on the IP address. But what if we want to actually specify the IP address? We don't use htonl (), we use inet_addr (). For example inet_addr ("129.42.12.241"). inet_addr parses the string and takes out the periods (".") and then converts it into a long.

To listen at the bound port:

// WSAStartup () has been called // SOCKET s is valid // s has been bound to a port using sockaddr_in sock if (listen(s,5)==SOCKET_ERROR) { // error! unable to listen WSACleanup (); return; } // listening…

Now we just have to accept a connection once some client tries to connect. The only peculiar thing about the above code is listen (SOCKET s, int backlog). What is this backlog? Backlog means the number of clients that can connect while the socket is being used. That means that these clients will have to wait until all clients before him have been dealt with. If you specify a backlog of 5 and seven people try to connect, then the last 2 will receive an error message and should try to connect again later. Usually a backlog between 2 and 10 is good, depending on how many users are expected on a server.

To try and connect to a socket:

// WSAStartup () has been called // SOCKET s is valid // s has been bound to a port using sockaddr_in sock sockaddr_in target; target.sin_family = AF_INET; // address family Internet target.sin_port = htons (5001); // set server's port number target.sin_addr.s_addr = inet_addr ("52.123.72.251"); // set server's IP if (connect(s, target, sizeof(target)) == SOCKET_ERROR) { // an error connecting has occurred! WSACleanup (); return; }

That's all you have to do to request a connection! target obviously defines the socket that you are trying to connect to. The connect () function requires a valid socket (s), the description of the target socket (target), and the size or length of the description (sizeof(target)). This function will just send a connection request and then wait to be accepted or report any occurring errors.

Accepting a connection:

// WSAStartup () has been called // SOCKET s is valid // s has been bound to a port using sockaddr_in sock // s is listening #define MAX_CLIENTS 5; // just used for clearness int number_of_clients = 0; SOCKET client[MAX_CLIENTS]; // socket handles to clients sockaddr client_sock[MAX_CLIENTS]; // info on client sockets while (number_of_clients < MAX_CLIENTS) // let MAX_CLIENTS connect { client[number_of_clients] = // accept a connection accept (s, client_sock[number_of_clients], &addr_size); if (client[number_of_clients] == INVALID_SOCKET) { // error accepting connection WSACleanup (); return; } else { // client connected successfully // start a thread that will communicate with client startThread (client[number_of_clients]); number_of_clients++; } }

I hope you can follow that. MAX_CLIENTS isn't really necessary, but I just use it to make the code cleaner and simpler for demonstrative purposes. number_of_clients is a counter that keeps track of how many clients are connected. client[MAX_CLIENTS] is an array of SOCKETs which is used to save the handles of the sockets that are connected to the clients. client_sock[MAX_CLIENTS] is an array of sockaddr that is used to keep information about the type of connection, what port, etc. Usually, we don't want to mess with client_sock, but a bunch of functions will require it as a parameter. Basically this loop just waits until someone requests a connection, then it accepts it and starts a thread that communicates with the client.

Writing (or sending):

// SOCKET s is initialized char buffer[11]; // buffer that is 11 characters big sprintf (buffer, "Whatever…"); send (s, buffer, sizeof(buffer), 0);

Parameter two of send () is const char FAR *buf and it points to the buffer of chars that we wish to send. Parameter three is an int and it is the length (or size) of the buffer we are sending. The last parameter is for flags that we will never use; keep it 0.

Reading (or receiving):

// SOCKET s is initialized char buffer[80]; // buffer that is 80 characters big recv (s, buffer, sizeof(buffer), 0);

recv () is pretty much the same as send, except that this time, we are not transmitting a buffer, but receiving it.

Resolving an IP address or URL:

// const char *Host contains either a IP address or a domain name u_long addr = inet_addr(Host); // try and parse it if it is an IP address if (addr == INADDR_NONE) { // Host isn't an IP address, try using DNS hostent* HE = gethostbyname(Host); if (HE == 0) { // error: Unable to parse! WSACleanup (); return; } addr = *((u_long*)HE->h_addr_list[0]); }

Although it may be hard to understand at first, the code isn't actually that complicated. What the code does is try to parse const char *Host into u_long addr, which we can use to connect to another computer.

To close a socket:

shutdown (s, SD_SEND); // s cannot send anymore // you should check to see if any last data has arrived here closesocket (s); // close

I know it seems stupid that you must call two functions and then check if any more data has been received to close a socket, but that's life. shutdown(SOCKET s, int how) locks a specific attribute of a socket. Here are the possible attributes (that are passed in the how parameter:

  • SD_SEND means that the socket cannot send anymore

  • SD_RECEIVE means that the socket cannot receive anymore

  • SD_BOTH means that the socket cannot send or receive




Next : Blocking, non-blocking, and asynchronous sockets