350 likes | 655 Views
Non-blocking I/O. Computing Network Programming. Outline. Socket operations that cause blocking and how there operations behave for non-blocking sockets non-blocking read and write non-blocking connect daytime client web client non-blocking accept. Motivation.
E N D
Non-blocking I/O Computing Network Programming
Outline • Socket operations that cause blocking and how there operations behave for non-blocking sockets • non-blocking read and write • non-blocking connect • daytime client • web client • non-blocking accept
Motivation • If we use non-blocking I/O, the process can some useful task after initiating the operation, or after detecting that I/O operation can not be completed (we don’t waste time by blocking and sleeping) • If we use non-blocking I/O, we can write network programs who perform better in terms of time • Establish simultaneous connections and data transfers between a server and client: example: netscape browsers
Socket operations that cause blocking • By default sockets are blocking • if socket function call can not complete immediately, process is put into sleep mode until kernel completes the operation • Socket calls that can block • Input operations: read, readv, recv, recvmsg, recvfrom • block until some data arrives (TCP) or until a complete UDP datagram arrives (UDP) • for non-blocking socket, if input can not received, function returns immediately with error code EWOULDBLOCK
Socket operations that cause blocking • Output operations: write, writev, send, sendto, sendmsg • block until socket sendbuffer has room (TCP). • For non-blocking socket, output operation returns immedialy if there no room with error EWOULDBLOCK. • never block for UDP. • Accepting incoming connections: accept • if there is no new connection available accept will block until a new connection is established. • For a non-blocking socket accept return immediately if there is no new connection available with an error EWOULDBLOCK
Socket operations that cause blocking • Initiating outgoing connections: connect • connect for TCP blocks the process until a TCP connection is established (until client receives the ACK of its SYN: at least one round-trip time) • for non-blocking socket, if connect is called for TCP, the connection is initiated and connect returns with error EINPROGRESS. (sometimes connection can be established immediately if two process are at the same machine, in which case connect will return 0 = OK).
How to set a socket non-blocking mode We use fcntl function to set a socket to non-blcoking mode. We saw this function earlier when we studied the socket options int flags; int socketfd; …. sockfd = socket(AF_INET, SOCK_STREAM, 0); if ((flags = fcntl(fd, F_GETFL, 0)) < 0) err_sys(“F_GETFL error”), flags = flags | O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) < 0) err_sys(“F_SETFL error”),
Non-blocking Read and Write We will use our echo client again to show not blocking read and writes. Focus on str_cli() function We had developed earlier version that was using select on stdin and socket. But it is still using blocking I/O, because after obtaining a descriptor to be readable/writebale, the I/O operation can still block (see next slide) We will now develop echo client that is completely blocking free, hence it is more efficient. However, buffer management is more complex with non-blocking I/O, and programs can be longer. Tradeoff between performance and development effort
Using select (still can block) void str_cli(FILE *fp, int sockfd) { int maxfdp1; fd_set rset; char sendline[MAXLINE], recvline[MAXLINE]; FD_ZERO(&rset); for ( ; ; ) { FD_SET(fileno(fp), &rset); FD_SET(sockfd, &rset); maxfdp1 = max(fileno(fp), sockfd) + 1; Select(maxfdp1, &rset, NULL, NULL, NULL); if (FD_ISSET(sockfd, &rset)) { /* socket is readable */ if (Readline(sockfd, recvline, MAXLINE) == 0) /* can BLOCK here */ err_quit("str_cli: server terminated prematurely"); Fputs(recvline, stdout); } if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */ /* can BLOCK here */ if (Fgets(sendline, MAXLINE, fp) == NULL) /* can BLOCK here */ return; /* all done */ Writen(sockfd, sendline, strlen(sendline)); } } }
Non-blocking I/O: Buffer Management We maintain two buffers called “to” and “fr”: to:contains data going from standard input (keyboard) tothe server (socket) fr:contains data arriving fromserver (socket) to standard output (screen) stdin keyboard to/from Server to buffer sockfd screen fr buffer stdout echo client process str_cli() function reads from keyboard into “to” buffer and writes into socket, and reads from socket into “fr” buffer and writes to screen
Buffers and Pointers stdin toiptr &to[MAXLINE] to already sent data to send to the server available space to read into fom stdin tooptr socket socket friptr &fr[MAXLINE] fr already sent data to send to standard output available space to read into from socket froptr stdout
str_cli() function of echo client 5 void str_cli(FILE *fp, int sockfd) 6 { 7 int maxfdp1, val, stdineof; 8 ssize_t n, nwritten; 9 fd_set rset, wset; 10 char to[MAXLINE], fr[MAXLINE]; 11 char *toiptr, *tooptr, *friptr, *froptr; 12 13 val = Fcntl(sockfd, F_GETFL, 0); 14 Fcntl(sockfd, F_SETFL, val | O_NONBLOCK); 15 16 val = Fcntl(STDIN_FILENO, F_GETFL, 0); 17 Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK); 18 19 val = Fcntl(STDOUT_FILENO, F_GETFL, 0); 20 Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK); 21 22 toiptr = tooptr = to; /* initialize buffer pointers */ 23 friptr = froptr = fr; 24 stdineof = 0; 25
str_cli() continued 26 maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1; 27 for ( ; ; ) { 28 FD_ZERO(&rset); 29 FD_ZERO(&wset); 30 if (stdineof == 0 && toiptr < &to[MAXLINE]) /* if buffer has space */ 31 FD_SET(STDIN_FILENO, &rset); /* read from stdin */ 32 if (friptr < &fr[MAXLINE]) 33 FD_SET(sockfd, &rset); /* read from socket */ 34 if (tooptr != toiptr) /* if there is data to write */ 35 FD_SET(sockfd, &wset); /* data to write to socket */ 36 if (froptr != friptr) 37 FD_SET(STDOUT_FILENO, &wset); /* data to write to stdout */ 38 39 Select(maxfdp1, &rset, &wset, NULL, NULL); 40 41
str_cli() continued 42 if (FD_ISSET(STDIN_FILENO, &rset)) { 43 if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) { 44 if (errno != EWOULDBLOCK) 45 err_sys("read error on stdin"); 46 47 } else if (n == 0) { 48 49 stdineof = 1; /* all done with stdin */ 50 if (tooptr == to) 51 Shutdown(sockfd, SHUT_WR);/* send FIN */ 52 53 } else { 54 toiptr += n; /* # just read */ 55 FD_SET(sockfd, &wset); /* try and write to socket below */ 56 } 57 }
str_cli() continued I deleted some code that is not very important hence there is shift in line numbers! Don’t get confused. 64 if (FD_ISSET(sockfd, &rset)) { 65 if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) { 66 if (errno != EWOULDBLOCK) 67 err_sys("read error on socket"); 68 } else if (n == 0) { 69 if (stdineof) 70 return; /* normal termination */ 71 else 72 err_quit("str_cli: server terminated prematurely"); 73 } else { 74 friptr += n; /* # just read */ 75 FD_SET(STDOUT_FILENO, &wset); /* try and write below */ 76 } 77 }
str_cli() continued 89 if (FD_ISSET(STDOUT_FILENO, &wset) && ( (n = friptr - froptr) > 0)) { 90 if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) { 91 if (errno != EWOULDBLOCK) 92 err_sys("write error to stdout"); 93 } else { 94 froptr += nwritten; /* # just written */ 95 if (froptr == friptr) 96 froptr = friptr = fr; /* back to beginning of buffer */ 97 } 98 }
str_cli() continued 105 if (FD_ISSET(sockfd, &wset) && ( (n = toiptr - tooptr) > 0)) { 106 if ( (nwritten = write(sockfd, tooptr, n)) < 0) { 107 if (errno != EWOULDBLOCK) 108 err_sys("write error to socket"); 109 110 } else { 111 tooptr += nwritten; /* # just written */ 112 if (tooptr == toiptr) { 113 toiptr = tooptr = to; /* back to beginning of buffer */ 114 if (stdineof) 115 Shutdown(sockfd, SHUT_WR); /* send FIN */ 116 } 117 } 118 } 119 } 120 } End of Function
Other ways of implementing echo client • Non-blocking I/O increses performance but it is complex • We can split the client into 2 processes using fork() and obtain a simpler program • one child will handle keyboard to socket transfer • other child will handle socket to screen transfer
Echo Client with 2 Processes client stdin One TCP connection (full duplex) parent server fork() stdout child Server and Child share the same socket - one socket, one recv buffer, one send buffer in the kernel
client code with fork() #include "unp.h" void str_cli(FILE *fp, int sockfd) { pid_t pid; char sendline[MAXLINE], recvline[MAXLINE]; if ( (pid = Fork()) == 0) { /* child: server -> stdout */ while (Readline(sockfd, recvline, MAXLINE) > 0) Fputs(recvline, stdout); kill(getppid(), SIGTERM); /* in case parent still running */ exit(0); } /* parent: stdin -> server */ while (Fgets(sendline, MAXLINE, fp) != NULL) Writen(sockfd, sendline, strlen(sendline)); Shutdown(sockfd, SHUT_WR);/* EOF on stdin, send FIN */ pause(); /* is used only to measure the time correctly */ return; }
Non-blocking Connect() • We have seen I/O functions on non-blocking sockets • Now we will see how to connect behaves on non-blocking sockets • We will see an application that uses this approach: netscape browser • opens multiple TCP connections simultaneously
Example Scenerio We access and download a web page (/) and that webpage contains links to other objects (GIF files) and we would like to download these objects simultaneously over different connections (object may reside on different servers) Two steps - establish a separate TCP connection for each object - transfer the object over the established connection Do these two steps for each objects. 3 approaches 1- totally serialized (one connection at a time) 2- establish connections first one by one serially, then transfer data simulaneously 3- establish connections and transfer objects all simultaneously (we will see how to do this)
Establishing simultaneous Connections Other web servers and objects Download main web page Links Client browser Server httpd Main page dowload over this connection Client will download the other objects specified in the main page.
Performance Improvement with simultaneous connections One connection at a time Two connections simultaneously Three connections simulatenously 4 10 10 10 15 15 4 15 15 unit time 15 unit time 4 29 unit time
How to use non-blocking socket with connect • First call connect() • initiates TCP connection and returns. • Then call select() • when select returns (either there is timeout, or socket available for reading or for both reading and writing) • Check the error value with getsockopt function and SO_ERROR socket option • IF the error is zero (no error) • then connection established successfully, we can read from and write to the socket. • IF the error is non-zero • connection could not estanlished successfully, either because of select timeout or some other error like connection refused, TCP timeout, hostunreachable, etc.
Example programWeb client > web 3 www.foo.com / image1.gif image2.gif image3.gif - retrieves first root home page: / - then establishes simultaneous connections to retrive 3 objects image1.gif, image2.gif, image3.gif thereby simulates a web browser operation.
First look at the program header file web.h #include "unp.h" #define MAXFILES 20 #define SERV "80" /* port number or service name */ struct file { char *f_name; /* filename */ char *f_host; /* hostname or IPv4/IPv6 address */ int f_fd; /* descriptor */ int f_flags; /* F_xxx below */ } file[MAXFILES]; #define F_CONNECTING 1 /* connect() in progress */ #define F_READING 2 /* connect() complete; now reading */ #define F_DONE 4 /* all done */ #define GET_CMD "GET %s HTTP/1.0\r\n\r\n" /* globals */ int nconn, nfiles, nlefttoconn, nlefttoread, maxfd; fd_set rset, wset; /* function prototypes */ void home_page(const char *, const char *); void start_connect(struct file *); void write_get_cmd(struct file *);
web.c intialization int main(int argc, char **argv) { int i, fd, n, maxnconn, flags, error; char buf[MAXLINE];fd_set rs, ws; if (argc < 5) err_quit("usage: web <#conns> <hostname> <homepage> <file1> ..."); maxnconn = atoi(argv[1]); nfiles = min(argc - 4, MAXFILES); for (i = 0; i < nfiles; i++) { file[i].f_name = argv[i + 4]; file[i].f_host = argv[2]; file[i].f_flags = 0; } printf("nfiles = %d\n", nfiles); home_page(argv[2], argv[3]); /* retrieves the main (root) page */ FD_ZERO(&rset); FD_ZERO(&wset); maxfd = -1; nlefttoread = nlefttoconn = nfiles; nconn = 0; /* …. Will be continued */
Retrieving the main page (/): home_page() function void home_page(const char *host, const char *fname) { int fd, n; char line[MAXLINE]; fd = Tcp_connect(host, SERV); /* blocking connect() */ n = snprintf(line, sizeof(line), GET_CMD, fname); Writen(fd, line, n); /* send the GET command */ /* receive the main page */ for ( ; ; ) { if ( (n = Read(fd, line, MAXLINE)) == 0) break; /* server closed connection */ printf("read %d bytes of home page\n", n); /* do whatever with data */ } printf("end-of-file on home page\n"); Close(fd); } We will use blocking connect() and read() while retrieving the main page. But for rest of the objects, we will use non-blocking connect and I/O operations.
Initiating a connection: start_connect() function void start_connect(struct file *fptr) { int fd, flags, n; struct addrinfo *ai; ai = Host_serv(fptr->f_host, SERV, 0, SOCK_STREAM);/* obtain addrinfo for server */ fd = Socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); /* create the socket */ fptr->f_fd = fd; flags = Fcntl(fd, F_GETFL, 0); /* Set socket nonblocking */ Fcntl(fd, F_SETFL, flags | O_NONBLOCK); /* Initiate non-blocking connect to the server. */ if ( (n = connect(fd, ai->ai_addr, ai->ai_addrlen)) < 0) { if (errno != EINPROGRESS) err_sys("nonblocking connect error"); fptr->f_flags = F_CONNECTING; FD_SET(fd, &rset); /* select for reading and writing */ FD_SET(fd, &wset); if (fd > maxfd) maxfd = fd; } else if (n >= 0) /* connect is already done */ write_get_cmd(fptr); /* write() the GET command */ }
Sending HTTP GET command to the server write_get_cmd() function void write_get_cmd(struct file *fptr) { int n; char line[MAXLINE]; n = snprintf(line, sizeof(line), GET_CMD, fptr->f_name); Writen(fptr->f_fd, line, n); printf("wrote %d bytes for %s\n", n, fptr->f_name); fptr->f_flags = F_READING; /* clears F_CONNECTING */ FD_SET(fptr->f_fd, &rset); /* will read server's reply */ if (fptr->f_fd > maxfd) maxfd = fptr->f_fd; }
main() function continued while (nlefttoread > 0) { /* more files need to be downloaded */ while (nconn < maxnconn && nlefttoconn > 0) { /* find a file to read and start connection to the server for file*/ for (i = 0 ; i < nfiles; i++) if (file[i].f_flags == 0) break; if (i == nfiles) err_quit("nlefttoconn = %d but nothing found", nlefttoconn); start_connect(&file[i]); nconn++; nlefttoconn--; } rs = rset; ws = wset; n = Select(maxfd+1, &rs, &ws, NULL, NULL); for (i = 0; i < nfiles; i++) { flags = file[i].f_flags; if (flags == 0 || flags & F_DONE) continue; fd = file[i].f_fd; /* continues on the next slide */
main() function continued if (flags & F_CONNECTING && (FD_ISSET(fd, &rs) || FD_ISSET(fd, &ws))) { n = sizeof(error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &n) < 0 || error != 0) { err_ret("nonblocking connect failed for %s", file[i].f_name); } /* connection established */ FD_CLR(fd, &wset); /* no more writeability test */ write_get_cmd(&file[i]); /*send the GET command to server */ } else if (flags & F_READING && FD_ISSET(fd, &rs)) { if ( (n = Read(fd, buf, sizeof(buf))) == 0) { Close(fd); /* end of file reached */ file[i].f_flags = F_DONE; /* clears F_READING */ FD_CLR(fd, &rset); nconn--; nlefttoread--; } else { /* we are not doing any special processing on the file after we read */ printf("read %d bytes from %s\n", n, file[i].f_name); } } } } exit(0); } /* END OF PROGRAM */