Network Game Programming - Issue 03 - cClient. cClient run by Dan Royer (07 July 1999) | Return to The Archives

Introduction

Before we get into the nitty gritty burlesque code you all came for, I'd like to thank John Marlon for pointing out two oversights in the previous articles. First, SM_GETHOST is defined by you as (WM_USER+1) or any constant greater than WM_USER. Second, there are some files you will need in order to compile Winsock code. You'll need to include the library wsock32.lib in your project linking and you'll have to #include < winsock2.h >. The really important thing to remember is that winsock2.h has to be include before windows.h because it overrides some default windows things.
Okely Dokely, let's get down to business. We've learned how to open a socket and ...assemble all the information we'll need to connect said socket to another computer somewhere out in cyberspace. Now to do the actual CONNECTING.

    // This isn't what winsock 2.0 recommends but I
    // couldn't get it to compile using their system...
    int cComm::AddressHost() {
      LPHOSTENT lphostent;

      lphostent = (LPHOSTENT)d_hostentbuf;

      d_address.sin_addr = *(LPIN_ADDR)lphostent->h_addr;

      // There will be more here later...

      return 0;
    }

    // Tells winsock which window to send messages to.
    // Tells winsock what message id should be.
    // I set it up this way so that you could call
    // it as often as necessary (but try to avoid it)
    int cComm::ChangeWindows( HWND hwnd ) {
      d_hwnd = hwnd;

      return WSAAsyncSelect( d_socket, d_hwnd, SM_ASYNC, d_socketFlags );
    }

    // Only tries to connect once.
    int cComm::AttemptConnect() {
      return connect( d_socket, (LPSOCKADDR)&d_address, sizeof( SOCKADDR_IN ) );
    }

Ok, let's break that down. d_address.sin_addr is filled in with the address data returned to OnGetHost(). Next, winsock is told which window to send future SM_GETHOST type messages to and which message the window wants. This is a set of defines ORed together, typically FD_CONNECT | FD_CLOSE | FD_READ | FD_WRITE for a client. Lastly, a connection attempt is made. With all this new stuff our OnGetHost() will end up looking something like this:

      // old OnGetHost() stuff

      if( ChangeWindows( gameWindow ) == SOCKET_ERROR ) {
        // ARROOOGA! ARROOOGA!  DIVE, DIVE!
        Disconnect();
      }
      if( AddressHost() ) {
        // uh oh...
        Disconnect();
      }
      if( AttemptConnect() ) {
        // ARGH!
        Disconnect();
      }

I deliberately left this out of the first version of OnGetHost() because server and client OnGetHost() have small but crucial differences. So what comes next? Well, connect() does the same annoying thing that GetHost...() does: it returns immediately and sends a message to the window specified in ChangeWindows() when it finishes the work. The message SM_ASYNC is another defined constant, probably (WM_USER+2). In my applications when I get an SM_ASYNC message I immediately call OnAsync(), as follows:

    // Remember: This is the CLIENT version of OnAsync().
    LRESULT cComm::OnAsync( WPARAM wparam, LPARAM lparam ) {
      int event, errmsg;

      event = WSAGETSELECTEVENT( lparam );
      errmsg = WSAGETSELECTERROR( lparam );
      switch( event ) {
        case FD_CONNECT:
          // The socket associated with this window has made a connect() attempt
          // and the results are back.
          break;
        case FD_READ:
          // The first time this message is sent means the socket can be read from.
          // After that it means there is data to be read.  If you call
          // ChangeWindows() the first message to the new window will also be to
          // tell that the socket can be read from.
          break;
        case FD_WRITE:
          // The first time this message is sent means the socket can send data.
          // After that it means that the buffer of data to be sent now has room
          // to send some more.  If you call ChangeWindows() the first message to
          // the new window will also be to tell that the socket can send data.
          break;
        case FD_CLOSE:
          // The connection on the socket is being closed, either by the local
          // machine, the remote machine or because of loss of network (ie phone
          // line was unplugged).  We were very bad earlier but I had to so that
          // the axious among you wouldn't leave a billion open sockets.  You
          // should call shutdown() and it will CAUSE an FD_CLOSE message where
          // you then closesocket().  Still, leave the old code in and after
          // every call to closesocket() set the socket to 0.  That way if the
          // socket is 0 you know there's nothing to close.
          break;
      }
    }

And now all you have to do is start trading data after you interpret the error messages (error message? What error messages? I don't know what you're talking about...(=). Of course, the server isn't LISTENING for new connection attempts, so until then all connect() will fail. Two guesses what we're going to cover next week. Until then, remember: nothing pisses people in a crowded elevator off more than whistling the first seven notes of "it's a small world after all."

Article Series:

> - Network Game Programming - Issue 01 - Things that make you go "hmm..."
> - Network Game Programming - Issue 02 - cComm one, cComm all
> - Network Game Programming - Issue 03 - cClient. cClient run
> - Network Game Programming - Issue 04 - cServer? I barely know her!
> - Network Game Programming - Issue 05 - Watch your Language
> - Network Game Programming - Issue 06 - Talk about things you like to do...
> - Network Game Programming - Issue 07 - I bent my Wookie...

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.