I like the listen() API, don't you? However, it doesn't do us much good to queue up clients that have connected to us, if we don't ever take them off of the queue!
That's exactly what the accept() API does. It takes a new connection off of the queue and creates a new socket which will be used for talking to the client on the other end.
The IBM manual page for the accept() API is found here: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/accept.htm
And it tells us that the C language prototype for accept() looks like this:
int accept(int socket_descriptor, struct sockaddr *address, int *address_length);
Is this format starting to look familiar yet? The accept() API has the same parameters as the connect() and bind() APIs do, with one exception. For the 'address_length' parameter, we pass a pointer to an integer, instead of passing the value of the integer.
This means that it accepts 3 parameters. An integer, a pointer and another pointer. And, it returns an integer.
You might recall that when you pass something by reference, you're not actually passing the data itself, but rather you're passing the address of that data. Since pointers are the variables that are used to store addresses, passing the address of a variable is exactly the same thing as passing the value of a pointer. So, instead of passing the third parameter as a pointer, we can safely pass an integer by reference.
The accept() prototype will, therefore, look like this:
D accept PR 10I 0 ExtProc('accept') D sock_desc 10I 0 Value D address * Value D address_len 10I 0
Passing an integer by reference has two advantages:
It's less typing to code "accept(socket: connfrom: len)" than it is to code "accept(socket: connfrom: %addr(len))"
The compiler can do better syntax checking, because it knows that only integers are allowed as the 2nd parameter.
So, as was mentioned earlier... the listen() API tells the system that we're willing to accept connections. When someone connects, the system puts them in a queue. The accept() API takes the connections off of the queue in First-In-First-Out (FIFO) order.
Each time you accept a new connection, the IP address and port number of the connecting client is placed in a sockaddr_in structure that is pointed to by the 'address' parameter.
The 'address_len' parameter has two purposes. On input to the accept() API, it contains the amount of memory that you've allocated to the 'address' parameter. Accept() uses this to ensure that it doesn't write data beyond what you've allocated. On output from the accept() API, it contains the actual number of bytes that the accept() API wrote into the area of memory that you supplied as 'address'.
It's important to understand that accept() creates a new socket. The original socket which you used with bind() and listen() will continue to listen for new connections and put them into the backlog queue.
The new socket created by accept() contains your TCP connection to the client program. Once you've accept()-ed it, you can use the send() and recv() APIs to hold a conversation with the client program.
When you're done talking to the client, you'll want to call close() for the descriptor returned by accept(). Again, remember that this is a separate socket from the one that's listen()-ing. So, when you close() the socket that was returned from accept(), your program will still be listening for more connections.
To stop listening for connections, you need to call close() for the original socket.
Here's how you call accept:
D connfrom S * D len S 10I 0 c eval len = %size(sockaddr_in) c alloc len connfrom c eval newsock = accept(sock: connfrom: len) c if newsock < 0 C* accept() failed. Check errno c endif c if len <> 16 C* illegal length for a TCP connection! c endif c eval p_sockaddr = connfrom c eval msg = 'Received a connection from ' + c %str(inet_ntoa(sin_addr)) + '!'