Accessing socket that is created in another Class

My project uses this structure:
BasexClient.h
1
2
3
4
5
6
7
8
9
10
11
12
class BasexClient {
  public:
    BasexClient (const std::string, const std::string, const std::string, const std::string);

    std::vector <std::byte> Command(const std::string command);

  private:
    BasexSocket Socket;
    BasexClient addVoid(std::string App, std::vector<std::byte> &To);
    BasexClient addVoid(std::vector<std::byte> App, std::vector<std::byte> &To);
    BasexClient handShake(std::vector<std::byte> Input, std::vector<std::byte> &result);
};

BasexSocket.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BasexSocket {
public:
  BasexSocket (const std::string, const std::string, const std::string, const std::string);
  ~BasexSocket() { close( Master_sfd);}
  int get_Socket() {return Master_sfd;};

  int readSocket( std::string &sockResponseString);
  int writeData(const std::string & input);
  int writeData(const std::vector<std::byte> & input);

private:
  int Master_sfd;

  BasexSocket& CreateSocket (string, string);
  BasexSocket& Authenticate (string, string);
} ;

The constructor from BasexClient calls the constructor from BasexSocket and stores the socket descriptor as a private field.
readSocket() and (the first) writeData() are used in the authenticating process. No problems here.

Both addVoid functions are used to build an input vector for handShake().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BasexClient BasexClient::handShake(std::vector<std::byte> Input, std::vector<std::byte> &Result) {
  int error = 0;
  socklen_t len = sizeof (error);
  int retval = getsockopt (Socket.get_Socket(), SOL_SOCKET, SO_ERROR, &error, &len);
  if (retval != 0) {
    /* there was a problem getting the error code */
    fprintf(stderr, "error getting socket error code: %s\n", strerror(retval));
  }
  if (error != 0) {
    /* socket has a non zero error status */
    fprintf(stderr, "socket error: %s\n", strerror(error));
  }
  Socket.writeData(Input);
  return *this;
}

Calling handshake() results in the following output. The last line comes from the line "fprintf(stderr, "ERROR: (errno = %d), %s ", errno, strerror(errno));" in Socket.writeData().
error getting socket error code: Unknown error -1
ERROR: (errno = 9), Bad file descriptor libBasexTest: Writing data failed


My question is how I can access/use the socket file descriptor that is created in BasexSocket?


Last edited on
What debugging of the code have you done? Have you traced Master_sfd through the code to see where it is being set, to what value and if it changes?
This is the constructor of BasexClient:
1
2
3
BasexClient::BasexClient(std::string DBHOST, std::string DBPORT, std::string DBUSERNAME, std::string DBPASSWORD) :
  Socket(DBHOST, DBPORT, DBUSERNAME, DBPASSWORD){
};

And this of BasexSocket:
1
2
3
4
BasexSocket::BasexSocket (const std::string DBHOST, const std::string DBPORT,
		const std::string DBUSERNAME, const std::string DBPASSWORD) {
	CreateSocket (DBHOST, DBPORT).Authenticate (DBUSERNAME, DBPASSWORD);
};


Master_sfd is set in BasexSocket::CreateSocket to value 3. This value does not change.

I experimented using writeData within one of the addVoid calls.
1
2
int sent = Socket.writeData((std::string) "Addvoid_1");
cout << "Command " << __PRETTY_FUNCTION__ << " " << sent << std::endl;

This results in this output:
Command BasexClient BasexClient::addVoid(std::string, std::vector<std::byte>&) 9
error getting socket error code: Unknown error -1
ERROR: (errno = 9), Bad file descriptor libBasexTest: Writing data failed

It's strange to see that the correct value 9 is echoed, followed by the ERROR message.
Value 9 suggests that writeData did send 9 bytes...
 
fprintf(stderr, "error getting socket error code: %s\n", strerror(retval));


No! retval can't be used like this. You need to use WSAGetLastError() function to obtain the error code.

See:
https://learn.microsoft.com/en-us/windows/win32/winsock/error-codes-errno-h-errno-and-wsagetlasterror-2
https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsagetlasterror
Last edited on
Isn't strerror() the linux equivalent to the Windows WSAGetLastError()?

