Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x
?
Solved

C++ Class Help (WinSock)

Posted on 2004-10-29
10
Medium Priority
?
1,088 Views
Last Modified: 2013-12-14
I am currently trying to learn (and program with) the WinSock API.   Currently I have a socket class that has limited functionality (but functional none-the-less) and can create sockets, a server, and a client.  I am attempting to add the ability to grab the connecting client's IP Address or name.  I would assume I need to use the "gethostbyaddr( )" however am lost as to where to put it in my code and return it.  I think my problem is more a lack of advanced class building/use skills than it is understanding the WinSock API, however I could be wrong.

Here's some source I am working with.  the first here is my class definition in my header:
_________________________________________________________________________
using namespace std;

enum TypeSocket {BlockingSocket, NonBlockingSocket};

class Socket {
public:

  virtual ~Socket();
  Socket(const Socket&);
  Socket& operator=(Socket&);

  string rxData();
  //std::string ReceiveBytes();

  void   Close();
  void   txData (std::string);

protected:
  friend class SocketServer;
  friend class SocketSelect;

  Socket(SOCKET s);
  Socket();

  SOCKET s_;

private:
  static void Start();
  static void End();
};

class SocketClient : public Socket {
public:
  SocketClient(const std::string& host, int port);
};

class SocketServer : public Socket {
public:
  SocketServer(int port, int connections, TypeSocket type=BlockingSocket);

  Socket* Accept();
//***GetHostByAddr( ) here***
};

To specify the question a tad more, I would like to know the most efficient way to add methods (i.e. gethostbyaddr(), gethostbyname(), etc...) to this class, and return their values from it to my client.  Code snippets always welcome.  Like I said, more of a class question than sockets :)  

Here are the method descriptions:
___________________________________________________________________________

using namespace std;

void Socket::Start() {

    WSADATA info;
      int wsaret=WSAStartup(0x101,&info);
    if (wsaret!=0) {
      throw "WinSock1.1 WSA Startup Failure";//throws err to console
    }
}

void Socket::End() {
  WSACleanup();
}

//socket "s_" construction
Socket::Socket(SOCKET s) : s_(s) {
  Start();
};

void Socket::Close() {//doesn't destruct obj of "Socket"
  closesocket(s_);
}

//socket "s_" destruction
Socket::~Socket() {
  Close();
}

//main constructor/destructor of socket "s_"
Socket::Socket() : s_(0) {//defining socket "s_"
  Start();
  //UDP: use SOCK_DGRAM instead of SOCK_STREAM
  //Will allow for less OH, more err prone
  s_ = socket(AF_INET,SOCK_STREAM,0);

  if (s_ == INVALID_SOCKET) {
    throw "INVALID_SOCKET";//throws err to console
  }

}

string Socket::rxData() {
  std::string ret;
   while (1) {
     char r;

     switch(recv(s_, &r, 1, 0)) {
       case 0: //indicates broken conn
         return "";
       case -1:
          if (WSAGetLastError() == EAGAIN) {
                    return ret;
          } else {  //indicates broken conn
           return "";
         }
     }

     ret += r;
     if (r == '\n')  return ret;
   }
}

void Socket::txData(std::string s) {
  s += '\n';
  send(s_,s.c_str(),s.length(),0);
}

SocketServer::SocketServer(int port, int connections, TypeSocket type) {
  sockaddr_in local;
  std::string error;
  std::string hostIP;
 
  memset(&local, 0, sizeof(local));

  local.sin_family = PF_INET;            
  local.sin_port = htons(port);//htons - Host to Network Short Int

  s_ = socket(AF_INET, SOCK_STREAM, 0);
  if (s_ == INVALID_SOCKET) {
    throw "INVALID_SOCKET";
  }

  if(type==NonBlockingSocket) {
    u_long arg = 1;
    ioctlsocket(s_, FIONBIO, &arg);
  }

  /*      if(bind(server, (sockaddr*)&local, sizeof(local))!=0) //connect address with socket
      {
            return 0;
      }*/
  if (bind(s_, (sockaddr *)&local, sizeof(local)) == SOCKET_ERROR) {
    closesocket(s_);
    throw "INVALID_SOCKET";
  }
 
  //      if(listen(server, 10)!=0)
  //{
  //      return 0;
  //}
  if(listen(s_, connections)!=0){ //lstn for incming conns, conn is the bcklog
       error = strerror(WSAGetLastError());
       throw error;
  }                              
}

