Thread: async Client/Server app, accept() stalls?

  1. #1
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972

    Question async Client/Server app, accept() stalls?

    First off, I am very inexperienced with network programming, but I thought I'd try my hand with asynchronous sockets. So, after reading some of this tutorial, I made a program that should be able to connect or wait for a connection. So I made a network class, and simply set it to connect to my local ip address if a button is pressed, or set to listen mode if the other button is pressed.

    I can tell something is working, because I receive a FD_ACCEPT message on the instance I set to listen, but then my program never seems to reach any code after I call accept():

    My window proc:
    Code:
    LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)                  /* handle the messages */
        {
          case WM_CREATE:
            CreateWindowEx(0,                                      //more or 'extended' styles
                           TEXT("BUTTON"),                         //'class' of control to create
                           TEXT("Connect"),            //the control caption
                           WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON,   //control style: how it looks
                           10,                                     //control position: left
                           10,                                     //control position: top
                           100,                                    //control width
                           20,                                     //control height
                           hwnd,                                   //parent window handle
                           (HMENU)IDC_CONNECT,                                   //control's ID
                           g_hInst,                                //application instance
                           NULL); 
            CreateWindowEx(0,                                      //more or 'extended' styles
                           TEXT("BUTTON"),                         //'class' of control to create
                           TEXT("Wait"),            //the control caption
                           WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON,   //control style: how it looks
                           120,                                     //control position: left
                           10,                                     //control position: top
                           100,                                    //control width
                           20,                                     //control height
                           hwnd,                                   //parent window handle
                           (HMENU)IDC_SERVER,                                   //control's ID
                           g_hInst,                                //application instance
                           NULL);  
                           
          break;
          case WM_WSAASYNC:
          // what word?
            
            switch(WSAGETSELECTEVENT(lParam))
            {
              case FD_ACCEPT:
                MessageBox(NULL,"Connection request received","Alert!",MB_OK); //this comes up
                // check for an error
                if (WSAGETSELECTERROR(lParam))
                  return(FALSE);
                // process message
                try
                {
                netwkobj.Accept();
                }
                catch (const NetErr& error)
                {
                  MessageBox(NULL,error.what(),"Network Error",MB_OK|MB_ICONEXCLAMATION);
                }
                return(0);
              break;
              case FD_READ:
               //receiving data
              break;
              case FD_WRITE:
              //..
              break;
              case FD_CONNECT:
              //server got our connect message
                MessageBox(NULL,"Connect request accepted","Alert!",MB_OK); // this never comes up
              break;
              case FD_CLOSE:
              //connection closed
                PostQuitMessage(0); //just quit for now...i guess
              break;
            }
            
          break; 
          case WM_COMMAND:
            switch(LOWORD(wParam))
            {
              case IDC_CONNECT: //button pressed: try to connect to a server
                try
                {
                  netwkobj.Init(hwnd,false); 
                  netwkobj.Connect("192.168.2.15");
                }
                catch (const NetErr& error)
                {
                  MessageBox(NULL,error.what(),"Network Error!",MB_OK|MB_ICONEXCLAMATION);
                }
                //MessageBox(NULL,"Connected","OK",MB_OK);
              break;
              case IDC_SERVER:
                try
                {
                  netwkobj.Init(hwnd,true); //move this to when we try to join or host a game in a menu
                }
                catch (const NetErr& error)
                {
                  MessageBox(NULL,error.what(),"Network Error!",MB_OK|MB_ICONEXCLAMATION);
                }
            //    MessageBox(NULL,"Listening...","OK",MB_OK);
              break;
            }
          break;    
          case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
          break;
          default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
        }
    
        return 0;
    }
    Member functions:
    Code:
    //network class member fcns
    Network::Network()
    {
    hSocket = INVALID_SOCKET;
    numclients=0;
    
    }
    
    void Network::Init(HWND hwnd, bool isserver) 
    {
    init = true;
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(REQ_WINSOCK_VER,0), &wsaData)==0) //start winsock
    {
    // Check if major version is at least REQ_WINSOCK_VER
      if (LOBYTE(wsaData.wVersion) < REQ_WINSOCK_VER)
      {
        throw NetErr("Required Winsock Version not met!");
      }
    }  
    else
      throw NetErr("Error starting Winsock!");
    // create and test the socket
    hSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (hSocket == INVALID_SOCKET)
      throw NetErr("INVALID_SOCKET");
    // make the socket asynchronous and notify of read, write, connect and close events
    // this is the client socket
    if (!isserver)
    {
    WSAAsyncSelect(hSocket, hwnd, WM_WSAASYNC, FD_WRITE | FD_CONNECT |
                                            FD_READ | FD_CLOSE); //set up asynchronous sockets
                                //request WRITE, READ, CONNECT, and CLOSE messages be sent
    server=false;
    }
    // make the socket asynchronous and notify of read, write, accept and close events
    // this is the server socket
    else
    {
    //is this the right order? (set to listen before call to WSAAsyncSelect()?
    
    sockaddr_in    sockAddr = {0};
    SetServerSockAddr(&sockAddr, SERVER_PORT);
    if (bind(hSocket, reinterpret_cast<sockaddr*>(&sockAddr), sizeof(sockAddr))!=0) 
      throw NetErr("Could not bind socket.");
    if (listen(hSocket, SOMAXCONN)!=0) //set socket to listen for incoming data
      throw NetErr("Could not put socket into listening mode.");
    
    WSAAsyncSelect(hSocket, hwnd, WM_WSAASYNC, FD_READ | FD_WRITE |
                                            FD_ACCEPT | FD_CLOSE); //again, async sockets
    
    
      
                                            
    server=true;
    }
    
    }
    
    void Network::Connect(const char* servIp)
    {
    if (!init)
      throw NetErr("Network not initialized!");
    sockaddr_in    sockAddr = {0};
    FillSockAddrDot(&sockAddr, servIp, SERVER_PORT);
    
    if ((hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
      throw NetErr("Could not create socket.");
            
            // Connect to server
    if (connect(hSocket, reinterpret_cast<sockaddr*>(&sockAddr), sizeof(sockAddr))!=0)
      throw NetErr("Could not connect.");
    
    
    }
    
    void Network::Accept()
    {
    if (!init)
      throw NetErr("Network not initialized!");
      int clientSockSize = sizeof(client_addr);
      client_sock[0] = accept(hSocket,reinterpret_cast<sockaddr*>(&client_addr), &clientSockSize);
    //I never get an exception thrown, and if I put a messagebox here
    //it never comes up
      if (client_sock[numclients]==INVALID_SOCKET)
        throw NetErr("Could not accept incoming connection");
      numclients++;  
    }
    As I said, it seems to be working up to that point (I get a "could not connect" exception if I try to connect without setting another instance to wait, and if I set one to wait, when I click "connect" the other instance receives an FD_ACCEPT message)

    Hopefully I'm just doing something stupid, since this is my first try with asynchronous sockets.

    Edit: Boy, I feel stupid. I guess WSAGETSELECTERROR is telling me there's an error. Somehow I just glanced over that line. Should've put some kind of notification there. Now I just gotta figure out what the error means...

    Edit2: MSDN doesn't have any error codes listed for the FD_ACCEPT message, and it returns error code 8...If I just continue like there's no problem, accept finished like there isn't a problem, but my client doesn't receive an FD_CONNECT message.

    Edit3: Yes, another edit. Ok so now I realize that there aren't actually any error (stupid tutorial told me wrong) But I still don't receive a FD_CONNECT message...I think I'll take a break on this now...
    Last edited by JaWiB; 01-27-2005 at 10:07 PM.
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  2. #2
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    You'll probably spot this when you look at the code again but...

    1. You are sharing the one netwkobj (and by extension the one socket variable) for both your client and server. This means that when you initiate the client you leak the server socket handle and replace it with the client socket handle. So when FD_ACCEPT arrives you are calling accept on the client socket handle.

    2. Only call WSAStartup once at program startup.

  3. #3
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    I'm not sure I follow...I opened two instances of the application and click "wait" on one and "connect" on the other, so each open app should have it's own netwkobj, right? I also realize that if I press wait or connect twice, it won't work correctly, but I just don't do that (I'll probably fix that later)

    Maybe I just didn't explain myself well, or maybe I don't quite understand what you're saying
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  4. #4
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    Yes, I thought this was all in the one instance. However, there is a similar problem. In the Network::Connect function you are creating a new socket and leaking the old one. This new socket is blocking and so the connect call will block. However, this doesn't explain why no conenction is established. Are you running any firewall software that may be blocking the accept call, for example waiting on user approval?

  5. #5
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    Turns out that calling socket() twice and leaking my socket was the problem after all. Can't see how I missed that one.

    So now I get an FD_CONNECT message, but I can't seem to send or receive data. I went back to the tutorial I was reading and it says you need to fill the send buffer before it will actually send anything. So, I wanted to change the size of the buffer so that I don't have to send 8192 bytes or whatever it is. So I tried this:

    Code:
    void Network::Send(const char* data)
    {
    
      int bOptVal = strlen(data);
      int bOptLen = sizeof(int);
      setsockopt(hSocket,SOL_SOCKET, SO_SNDBUF, (char*)&bOptVal, bOptLen); 
      while(TRUE) {
            if (send(hSocket, data, strlen(data), 0) == SOCKET_ERROR) { //send the whole read buffer
    
                if (WSAGetLastError() != WSAEWOULDBLOCK) {
                    
                      throw NetErr("Failed to send data.");
                    
                }
                else {
                    return; //we have filled the buffer, we will wait for another FD_WRITE...
    
                }
            }
    
    }
    }
    Which is adapted from some code on the discussion board for the tutorial I was reading (here). Here's what the person said:
    To fix this problem, the trick is to send BufferSize bytes. BufferSize is an unsigned integer that contains the size of the winsock buffer which is availabe with the getsockopt() function. you can use setsockopt() if you want to change its size.

    however, the problem is that even though the default reported buffer is 8192 bytes, sending 8192 bytes won't cause a WSAEWOULDBLOCK. Not even sending 8193 bytes. I don't know how much the real buffer size is, but I can tell that its less than 8192 * 2 because when I send the data two times on the same socket it creates a WSAEWOULDBLOCK error and the excedent data is discarded (which means that if you're sending a file it won't be damaged because a packet is not sent 2 times).
    Maybe I'm just calling setsockopt wrong

    And my message handler:
    Code:
     case FD_WRITE:
               try
                {
                netwkobj.Send("Some data");
                }
                catch (const NetErr& error)
                {
                  MessageBox(NULL,error.what(),"Error!",MB_OK);
                }
                MessageBox(NULL,"Data sent","Completed",MB_OK);
              break;  
     case FD_READ:
              {
               //receiving data
               char tempbuf[TEMP_BUFFER_SIZE];           
               int bytes_recv = recv(wParam, tempbuf, sizeof(tempbuf), 0);
               
               MessageBox(NULL,tempbuf,"Received",MB_OK);
               break;
              }

    Any more bright ideas?
    Last edited by JaWiB; 01-28-2005 at 06:56 PM.
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  6. #6
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    >> I went back to the tutorial I was reading and it says you need to fill the send buffer before it will actually send anything. <<

    No, small packets will be sent. There may be a sub-second delay so any subsequent sends can be combined. See Nagle algorithm. You don't need to change the send buffer size unless you want to send larger amounts.

    I can't see any problems with your code except that you are not nul terminating the received data:
    Code:
               int bytes_recv = recv(wParam, tempbuf, sizeof(tempbuf) - 1, 0);
               
               if (bytes_recv >= 0) tempbuf[bytes_recv] = '\0';
    
               MessageBox(NULL,tempbuf,"Received",MB_OK);
    Are you getting any errors? What's happening?

  7. #7
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    Ok so after doing some re-reading I guess I don't need to handle the FD_WRITE message at all (I can just send data when I need to), is that correct?

    And no I don't get any errors. As far as I can tell, the data is sent correctly, but I never get an FD_READ message (even if I put a messagebox before calling recv() it won't pop up)
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  8. #8
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    >> Ok so after doing some re-reading I guess I don't need to handle the FD_WRITE message at all (I can just send data when I need to), is that correct? <<

    No, you send until you receive a WSAEWOULDBLOCK and then you have to wait for a FD_WRITE before you can issue another send. Your sending seems find except you don't need to change the send buffer size.

    I don't know why it's not working. If you post something compilable, I can try it out.

  9. #9
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    For some reason on this computer I only receive the FD_WRITE message once when I connect, and then it doesn't come up again. But when I tried it on another computer I kept getting the FD_WRITE message, but still no FD_READ message. And if I need to fill up the buffer before I receive FD_WRITE again, can I still send small chunks of data, or is there another way of doing it?

    Anyways, I'll attach my code; hopefully my error is painfully obvious
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  10. #10
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    I think I might have figured it out. Here's what threw me (from msdn):
    The newly created socket is the socket that will handle the actual connection; it has the same properties as socket s, including the asynchronous events registered with the WSAAsyncSelect or WSAEventSelect functions.
    Maybe I just interpreted it wrong, but it sounds like you shouldn't have to call WSAAsyncSelect for the socket that accept returns. However, changing my accept function like so:
    Code:
    void Network::Accept()
    {
    if (!init)
      throw NetErr("Network not initialized!");  
    if (numclients>=MAX_CONNECTIONS)
      throw NetErr("Too many connections"); //figure out how to handle this better later  
      int clientSockSize = sizeof(client_addr);
      client_sock[numclients] = accept(hSocket,reinterpret_cast<sockaddr*>(&client_addr), &clientSockSize);
      if (WSAAsyncSelect(client_sock[numclients], chwnd, WM_WSAASYNC,  FD_CONNECT | FD_WRITE |
                                            FD_READ | FD_CLOSE)!=0) //set up asynchronous sockets
                                //request WRITE, READ, CONNECT, and CLOSE messages be sent
      {
        throw NetErr("WSAAsyncSelect() Failed!");
      }
      if (client_sock[numclients]==INVALID_SOCKET)
        throw NetErr("Could not accept incoming connection");
      numclients++; 
    }
    Caused FD_READ messages to be sent.

    So I *think* everything works now, except I'm still not sure if I'll keep getting FD_WRITE messages if I'm only sending small pieces of data.
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  11. #11
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    >>I only receive the FD_WRITE message once when I connect, and then it doesn't come up again.
    You get an FD_WRITE when you first connect, but after that you should never get another FD_WRITE until send() gives you a WSAEWOULDBLOCK. So you just keep sending and sending until WSAEWOULDBLOCK, and then you wait for FD_WRITE to let you know that it's safe to send again.
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  12. #12
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    Ok, so would it be safe to have my send function be like:
    Code:
    bool Network::Send(const char* data)
    {
    if (wait)
      return false; //can't send data right now...
    if (send(hSocket, data, strlen(data), 0) == SOCKET_ERROR) { //send the whole read buffer
    
        if (WSAGetLastError() != WSAEWOULDBLOCK)
       {
          throw NetErr("Failed to send data.");
       }
       wait=true;  
    }
    return true;
    }
    And then set "wait" to false when I receive another FD_WRITE?
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  13. #13
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    It looks about OK to me, except not all of the data might be transmitted in one send() - you might only get half of the data across, and the other half will be lost if you don't account for it somewhere. send() returns the number of bytes that were successfully transmitted, so you might want to have Send() return that instead of a bool.

    Something else fun to try would be to return false, but queue up the data in an internal buffer (or a list of buffers) and finish all incomplete sends the next time you call Send(), before beginning to send the new data - or perhaps even allow Send(NULL), which would mean that rather than sending any new data, Send() should just finish up sending any leftovers from a previous call
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  14. #14
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    Yeah, something like that sounds better. I could just add all the data that isn't sent to a buffer and then when FD_WRITE comes up again try to send it all again. But I think I might still want to return whether all the data is sent so I can display a message in the corner of the app or something (i.e. "Sending data...")

    And then I also have to make sure I receive everything on the other end. It keeps getting more and more complicated!

    Edit: And I also wonder if I need to have several buffers for the server app so it can store data for each client it needs to be sent to...
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  15. #15
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    >>It keeps getting more and more complicated!
    As you can probably tell from the other thread in Networking, I'm not having a ton of luck myself I had the whole 'unsent' buffer thing written out earlier and it even seemed to be working, but then everything else in my project broke and I couldn't figure it out, so I ended up scrapping the whole thing and starting over again. Hopefully Anonytmouse can get us all sorted out
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 12
    Last Post: 02-10-2009, 02:14 PM
  2. non-MFC DLL with MFC app question.
    By Kempelen in forum Windows Programming
    Replies: 10
    Last Post: 08-20-2008, 07:11 AM
  3. Bluetooth client/server app
    By wierdbeard65 in forum Networking/Device Communication
    Replies: 0
    Last Post: 06-12-2007, 06:05 AM
  4. best program to start
    By gooddevil in forum Networking/Device Communication
    Replies: 4
    Last Post: 05-28-2004, 05:56 PM
  5. pasword app
    By GanglyLamb in forum C Programming
    Replies: 2
    Last Post: 06-07-2003, 10:28 AM