You can even create Cross-Platform messages
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const char *get_error_text() {

#if defined(_WIN32)
    static char message[256] = {0};
    FormatMessage(
        FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
        0, WSAGetLastError(), 0, message, 256, 0);
    char *nl = strrchr(message, '\n');
    if (nl) *nl = 0;
    return message;
#else
    return strerror(errno);
#endif

}


See:
https://handsonnetworkprogramming.com/articles/socket-error-message-text/
I don't use Linux - but the MS documentation for getSockOpt() specifies that the return value is either 0 (success) otherwise SOCKET_ERROR. WSAGetLastError() then obtains the error code - which is then formatted as needed.
https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockopt

PS. Linux returns either 0 (success) or -1. For -1, errno is set to indicate the error.
https://man7.org/linux/man-pages/man2/getsockopt.2.html

So for Linux you need (as above):

 
fprintf(stderr, "error getting socket error code: %s\n", strerror(errno));


with errno used instead of retval.
Last edited on
The line fprintf(stderr, "error getting socket error code: %s\n", strerror(errno));
from BasexClient::handShake now returns
error getting socket error code: Bad file descriptor


fprintf(stderr, "ERROR: (errno = %d), %s ", errno, strerror(errno)); from BasexSocket::writeData()
returns
ERROR: (errno = 9), Bad file descriptor libBasexTest: Writing data failed
OK. So the problem is with the file descriptor. How/where is this set?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
BasexSocket& BasexSocket::CreateSocket (std::string host, std::string port) {
  if (host.empty() || port.empty()) {
    Master_sfd = -1; return *this;
  }
  struct addrinfo hints;
  struct addrinfo *result = NULL, *rp;
  // Initialize hints
  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family   = AF_INET;                             // Accept both AF_INET and AF_INET6
  hints.ai_socktype = SOCK_STREAM;                         
  hints.ai_flags    = AI_NUMERICSERV;                      // Port must be specified as number

  int rc;
  rc = getaddrinfo( host.c_str(), port.c_str(), &hints, &result);
  if (rc != 0) perror(gai_strerror(rc));
    for (rp = result; rp != NULL; rp = rp->ai_next) {        // result is a linked list of address structures.
      Master_sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
      if (Master_sfd == -1) continue;
      if (connect(Master_sfd, rp->ai_addr, rp->ai_addrlen) != -1) break; // Try to connect. Return the first successfull connect or abort
      close(Master_sfd);
    }
    set_nonblock_flag( Master_sfd, 1);
    if (rp == NULL) {
      warnx("Can not connect to Basex server"); }

  freeaddrinfo(result);
  return *this;
}


After creating the socket, the socket is used without problems in the authenticating process.

EDIT:
1: I changed in BasexClient.h
BasexSocket Socket; into BasexSocket *Socket;
2: In BasexClient.cpp
- In the constructor I changed Socket(DBHOST, DBPORT, DBUSERNAME, DBPASSWORD) into Socket(new BasexSocket(DBHOST, DBPORT, DBUSERNAME, DBPASSWORD))
- All calls to BasexSocket's methods were changed from Socket.writeData() into Socket->writeData()

I have not yet implemented the readSocket part in BasexClient::handShake but writeData() executes without error and reports the correct number of bytes is sent.

Can you explain why introducing the pointer solves the problem?
Last edited on
I see that BasexSocket does not handle being copied correctly. If the BasexSocket object was accidentally copied you would end up with two BasexSocket objects with the same file descriptor. If one of them was destroyed it would close the file descriptor leaving the other object with a bad file descriptor.

I don't say this necessarily what happens, but to rule it out and prevent it from happening you might want disable copying by declaring the copy constructor and copy assignment operator as deleted.

1
2
BasexSocket(const BasexSocket&) = delete;
BasexSocket& operator=(const BasexSocket&) = delete;
Last edited on
Peter87 wrote:
I don't say this necessarily what happens

Actually, I do think this is what's happening.

