Libasync tutorial

Lesson 5: Client sockets

Prev Index Next

You can connect to a socket using tcpconnect. The example below illustrates how.

examples/tcpconnect.C

 1:   #include "async.h"
 2:   
 3:   
 4:   void
 5:   connected(int fd)
 6:   {
 7:     if(fd < 0)
 8:       fatal << "tcpconnect\n";
 9:   
10:     warn << "fd = " << fd << "\n";
11:     exit(0);
12:   }
13:     
14:   
15:   int
16:   main(int argc, char *argv[]) 
17:   {
18:     async_init();
19:     tcpconnect("www.mit.edu", 80, wrap(connected));
20:     amain();
21:   }

The prototype of tcpconnect is

void tcpconnect(str hostname, int_16 port, callback<void, int>::ref cb);

tcpconnect calls cb when it is done and passes to cb the file descriptor for the socket that the connection was made on. It passes -1 to cb if something went wrong, for example if the underlying call to socket(), bind(), or connect() fails.

The prototype indicated that we need to pass tcpconnect a callback function that returns void and that still expects an int parameter. Our connected function fits the description.

Reading and writing from a socket: an HTTP client

You can't just read from a socket or write to a socket. You need to be sure that your program will not block. Using libasync's fdcb function, you will be notified when a certain file descriptor is readable or writable.

The code below builds on the tcpconnect example above and implements a very simple HTTP client, but it makes the point nonetheless.

examples/fdcb.C

 1:   #include "async.h"
 2:   
 3:   
 4:   void
 5:   http_read(int fd)
 6:   {
 7:     strbuf resp;
 8:   
 9:     int n = resp.tosuio()->input(fd);
10:     if(n < 0)
11:       fatal << "read\n";
12:   
13:     if(n == 0) {
14:       fdcb(fd, selread, 0);
15:       close(fd);
16:       exit(0);
17:     };
18:   
19:     warn << resp;
20:   }
21:   
22:   void
23:   http_write(int fd, strbuf req)
24:   {
25:     int n = req.tosuio()->output(fd);
26:   
27:     if(n < 0)
28:       fatal << "write\n";
29:   
30:     // still stuff to write: don't disable writability callback
31:     if(req.tosuio()->resid())
32:       return;
33:   
34:     // done writing request.
35:     fdcb(fd, selwrite, 0);
36:     fdcb(fd, selread, wrap(http_read, fd));
37:   }
38:   
39:   void
40:   connected(int fd)
41:   {
42:     if(fd < 0)
43:       fatal << "connect\n";
44:   
45:     strbuf req("GET / HTTP/1.0\r\n\r\n");
46:     fdcb(fd, selwrite, wrap(http_write, fd, req));
47:   }
48:     
49:   
50:   int
51:   main(int argc, char *argv[]) 
52:   {
53:     async_init();
54:     if(argc < 2)
55:       fatal << "usage: fdcb host\n";
56:   
57:     tcpconnect(argv[1], 80, wrap(connected));
58:     amain();
59:   }

main is pretty much the same as in the example above. However, connected uses fdcb to set a callback for writability of file descriptor fd. The prototype for fdcb is:

void fdcb(int socket, char operation, callback<void>::ptr cb);

operation can be either selread (readability) or selwrite (writability). The call to fdcb on line 46 instructs libasync to call http_write when file descriptor fd is ready for writing.

When http_write executes, it tries to write (the remaining bytes in) req out to file descriptor fd. This is what line 25 does---you'll get the details about suio later. Don't worry about it for now. (If you must know, consult Using libasync.) If we didn't write all of the request (if-statement on line 31) then don't disable the writability callback. When fd is writable again, http_write gets invoked again---this goes on until the contents of req have been written or until an error occurs. When we're done writing the request, we disable the writability callback (line 35) by passing 0 to fdcb. (Hence the ::ptr rather than ::ref in the fdcb prototype.) Now we're interested in reading the response so we set a callback function (http_read) for readability on line 36.

In http_read we read data from the file descriptor into a strbuf object on line 9. Forget about the details of it: it does a single read and puts the bytes in resp. We handle the error case, print out the junk we just read from the web server and quit the game if the remote party closed the connection. Notice that if we don't do anything (i.e., when the remote party did not close the connection), the readability callback stays where it is and we can expect http_read to be called again.

Prev Index Next

Back to main.