Socket* SocketServer::Accept() {
 
  SOCKET new_sock = accept(s_, 0, 0);
  if (new_sock == INVALID_SOCKET) {
          int rc = WSAGetLastError();
          if(rc==WSAEWOULDBLOCK) {
                  return 0; // non-blocking call, no request pending
          }
          else {
            throw "Invalid Socket";
      }
  }

   Socket* r = new Socket(new_sock);
  return r;
}

SocketClient::SocketClient(const std::string& host, int port) : Socket() {
  std::string error;

  hostent *he;
  if ((he = gethostbyname(host.c_str())) == 0) {//resolves from hostname
    error = strerror(WSAGetLastError());
    throw error;
  }

  sockaddr_in local;
  local.sin_family = AF_INET;//Address Family (internet)
  local.sin_port = htons(port);//htons - Host to Network Short Int
  local.sin_addr = *((in_addr *)he->h_addr);
  memset(&(local.sin_zero), 0, 8);

  if (::connect(s_, (sockaddr *) &local, sizeof(sockaddr))) {
    error = strerror(WSAGetLastError());//always use for error toss
    throw error;
  }
}

Any and all help is appreciated.  Thanks in advance, and feel free to make additional comments/advice on my current class.
0
Comment
Question by:logangsta
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 5
  • 5
10 Comments
 
LVL 19

Expert Comment

by:drichards
ID: 12450576
In your Accept method, you need to pass parameters in accept to receive the connecting client address:

  sockaddr_in addr;
  int addrLen = sizeof(sockaddr_in);
  SOCKET new_sock = accept(s_, (sockaddr*)(&addr), &addrLen);

You should also call WSACleanup once for each time you call WSAStartup - I didn't see where your code is calling it.
0
 

Author Comment

by:logangsta
ID: 12460020
drichards,

Thanks for the reply.  I think I follow your method to generate the connecting address in the Accept method.  What would be the appropriate means of passing this data out to another part of the program (seperate cpp) that is utilizing this class method?

Also, let's say I wanted to use the getHostByAddr( ) function, how would one incorporate this?  Make a seperate method??  

To answer your question, I do have a method to close an active socket (near the very start of my second source posting):

void Socket::End() {
  WSACleanup();
}

I was under the assumption this was sufficient to cleanup the WSAStartup garbage.  Let me know your thoughts.
0
 
LVL 19

Accepted Solution

by:
drichards earned 280 total points
ID: 12460749
Your End() method is OK, but as yo're calling Start in the constructor, I would have expected End in the destructor.

>> Also, let's say I wanted to use the getHostByAddr( ) function, how would one incorporate this?  Make a seperate method??
Depends what you're doing with getHostByAddr.  Making a separate method will let you get at the functionality directly if that's what you want.  It's probably a static method of the socket class, however, since it's not directly related to a socket.

As for passing the accepted address data out of the class, you can make it an optional output parameter of the Accept method.   If you are trying to hide all the Winsock structures, you will need to create some other type of data entity for te address information.  You coule turn it into a string, for example.  Depends on what you're going to do with it.  Here's one way to return the bare sockaddr.  The calling routine must pass a valid sockaddr_in pointer if it wants to receive the client address.
--------------------------------------
class SocketServer : public Socket {
public:
  SocketServer(int port, int connections, TypeSocket type=BlockingSocket);

  Socket* Accept(sockaddr_in *pAddr = 0);
//***GetHostByAddr( ) here***
};

Socket* SocketServer::Accept(sockaddr_in *pAddr) {
  int addrLen = 0;
  if ( pAddr != NULL ) addrLen = sizeof(sockaddr_in);
  SOCKET new_sock = accept(s_, pAddr, &addrLen);
  if (new_sock == INVALID_SOCKET) {
          int rc = WSAGetLastError();
          if(rc==WSAEWOULDBLOCK) {
                  return 0; // non-blocking call, no request pending
          }
          else {
            throw "Invalid Socket";
      }
  }

   Socket* r = new Socket(new_sock);
  return r;
}
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:logangsta
ID: 12463095
When I attempted to incorporate what you have, here is the error I am getting:

c:\vc projects\echoserver\socket.cpp(144) : error C2664: 'accept' : cannot convert parameter 2 from 'struct sockaddr_in *' to 'struct sockaddr *'
        Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

I attempted to make some changes to correct this error.  If I do get it to compile, the executable failes with an "abnormal program termination."  I'm no debugging expert, so I am somewhat lost with what is wrong as far as pointing to the member function.

>>The calling routine must pass a valid sockaddr_in pointer if it wants to receive the client address.

Could you show me/point me to an example that would be similar?

Thanks for all the help.  

0
 

Author Comment

