CSC209H Lecture 10 Dan Zingaro March 18, 2015
Creating a Client To create a client that can connect to a server, call the following, in order: socket: create a communication endpoint This is the same as the first step when creating a server connect: connect to a server read, write: receive and send data with the server
connect The connect syscall connects to a listening server socket. int connect(int sockfd, struct sockaddr *addr, socklen_t add_len); sockfd: the FD returned from socket addr: address and port of the server add_len: set it to sizeof(addr) connect returns 0 on success, -1 on error
Simple Writing Client (writeclient.c) Use this to connect to readserver.c. #define PORT 7004 int main(int argc, char* argv[]) { int soc, num, err; char port[10], buf[128]; sprintf (port, "%d", PORT); struct addrinfo *info, hints; struct sockaddr_in peer; memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; if ( argc!= 2) { fprintf(stderr, "Usage: %s hostname\n", argv[0]); exit(1); if ((err = getaddrinfo(argv[1], port, &hints, &info))) { fprintf (stderr, "%s\n", gai_strerror(err)); exit (1);
Simple Writing Client... (writeclient.c) peer = *(struct sockaddr_in *)(info->ai_addr); if ((soc = socket(af_inet, SOCK_STREAM, 0)) == -1) { perror ("socket"); exit (1); if (connect(soc, (struct sockaddr *)&peer, sizeof(peer)) == -1) { perror ("connect"); exit (1); while ((num = read(stdin_fileno, buf, sizeof(buf)))) write (soc, buf, num); close(soc); return(0);
Waiting on Multiple File Descriptors Assume that a parent p has two open file descriptors open for reading e.g. two sockets, two files, two reading ends of pipes, etc. The parent wants to read data from both... fd1 fd2 p
Waiting on Multiple File Descriptors... What if the parent doesn t know who will speak first? If it does a read on fd1, it will block until fd1 has data. But fd2 could already have some data waiting! For the same reason, the parent can t block on fd2: if it did, fd1 could have data but be ignored Extreme case: what if fd1 sends something once an hour, but fd2 sends something every minute?
Solution 1: fork Parent could fork one process for each FD it wants to monitor; each child monitors one FD Often requires the parent to set up communication mechanisms; e.g. A pipe used by children to communicate with the parent Shared memory accessible to both parent and children By default, parent and child have their own memory after fork; can t use that memory for communication Does not scale well because of the number of processes created
Solution 1: fork... In a forking server, the parent creates a child for each accepted client. while (1) {... fd = accept(...); ret = fork(); if (ret == 0) child(fd); else close(fd);
Solution 2: Nonblocking Reads You can change the behavior of read so that it returns -1 (and sets errno to EAGAIN) if no data is available read will never block in this mode However, this can lead to complex or inefficient code e.g. using a tight read-loop until data arrives
Solution 2: Nonblocking Reads... char buf[1024]; ssize_t bytesread;...open fd1 and fd2 if (fcntl(fd1, F_SETFL, O_NONBLOCK) == -1) { perror("fcntl"); exit(1); if (fcntl(fd2, F_SETFL, O_NONBLOCK) == -1) { perror("fcntl"); exit(1); for ( ; ; ) { bytesread = read(fd1, buf, sizeof(buf)); if ((bytesread == -1) && (errno!= EAGAIN)) return; /* real error */ else if (bytesread > 0) dosomething(buf, bytesread); bytesread = read(fd2, buf, sizeof(buf)); if ((bytesread == -1) && (errno!= EAGAIN)) return; /* real error*/ else if (bytesread > 0) dosomething(buf, bytesread);
Solution 3: select (Kerrisk 63.2.1) select is a syscall that allows you to monitor several FDs without forking multiple children or tight-looping You tell select the FDS that are of interest, using three FD sets The FDs monitored for reading, the FDs monitored for writing, and the FDs whose exceptions you want to know select blocks until at least one of those FDs has action The FDs that remain in the FD sets are the ones on which you can read without blocking, on which you can write without blocking, or on which there is an exception
select... int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); select returns the number of FDs that are ready, or returns -1 on error nfds is one more than the highest-numbered FD of interest e.g. if you are interested in FDs 3, 9, and 50, pass 51 for nfds
select... int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); readfds: the set of FDs you are interested in reading writefds: the set of FDs you are interested in writing exceptfds: the set of FDs in whose exceptions you are interested Set any of these to NULL if you don t care about that condition The sets are modified by select to contain only the FDs with action of the requested type
select... int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); timeout: if you don t want select to timeout, use NULL Otherwise, pass a pointer to a struct timeval If the timeout occurs before any FDs are ready, select returns 0 As with the fd_sets, select can modify timeout struct timeval { long tv_sec; //seconds long tv_usec; //microseconds ;
FD Sets Sets of FDs are constructed in much the same way as signal sets i.e. you can empty a set, fill a set, or add or remove individual FDs void FD_ZERO(fd_set *fdset); //empty a set void FD_SET(int fd, fd_set *fdset); //add FD void FD_CLR(int fd, fd_set *fdset); //remove FD int FD_ISSET(int fd, const fd_set *fdset); // in, membership
Example: FD Sets Assume fd1 and fd2 are open FDs. Whichever fds are in the set after select finishes have data ready to read. fd_set set, retset; FD_ZERO(&set); FD_SET(fd1, &set); FD_SET(fd2, &set); if (fd1 > fd2) maxfd = fd1; else maxfd = fd2; retset = set; //copy set, because select changes it while (select(maxfd + 1, &retset, NULL, NULL, NULL) > 0) { if (FD_ISSET(fd1, &retset))... read from fd1 (won t block) if (FD_ISSET(fd2, &retset))... read from fd2 (won t block) retset = set;
Full Example (threepipes.c) This example has a parent waiting for data from three children Three pipes are used; each child sends data through a different pipe The parent doesn t know the order that it will receive data, so it uses select on the reading ends of the pipes The parent echoes what it receives, and quits when all three writing ends are closed by the children c1 c2 c3 write write parent write pipe pipe pipe read read read
Full Example... int main(void) { int pip[3] [2], i, j; for (i = 0; i < 3; i++) { if (pipe(pip[i]) == -1) { perror("pipe"); exit(1); switch(fork()) { case -1: perror("fork"); exit(1); case 0: for(j = 0; j < i; j++) { //Close unused close(pip[j] [0]); close(pip[j] [1]); child(pip[i]); parent(pip);
Full Example... int parent(int p[3][2]) { char buf[msg_size]; fd_set set, retset; int numactive = 3, i; for(i = 0; i < 3; i++) //Close write ends close(p[i][1]); FD_ZERO(&set); //Put read FDs in set for(i = 0; i < 3; i++) FD_SET(p[i][0], &set);
Full Example... retset = set; //save set while (numactive > 0 && select(p[2][0] + 1,&retset,NULL,NULL,NULL) > 0) { for(i = 0; i < 3; i++) { if(fd_isset(p[i][0], &retset)) { if (read(p[i][0], buf, MSG_SIZE) <= 0) { close(p[i][0]); FD_CLR(p[i][0], &set); numactive--; else { printf("message from child%d\n", i); printf("msg=%s\n",buf); retset = set; exit(0);
Full Example... int child(int p[2]) { int count; close(p[0]); //close read end for(count = 0; count < 2; count++) { write(p[1], msg1, MSG_SIZE); /* pause for a random amount of time */ sleep(getpid() % 4); write(p[1], msg2, MSG_SIZE); //last message exit(0);
select and Servers Recall that servers use accept to block until a client connects While you re stuck in the accept, you can t read and write with existing clients e.g. Last week, our readserver could handle only one client at a time Now, we can use select to allow a server to wait for new clients while at the same time servicing existing clients Put the listening FD and the FD for each client into the readfds parameter
select and Servers... After select returns, check to see who is in the readfds set If the listening FD is in there, a new client is connecting If an existing client s FD is in there, read that FD If read returns 0 or -1, the client has dropped If read returns a positive integer k, you have received k bytes from that client
Limitations of Select select causes fd_sets to be copied to/from the kernel on every call select does not scale well when monitoring too many FDs Kernel must loop through the first nfds FDs to determine which ones are in the sets and hence which FDs to monitor We must loop through the first nfds FDs when select returns select can monitor at most FD_SETSIZE FDs (1024 on Linux) Poorer performance when FDs to monitor are sparse