280 likes | 387 Views
Advanced I/O. Computer Network Programming. Regarding Project1. Use RCS to store different revisions of your programs this is a requirement! you will loose points if you don’t conform! Your programs should be able to compile and run on SunOS 5.5 . We will do out tests on SunOS 5.5
E N D
Advanced I/O Computer Network Programming
Regarding Project1 • Use RCS to store different revisions of your programs • this is a requirement! • you will loose points if you don’t conform! • Your programs should be able to compile and run on SunOS 5.5. • We will do out tests on SunOS 5.5 • Don’t waste time in trying to reduce the time that your programs spends in transferring files. • There will tiny bonus or may be not at all. • Project 2 will be assigned soon, so that you can have larger window of time to work. • Probably on Friday.
Memory Allignment The data from memory is read usually in chunks of 2, 4, 8 bytes. We have to allign the data types in multiple of their size. Meaning that: short should start at even memory addresses long should start at memory addresses divisable by 4. char can start at any memory address strings and char arrays can start at any memory address. If you try to retrieve an integer value from a memory address which is not multiple of 4, you will most likely get a segmentation fault error: core dump!
Compiler aligns the fields of a structure struct foo { char bytes[17]; /* 17 bytes */ int numbers[33]; /* 33 * 4 bytes */ } } X; /* 149 bytes */ The allocated amount of memory for varible X is not 149, but it is 152 bytes. The reason is that integers should start at word (4 bytes) boundaries and compiler inserts 3 padding bytes after the bytes array (after first 17 bytes). Then starts the integers.
Lets see some alignment problems unsigned int x; unsigned char *buf; buf = (unsigned char *) malloc (1024); x = *((unsigned int *)buf + 2); /* you may get a segmentation fault on some machines */ x = *((unsigned int *)buf + 4); /* this is OK */ /* to prevent segmentation fault you may use memcpy ınstead of direct assignment using type casting */ memcpy ((void*) &x, (void *) buf + 2, sizeof(unsigned int)); /* this is OK - copies 4 bytes starting at address buf + 2 */ For project you may encounter similar aligment problem. So use memcpy when you are accessing the header fields of the message that I defined: [len(2), seqno(4)][data(N)] I should have defined the header in an other format like following: [seqno(4 bytes), len(4 bytes)] [data(N)]
unsighned int x; unsigned short y; char *buf; struct header { unsigned int seqno; unsigned short len; } *hptr; buf = (char *) malloc (1024); If I would have defined the header in the following format [seqno(4 bytes), len(4 bytes)] [data(N)] than you could access the fields of the hader also like following (so my header definition is not very efficient) hptr = (struct header *)buf; x = hptr->seqno; y = hptr->len; or hptr->seqno = 1000; Again you can use memcpy for this case also. y = *((unsigned short *)buf + 4);
Lets look to the header definitions of well-known protocols struct ip { u_char ip_v:4, /* half byte - version */ ip_hl:4; /* half byte - header length */ u_char ip_tos; /* 1 byte - type of service */ short ip_len; /* 2 bytes - total length */ u_short ip_id; /* 2 bytes - identification */ short ip_off; /* 2 bytes - fragment offset field */ u_char ip_ttl; /* 1 byte - time to live */ u_char ip_p; /* 1 byte - protocol */ u_short ip_sum; /* 2 byte - checksum */ struct in_addr ip_src, /* 4 bytes - source address */ ip_dst; /* 4 bytes - dest address */ }; IP Protocol Header If you look carefully, each fied is alligned according to its size. char can present at any address, short can present at even addresses and int can present at addresses multiples of 4. uchar *buffer; struct ip * hptr; /* assume buffer contains an IP datagram. Then you can access the header fields of the IP datagram as follows */ hptr = (struct ip *)buffer; printf (“ %d %d “, ntohl(hptr->ip_src), ntohs(hptr->ip_len)); …..
Lets look to the header definitions of well-known protocols TCP Protocol Header struct tcphdr { u_short th_sport; /* 2 bytes - source port */ u_short th_dport; /* 2 bytes - destination port */ tcp_seq th_seq; /* 4 bytes- sequence number */ tcp_seq th_ack; /* 4 bytes - acknowledgement number */ u_int th_off:4, /* half byte - data offset */ th_x2:4; /* half byte - (unused) */ u_char th_flags; /* 1 byte */ u_short th_win; /* 2 bytes - window */ u_short th_sum; /* 2 bytes - checksum */ u_short th_urp; /* 2 bytes - urgent pointer */ }; If you look carefully, each fied is alligned according to its size. char can present at any address, short can present at even addresses and int can present at addresses multiples of 4.
One more thing There are two ways to allocate memory for a buffer: - use char array - use malloc and there is a difference between these two in terms of memory alignment: uchar bufarray[1024]; /* array may start at any address, odd, even,…. */ uchar *bufdynamic; bufdymamic = (char *)malloc(1024); /* dynamic memory allocation gives a address pointer which is multiple of 8 - aligned*/ /* Hence it is safer to allocate memory using malloc if you would access the fields of a header or an other structure that is mapped to the buffer */ /* If you use memcpy, both are OK, but this is less efficient */
One more thing continued If you want your static buffer (char array) to start at a correctly aligned memory address then use union (this will force the compiler not start the array at an arbitrary address but at an aligned address) struct hdr { int x; int y; } *h; union aligned_buf { struct hdr h; char data[1024]; } mybuffer; /* by this definition compiler makes sure that the start address of mybuffer is word (4 bytes) aligned. / * then you can safely cast a struct hdr pointer to point to mybuffer.data and access its members */ h = (struct hdr *)mybuffer.data; h->x = …. h->y = ….
Outline for todays Class • How we can set timeout on an I/O operation • select, alarm, socket options. • New I/O functions • recv, send, recvmsg, sendmsg, readv, writev • How to determine the amount of data in socket recv buffer • How to use the standard I/O library with sockets. • independency from operating system
Timeouts • Sometimes we want to set timout on I/O operations and also on connect() function, so that they don’t block forever or for a long time. • 3 ways to set a timeout • call alarm() function which generates SIGALRM signal when timer expires. • Call select() and block on select until specified timeout value or until data is available. • Set socket options SO_RCVTIMEO and SO_SNDTIMEO • not all systems support this
connect() with timeout using alarm() static void connect_alarm(int); int connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec) { Sigfunc *sigfunc; int n; sigfunc = Signal(SIGALRM, connect_alarm); /* set the alarm clock for the process*/ if (alarm(nsec) != 0) err_msg("connect_timeo: alarm was already set"); if ( (n = connect(sockfd, (struct sockaddr *) saptr, salen)) < 0) { close(sockfd); if (errno == EINTR) errno = ETIMEDOUT; } alarm(0);/* turn off the alarm */ Signal(SIGALRM, sigfunc);/* restore previous signal handler */ return(n); } /* our SIGALRM signal handler */ static void connect_alarm(int signo) { return; /* just interrupt the connect() */ } SIGALRM signal makes the connect return after specified amount of time if connection could not be established.
UDP echo client with timeout using alarm() static void sig_alrm(int); void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; Signal(SIGALRM, sig_alrm); while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); alarm(5); /* set timer for 5 seconds */ if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) { if (errno == EINTR) fprintf(stderr, "socket timeout\n"); else err_sys("recvfrom error"); } else { alarm(0); /* clear the timer */ recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } } } static void sig_alrm(int signo) { /* just interrupt the recvfrom() */ return; }
UDP echo client with timeout using select() void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); if (readable_timeo(sockfd, 5) == 0) { fprintf(stderr, "socket timeout\n"); } else { n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } } } int readable_timeo(int fd, int sec) { fd_set rset; struct timeval tv; FD_ZERO(&rset); FD_SET(fd, &rset); tv.tv_sec = sec; tv.tv_usec = 0; return(select (fd+1, &rset, NULL, NULL, &tv)); /* > 0 if descriptor is readable */ }
UDP echo client with timeout using socket option void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; /* set timeout value to 5 seconds */ Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); if (n < 0) { if (errno == EWOULDBLOCK) { fprintf(stderr, "socket timeout\n"); continue; } else err_sys("recvfrom error"); } recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } }
New Socket I/O functions • recv(), send() • ssize_t recv(int sd, void *buf, size_t nbytes, int flags); • ssize_t send(int sd, void *buf, size_t nbytes, int flags); • similar to read and write functions exept they take flags argument: • Flags could be: • zero • MSG_DONTROUTE, MSG_DONTWAIT, MSG_OOB, MSG_PEEK, MSG_WAITALL
New Socket I/O functions • readv and writev functions • similar to read and write but allows to read into or write from multiple buffers with a single function call. • ssize_t readv (int fd, const struct iovec *iov, int iovcnt); • ssize_t writev (int fd, const struct iovec *iov, int iovcnt); • returns the number of bytes read or written, -1 on error. struct iovec { void *iov_base; size_t iov_len; } • can be used with any descriptor • for UDP, all buffers are sent in one UDP datagram
New Socket I/O functions • recvmsg() and sendmsg() functions • ssize_t recvmsg(int sd, struct msghdr *msg, int flags); • ssize_t sendmsg(int sd, struct msghdr *msg, int flags); struct msghdr { void *msg_name; /* optional address */ size_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array of buffers*/ int msg_iovlen; /* number of elements in msg_iov */ void *msg_control; /* ancillary data (control data) */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ }; Two flags of special importance: MSG_BCAST, MSG_MCAST These socket I/O functions are most generic, they can do all the things the other socket I/O functions can do.
msghdr structure sockaddr structure (variable length) (can contain an IP address (IPv4 or IPv5) msg_name msg_namelen msg_iov msg_iovlen iov_base Variable size buffer msg_control iov_len msg_controllen iov_base buffer msg_flags iov_len iov_base buffer iov_len DATA is stored in these buffers (before sending or after receiving) (can return control information, for example the IP destination address for a receivd UDP datagram with recvmsg function) control msg structure (variabe length)
Ancillary Data Ancillary data should be carried using the control message structure Control message format cmgg_len cmg_hdr cmgg_level cmg_len cmgg_type data Data could be IP address, descriptor, credentials, etc. Ancillary data can be originated form kernel or from sending process. Protocol cmg_level cmg_type descripton IPv4 IPPROTO_IP IP_RECVDSTADDR recv dest address with UDP data Unix SOL_SOCKET SCM_RIGHTS send/recv descriptor Domain SCM_CREDS send/recv user credentials
How much data is queued in the socket recv buffer • Sometimes, we would like to learn how much data is queued to be read on a socket. • 3 techniques • use non-blocking I/O ( we will see this later) • call recv with MSG_PEEK and MSG_DONTWAIT flags • for UDP, returns the size of the first UDP datagram queued (there may be more than one UDP datagram) • for TCP, returns the all data queued in the recv buffer • use ioctl() function with FIONREAD command. • Returns the amount of all the buffered data in the socket received buffer
Standard I/O and sockets • read, write, send, recv are not ANSI C functions and therefore can only be used in UNIX systems • It is also possible to use the standard I/O library functions with sockets. Standard I/O library ispart of ANSI C • the code could be more easily ported to non-Unix systems • Standard I/O library uses buffers and is stream based • the data to be output is stored in those buffers until the buffer is full. Then the output operation to device takes place
Things to consider • A standard I/O stream can be created from any descriptor (including sockets) by calling fdopen function. • The inverse is done using fileno function: given a I/O stream, get the corresponding integer descriptor. • Sockets are full duplex. • Standard I/O streams can also be full duplex but it requires careful attention when doing input and output on the stream at the same time. • Solution: open two I/O streams for socket: one for reading, and one for writing.
Str_echo function using Standard I/O void str_echo(int sockfd) { char line[MAXLINE]; FILE *fpin, *fpout; /* convert the socket descriptor into input and output stream */ fpin = fdopen(sockfd, "r"); fpout = fdopen(sockfd, "w"); for ( ; ; ) { /* read a line from the socket */ if (fgets(line, MAXLINE, fpin) == NULL) return; /* connection closed by other end */ /* write a line to the socket */ fputs(line, fpout); } } fgets and fputs are part of the standard I/O library (stdio.h) read and write, etc. are not part of the standard I/O library
Problem with this Solution • Standard I/O uses buffered I/O unless otherwise specified. • Hence when the echo server sends back the lines using fputs, they are actually buffered in the I/O library and transmitted only when the buffer is full • User will not be able to see the echoed lines and the server terminates upon which the buffer is flushed. Application fputs(line, MAXLINE, fpout) The lines are buffered until the buffer is full, then they are sent to the socket Standard I/O library stream buffer (8192 bytes) Kernel Socket Transport layer start transmitting the dataimmediately (if possible)using TCP or UDP
Standard I/O library buffering • 3 types of buffering is done by the standard I/O library • Fully buffered: I/O takes place when the buffer is full, or the process calls explicitly fflush() function, or the process terminates by calling exit. • Line buffered: I/O takes place only when new line (\n) is encountered on the steam, or when the process calls fflush(), terminates by calling exit. • Unbuffered I/O. I/O takes place immediately when the standard I/O output function is called.
What does Unix do • Standard input and output is fully buffered, unless they refer • to a terminal device in which case they are line buffered • /dev/console, /dev/ttya, /dev/ttyp0, /dev/ptyp0,….are • examples of terminal devices • All other streams are fully buffered, unless the stream refers • to a terminal device in which case they are again line buffered. • Force the output stream to be line buffered by calling setvbuf function • Call fflushfunction after each call to standard I/O output function fputs. Solution to our Problem