by:logangsta
ID: 12467172
Looking at this prototype:

string Socket::GetClientIP() {

sockaddr_in from;
  int fromlen = sizeof(from);
 
  return inet_ntoa(from.sin_addr);
}

Why would this give 204.204.204.204 as my return value??  No matter the connecting IP (127.0.0.1 or an actual remote) it still comes up with the same value.
0
 
LVL 19

Expert Comment

by:drichards
ID: 12468705
As to the first problem, I left out a cast:

    SOCKET new_sock = accept(s_, (sockaddr*)pAddr, &addrLen);

Then you call the Accept method like:

    sockaddr_in clientAddr;
    pSock = server.Accept(&clientAddr);

where pSock is a SOCKET* and server is a SocketServer.

As to the 204.204.204.204, the GetClientIP method puts a sockaddr_in on the stack and then converts it (uninitialized) into a string.  In debug bulds, memory gets initialized to 0xcc, which is 204.  You need to initialize the address to a value befire converting it to a string.  GetClientIP should take the address as a parameter.  Then you can do:

    sockaddr_in clientAddr;
    pSock = server.Accept(&clientAddr);
    string clientIP = GetClientIP(clientAddr);
0
 

Author Comment

by:logangsta
ID: 12476918
drichards,

Thanks for all the help.  You def. know your stuff.  Accept( ) is returning the IP as expected.  Now just have to look into catching the IP per socket.  I am somewhat working on a chat style peice of software, so would like the sending member of the chat group to broadcast its IP as well.  Not sure how to use what you've helped with to catch this, but it shouldn't be impossible, right?  Thanks again!
0
 
LVL 19

Expert Comment

by:drichards
ID: 12487549
>> so would like the sending member of the chat group to broadcast its IP as well
I am not sure what you mean by that.  How are the clients finding each other?  The typical method is to have each client connect to a central server.  The current list of active clients is then available from the server.  Broadacst messages do not travel very far on a network and are hence not very useful for things like chat.
0
 

Author Comment

by:logangsta
ID: 12494470
The clients do indeed connect to a central server.  From here, the server distributes the messages it receives to all connected clients.  For instance, let's say 192.168.1.1 is the server, and ips 192.168.1.2-4 connects.  If .2 sends a message, the server echos the received message to all ips but itself and the sending ip (in this case .2).  What I meant by broadcasting the ip is having all the clients that receive this echoed message, also receive the ip that sent the message initially.  This was why I attempted to create the seperate function to grab client IPs.  For example, when the Accept( ) is called, it will grab the IP and store it wherever I put it.  I am unsure of the best method to keep an array of connecting IPs, and then identify it with the appropriate socket.  Right now, I iterate through a list of connected sockets and send the echo message to all sockets != sendingSocket.  This works.  I did have a call to the getClientIP function in this iteration, but appearantly didn't pass the correct parameters over again.  I also tried (as stated) to keep an array of the connected IPs captured from Accept( ).  I was then to iterate through this list as i iterate through the list of sockets.  However, I had no method of matching one with the other.  I think (hopefully) I am overseeing the simple address capturing of WinSock functions.  :)
0
 
LVL 19

Expert Comment

by:drichards
ID: 12500030
Without embedding the original sender IP in the relayed messages, there is no way in your current architecture for the receiving clients to retrieve the IP of the original sender.  This is a reasonably straightforward extension.  You just need to have the chat clients strip out the IP information before displaying the message.

You could also look at a multicast solution.

>>  I had no method of matching one with the other
You need to keep that mapping on the server, though I'm not sure what you'd do with the information.  Any time a message is received on a socket, the source IP/socket mapping information is implicitly available.  Mapping a user name or some more human-friendly identification to the socket would be more useful.
0

Featured Post

Important Lessons on Recovering from Petya

In their most recent webinar, Skyport Systems explores ways to isolate and protect critical databases to keep the core of your company safe from harm.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Basic understanding on "OO- Object Orientation" is needed for designing a logical solution to solve a problem. Basic OOAD is a prerequisite for a coder to ensure that they follow the basic design of OO. This would help developers to understand the b…
Article by: evilrix
Looking for a way to avoid searching through large data sets for data that doesn't exist? A Bloom Filter might be what you need. This data structure is a probabilistic filter that allows you to avoid unnecessary searches when you know the data defin…
The viewer will learn how to use NetBeans IDE 8.0 for Windows to connect to a MySQL database. Open Services Panel: Create a new connection using New Connection Wizard: Create a test database called eetutorial: Create a new test tabel called ee…
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.

618 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question