C Board  

Go Back   C Board > General Programming Boards > Networking/Device Communication

Reply
 
LinkBack Thread Tools Display Modes
Old 01-27-2005, 08:52 PM   #1
carry on
 
JaWiB's Avatar
 
Join Date: Feb 2003
Location: Seattle, WA
Posts: 1,971
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...
__________________
"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

Last edited by JaWiB; 01-27-2005 at 10:07 PM.
JaWiB is offline   Reply With Quote
Old 01-28-2005, 01:37 AM   #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.
anonytmouse is offline   Reply With Quote
Old 01-28-2005, 10:44 AM   #3
carry on
 
JaWiB's Avatar
 
Join Date: Feb 2003
Location: Seattle, WA
Posts: 1,971
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
JaWiB is offline   Reply With Quote
Old 01-28-2005, 01:11 PM   #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?
anonytmouse is offline   Reply With Quote
Old 01-28-2005, 06:53 PM   #5
carry on
 
JaWiB's Avatar
 
Join Date: Feb 2003
Location: Seattle, WA
Posts: 1,971
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:
Quote:
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?
__________________
"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

Last edited by JaWiB; 01-28-2005 at 06:56 PM.
JaWiB is offline   Reply With Quote
Old 01-29-2005, 03:52 AM   #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?
anonytmouse is offline   Reply With Quote
Old 01-29-2005, 12:41 PM   #7
carry on
 
JaWiB's Avatar
 
Join Date: Feb 2003
Location: Seattle, WA
Posts: 1,971
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
JaWiB is offline   Reply With Quote
Old 01-30-2005, 09:38 AM   #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.
anonytmouse is offline   Reply With Quote
Old 01-30-2005, 02:19 PM   #9
carry on
 
JaWiB's Avatar
 
Join Date: Feb 2003
Location: Seattle, WA
Posts: 1,971
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
Attached Files
File Type: cpp main.cpp (8.3 KB, 28 views)
File Type: cpp netwk.cpp (4.2 KB, 32 views)
File Type: h netwk.h (866 Bytes, 28 views)
__________________
"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
JaWiB is offline   Reply With Quote
Old 01-30-2005, 05:24 PM   #10
carry on
 
JaWiB's Avatar
 
Join Date: Feb 2003
Location: Seattle, WA
Posts: 1,971
I think I might have figured it out. Here's what threw me (from msdn):
Quote:
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
JaWiB is offline   Reply With Quote
Old 01-31-2005, 11:00 AM   #11
Carnivore ('-'v)
 
Hunter2's Avatar
 
Join Date: May 2002
Posts: 2,866
>>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.
Hunter2 is offline   Reply With Quote
Old 01-31-2005, 04:55 PM   #12
carry on
 
JaWiB's Avatar
 
Join Date: Feb 2003
Location: Seattle, WA
Posts: 1,971
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
JaWiB is offline   Reply With Quote
Old 01-31-2005, 05:18 PM   #13
Carnivore ('-'v)
 
Hunter2's Avatar
 
Join Date: May 2002
Posts: 2,866
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.
Hunter2 is offline   Reply With Quote
Old 01-31-2005, 05:42 PM   #14
carry on
 
JaWiB's Avatar
 
Join Date: Feb 2003
Location: Seattle, WA
Posts: 1,971
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
JaWiB is offline   Reply With Quote
Old 01-31-2005, 05:59 PM   #15
Carnivore ('-'v)
 
Hunter2's Avatar
 
Join Date: May 2002
Posts: 2,866
>>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.
Hunter2 is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Cannot Accept Connections with Simple client/server program NuNn C Programming 12 02-10-2009 02:14 PM
non-MFC DLL with MFC app question. Kempelen Windows Programming 10 08-20-2008 07:11 AM
Bluetooth client/server app wierdbeard65 Networking/Device Communication 0 06-12-2007 06:05 AM
best program to start gooddevil Networking/Device Communication 4 05-28-2004 05:56 PM
pasword app GanglyLamb C Programming 2 06-07-2003 10:28 AM


All times are GMT -6. The time now is 09:55 PM.


Powered by vBulletin® Version 3.8.1
Copyright ©2000 - 2010, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.3.2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22