In boost.asio, there is a class called an acceptor. The acceptor is used to listen for connection requests on a specific endpoint and open a socket in response to a request.
There are three acceptor constructors I am interested in.
construct an acceptor without opening or binding
explicit basic_socket_acceptor(
const executor_type & ex);
construct an acceptor and open it
basic_socket_acceptor(
const executor_type & ex,
const protocol_type & protocol);
construct an acceptor, open it, and bind it to an endpoint
basic_socket_acceptor(
const executor_type & ex,
const endpoint_type & endpoint,
bool reuse_addr = true);
1 and 3 both make sense. 1 constructs the acceptor in the C++ sense: allocate memory on the stack, maybe initialize some variables, etc. 3 specifies the endpoint that the acceptor should listen on. Incoming connection requests addressed to the endpoint will be accepted and a connection will be created on a new socket. 2, however, appears to be a middle ground that allows you to set a "protocol type."
I want to say that that "2 allows you to specify more member variables (the protocol) than 1 and less member variables (e.g., IP address and port number) than 3." However, I don't know if it does anything "extra" over setting some member variables (does it engage with the OS or the NIC?).
What does it mean to open an acceptor?
It opens the socket, indeed.
I chose to talk about the constructor to differentiate between 1, 2, and 3. A question about the member function open would probably have the same answer as this question.
The question then becomes just what does ::socket do (https://pubs.opengroup.org/onlinepubs/009604499/functions/socket.html):
The socket() function shall create an unbound socket in a communications domain, and return a file descriptor that can be used in later function calls that operate on sockets
So for your practical understanding, it allocates and initializes in-kernel resources[1].
Note
Technically there is no difference between an "acceptor" socket and a "connection" socket. Asio distinguishes them conceptually so it's easier to use them right. The reverse question might offer enlightenment: Design rationale behind that separate acceptor class exists in ASIO
[1] on most operating systems, userland TCP stacks exists though
Do you know the underlying logic of accepting incoming networking connections with BSD sockets?
The steps, if you write them manually, are:
open a socket with socket()
just like opening a file, and you get an integer descriptor you can use with read(), write(), close() etc.
bind the socket to a network address with bind()
unlike a file, the socket descriptor is not associated with anything outside your process until you do this
tell the OS you want to listen for connections on this socket, with listen()
until this point, it could still be used as an outgoing connection, although it's unusual to bind the source address explicitly
start accepting incoming connection requests by calling accept()
So when you open an acceptor, you're asking the OS for a socket and saving the descriptor to a member variable.
When you bind an acceptor, you're just asking the OS to link that socket to an address (but there may be a member variable tracking its state as well, so the acceptor remembers this has been done).
Note that the "middle ground" passes a protocol type, which is needed to open a socket (it corresponds exactly to the arguments to socket()), but it doesn't have an address (or endpoint), so it can't bind that socket yet.
Related
I have a socket that's a member of a shared_ptr object. Do I have to explicitly call socket.close() or is it safe to assume that it is closed automatically?
The reason I ask is because in this TCP Daytime Server Example the author doesn't explicitly call socket.close().
It depends on when you need the socket to be closed.
In general, the socket desctructor causes the socket to close (because not doing so would amount to a resource leak).
boost::asio library provides support for SSL encrypted traffic over sockets by wrapping socket objects in boost::asio::ssl::stream decorator class template. For example:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslSocket;
Some operations of tcp::socket are not supported by ssl::stream. Calling ssl::stream::next_layer method returns reference to wrapped object and this way not supported by ssl::stream operations can be performed directly on wrapped object.
However it seems logical for me that performing read and write operations directly on wrapped socket object bypasses SSL decryption and encryption and for correct SSL behavior read and write on sslSocket must be performed directly over wrapping ssl::stream object.
Is this right or I can freely perform read and write operation on wrapped object?
Not only does using the underlying socket bypass the encryption, it bypasses all of the protocol. Including handshakes, (re)negotiation, side-channels, keepalive etc.
There's no actual guarantee that it would work at all. The only thing you can expect to work is when you use the underlying socket before initializing any SSL operation for the first time.
Beyond that, it's like writing random data to the filedescriptor of a socket that is managed by openssl.
I have a C++ Node.js extension that does network communication. Currently, it creates its own TCP connections in C. I would like to have it use sockets created in Node to take advantage of standard libraries like Cluster and to be able to use the existing event loop.
The solution I see is to simply create a net.Socket object and then extract the uv_tcp_t value from the underlying TCPWrap object. There are a couple of issues I see with this option:
The Socket documentation seems to indicate that a socket immediately starts reading when it connects. I would expect that to cause data loss if I want to read on the underlying UV socket in the extension instead of listening for the 'data' event in JavaScript.
While the TCPWrap class has a function to get the underlying uv_tcp_t struct, it does not seem to have an API to relinquish ownership of that struct. I expect this to cause problems later related to disposing of the struct and ownership of its data member (used for user data).
Is there any way to avoid these issues and use the socket in the extension?
Why does socket_base not have a send() method? Basically, I would like to use boost::asio's sockets like linux socket descriptors: whether the underlying socket is UDP or TCP it doesn't matter, you can call read(), write(), sendto(), etc... on them.
Is there a more proper solution than just writing a wrapper class around asio's udp & tcp socket classes?
You need to use a particular type of socket, like boost::asio::ip::tcp::socket, which is a stream-based TCP socket, or boost::asio::ip::udp::socket, which is for datagrams. The socket_base class is simply a base class which stores common functionality. The actual socket classes contain all the transfer functions you're looking for, like send and receive functions.
As you can see in the documentation, each socket type has send and receive functions.
Is it possible to detach a native socket from Boost.ASIO's socket class? If so, how can it be done? I can't seem to find anything obvious in the documentation.
As a quick overview of what I'm trying to accomplish: I have a class that makes a connection and does some negotiation using Boost.ASIO, then passes back a native Windows SOCKET on success or 0 on failure.
Unless I'm mistaken, the native socket will be closed and deallocated when my boost::asio::basic_socket is destructed.
Answering my own question.
Windows has a WSADuplicateSocket function, which can be used to duplicate the native socket. The underlying socket will remain open until all descriptors for this socket are deallocated.
http://msdn.microsoft.com/en-us/library/ms741565(VS.85).aspx
For Mac OS X do the following (for Linux it isn't hard to modify, just notice the very idea):
Wrap socket in a shared_ptr, so that it won't close when passing into different routines and keep it alive (at least one reference should always exist);
Get a native descriptor with socket.native();
Remove it from kqueue:
struct kevent event;
EV_SET(&event, descriptor, EVFILT_READ, EV_DELETE, 0, 0, 0); //or EVFILT_WRITE
And make it blocking if needed:
fcntl(descriptor, F_SETFL, fcntl(descriptor, F_GETFL, 0) & ~O_NONBLOCK);