After a second look through the code I noticed that addVoid and handShake returns a copy of the BasexClient object. This means that the BasexSocket object that the BasexClient object contains will also be copied leading to the problem I described above.
Last edited on
I'll look in my books and in https://www.geeksforgeeks.org/copy-constructor-in-cpp/ to learn more about copy constructors. But copying the first line introduces an error:
BasexSocket(const BasexSocket&) = delete;
expected unqualified-id before ‘const’

Nonetheless thx.
Last edited on
Easier to fix handshaking by returning a ref:

1
2
3
4
BasexClient& handShake(std::vector<std::byte> Input, std::vector<std::byte> &result);


BasexClient& BasexClient::handShake(std::vector<std::byte> Input, std::vector<std::byte> &Result) {


Same with addVoid().

Rough 'rule of thumb' - if return *this have a ref return type.

What compiler/version C++ are you using? =delete was introduced with C++11.

Note that those =delete statements go in the class definition in BasexSocket.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BasexSocket {
public:
  BasexSocket (const std::string, const std::string, const std::string, const std::string);
  ~BasexSocket() { close( Master_sfd);}

  BasexSocket(const BasexSocket&) = delete;
  BasexSocket& operator=(const BasexSocket&) = delete;

  int get_Socket() {return Master_sfd;};

  int readSocket( std::string &sockResponseString);
  int writeData(const std::string & input);
  int writeData(const std::vector<std::byte> & input);

private:
  int Master_sfd;

  BasexSocket& CreateSocket (string, string);
  BasexSocket& Authenticate (string, string);
} ;

Last edited on
I am using Eclipse.
When using the default C++ managed build projects, I know where I can set the C++ dialect that has to be used.
But in these CMake projects, I haven't found where I can set this value, other than in CMakeLists.txt
I thought that these lines would give the same result (I tried both of them):
1
2
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++2a")
set(CMAKE_CXX_STANDARD 20)
but apparently the dialect does not change.
Last edited on
Ok. You can do the same using the 'old way':

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BasexSocket {
public:
  BasexSocket (const std::string, const std::string, const std::string, const std::string);
  ~BasexSocket() { close( Master_sfd);}

   int get_Socket() {return Master_sfd;};

  int readSocket( std::string &sockResponseString);
  int writeData(const std::string & input);
  int writeData(const std::vector<std::byte> & input);

private:
  int Master_sfd;

  BasexSocket& CreateSocket (string, string);
  BasexSocket& Authenticate (string, string);

  BasexSocket(const BasexSocket&) {}
  BasexSocket& operator=(const BasexSocket&) {return *this;}
} ;


ie put the copy constructor/assignment as private - they then can't be used outside of the class.
Last edited on
Thanks to all the help I finally managed to successfully retrieve information from the database for the first time.
Besides studying all the examples, I learned the most by just experimenting.
Great! :)

Just a further point. When passing std::string as a function param, you'd usually pass by const ref (const atd::string& ) [or by using std::move semantics but that is a little more advanced topic] rather than by value as now to avoid an unnecessary copy.

I learned the most by just experimenting.


Yes - but be careful. C++ is one of those languages where you have to learn the basics and underlying principles as to how 'things work'. Just because it seems 'to work' doesn't mean the code is right!

I'd suggest you have a look at (if you don't already):
https://www.learncpp.com/
Last edited on
After I learned to program in Basic on a Commodore 64, I successively programmed in Pascal, Fortran and Clipper. Some colleagues moved to C about 30 years ago, and I heard from them about many programming errors associated with memory misuse. That was the reason for me to switch to the alternative Java.
Much later I learned to program in SWI-Prolog. That's really fun and challenging! That is also the reason for me to still learn C++.
After I have also programmed in R (see the RBaseX client), I eventually want to build a Prolog client for the BaseX database, but because SWI-prolog does not have enough low-level functionality for using a socket, I first have to develop a library that does offer that functionality. A full C++ client for the database is more or less an accidental spinoff.

All in all this will take a long time but as a pensioner I have enough time ;-)
Ben
PS. I'll add the references when passing a std::string
PS 2. I'm constantly checking the cplusplus.com, cppreference.com, and learncpp.com sites to get to grips with C++ as a development tool.
Last edited on
Good luck! :)
Topic archived. No new replies allowed.