IO特性(1): socket相关的几个高级IO函数(Unix网络编程笔记)

Subject: Advanced I/O Function


1: There are three ways to set timeout(block):
    First,signal driver: relying on the signal handle function of signal SIGALRM to generate a signal SIGINT to the blocked funtion, suck as connect, recv, read etc. Then the blocked function will return and get a EINTR error. For example:


  1. 1. signal(SIGALRM, alarm_handler); 2. ... 3. alarm(nsec); // if timeout, this will generate SIGALRM and interrupt recvfrom 4. recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL) ... // this will be interrupt by the SIGINT 5. 6. static void alarm_handler(int signo) { 7. return; // just interrupt the recvfrom() 8. } 
  2. Second, select function, this will make all read or write or except file descriptor blocked at the call of select but not the read or write unless one or more event happens, then select will choose the correct descriptor to process the event.This function can be used with any type descriptors including socket(tcp, udp or sctp),pipe(FIFO), file descriptor and so forth(This illustrates the philosophy of UNIX: everything is a file). This function is also a implementation of multiplex of IO(pselect, poll as the same).

    fuction prototype:        

int select(int fd, fd_set* read_set, fd_set* write_set, fd_set* except_set, struct timeval tv)
    There are also four macro two handle the structure of fd_set, they are:
        1. FD_ZERO(fd_set*) // always initial the fd_set 2. FD_SET(int fd, fd_set*) // add the fd to the fd_set 3. FD_ISSET(int fd, fd_set*) // check if the fd is int the fd_set 4. FD_CLR(int fd, fd_set*) // clean the fd from the fd_set 

    Because the fd_set is a structure of int array, so you can assign a fd_set to another
    The last arguement of select implement the functon of setting the timeout, it can attain to a precision of millisecond.
    The reture value of select: -1 on an error, 0 if a timeout occurs, or a positive value specifying the number of ready descriptors.
    Last, seting the SO_SNDTIMEO and SO_RCVTIMEO option of setsockopt function. Autovividly, this just impact on the socket descriptor. For example:
  1. struct timeval tv; 2. tv.tv_sec = 10; tv.tv_usec = 0; 3. setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); 4. // this will block the socket sockfd at most 10 seconds in reading.

2: The flag of function of send and recv
    These two functions are similar to the standard read and write, other than the last flag argument.
    These flags are:
    MSG_DONTROUTE: tell the kernel not to perform a lookup of the routing table.(only send)
    MSG_DONTWAITE: tell the kernel perform the read or write function once the buffer not empty.
    MSG_OOB:       tell the kernel there are out_of_band data int the datagram(truely one byte,tcp head URG option).
    MSG_PEEK:      (only recv) authenticate the header before receiving the data int the buffer.
    MSG_WAITALL:   tell the kernel not to return from a read function until the requested number of bytes have been read.(I think this flag relates with the SO_RCVLOWAT socket option ).

    MSG_PEEK is not a good flag for tcp since the data size is not specified.
3: Scatter read and gather write: readv and writev

1. #include <sys/uio.h> 2. /* fd can be any descriptor */ 3. ssize_t readv(int fd, const struct iovec* iov, int iovcnt); 4. ssize_t writev(int fd, const struct iovec* iov, int iovcnt); // This is an atomic operation 5. struct iovec { 6. void* iov_base; // starting address of buffer 7. size_t iov_len; // size of buffer 8. }
From the structure, we can see these two function are not type-specified. They are operation just on memory.
There is some limit to the number of elements int array of iovec structures. POSIX requires that the constant IOV_MAX be defined and that its value be at least 16.

4: recvmsg and sendmsg functions(#include <sys/socket.h>)
    Various input or output functions could be replaced with calls to these two functions.

1. ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags); // msg is a pointer, therefore, value-result passing 2. ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags); // flages similar to the send and recv function 3. struct msghdr { 4. void* msg_name; // protocol address(struct sockaddr(_in|_in6|_un|_storage)?/*) : regex, haha 5. socklen_t msg_namelen; // size of protocol address, normaly 16 6. struct iovec* msg_iov; // scatter/gather array ( writev, readv) 7. int msg_iovlen; // elements int msg_iov(number, not size) 8. void* msg_control; // ancillary data(struct cmsghdr), specifying the location 9. socklen_t msg_controllen; // length of ancillary data(bytes), specifying the size 10. int msg_falgs; // only used by recvmsg,return value is used by kernel to drive its receive processing 11. }
The return msg_flags value including MSG_OOB, MSG_EOR, MSG_BCAST, MSG_MCAST, MSG_NOTIFICATION and so forth.
MSG_EOR: at least one error occurs.
MSG_BCAST: broadcast datagram.
MSG_TRUNC: the datagram was truncated, there are more data than iovlen indicated
MSG_CTRUNC: the ancillary data was truncate, there are more data than controllen indicated
MSG_MCAST: multicast datagram.
MSG_NOTIFICATION: only for SCTP, an event notification

Ancillary Data:

1. struct cmsghdr { 2. socklen_t cmsg_len; // length in bytes, including this structrue 3. int cmsg_level; // originating protocol 4. int cmsg_type; // protocol-specific type 5. /* note: followed by unsigned char cmsg_data[], not show, but exist, just like a memory gap. This is really strange, but not affect comprehension I think. */ 6. };

 


   Msg_control points to the first ancillary data object, and the total length of the ancillary data is specified by msg_controllen. Each object is preceded by a cmsghdr structure that describe the object. There are paddings surely.
   The ancillary data returned by recvmsg can contain any number of ancillary data objects.
   The macros processing the ancillary data:

1. struct cmsghdr* CMSG_FIRSTHDR(struct msghdr* mhdrp); 2. //return pointer to first cmsghdr structure or NULL if no ancillary data 3. struct cmsghdr* CMSG_NXTHDR(struct msghdr*, struct cmsghdr*); 4. // return pointer to next cmsghdr of the second arguement or NULL; 5. unsigned char* CMSG_DATA(struct cmsghdr*) 6. // return the data address associated with cmsghdr 7. unsinged int CMSG_LEN(unsigned int length); // just one object equal cmsg_len 8. // return the length of data and cmsghdr, but not including the last padding 9. unsinged int CMSG_SPACE(unsinged int lenght); 10. // CMSG_LEN plus the size of the last padding 

   These macros would be used in the following pseudocode:
  for ( cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr) ) { if ( cmsgptr->cmsg_level == ... && cmsgptr->cmsg_type == ... ) { u_char* ptr = CMSG_DATA(cmsgptr); ... } }

5: Sockets and StandardI/O
    We can use fdopen(sockfd, flag) to make a socket become a standart I/O stream, and then use the standard I/O function. But notice: wo should not use fp = fdopen(sockfd, "r/+|w/+"), since we could lose the control of the I/O somehow and eventually lead to unpredictable error. The best way is:

fpin = fdopen(sockfd,"r") then fpout = fdopen(sockfd, "w").

    This is a full-duplex way while the TCP and UDP socket are duplex.
    There is also a buffering problem when we use the standard I/O to communicate with the other side because of standard I/O's buffer. This pushes the input data into the buffer but not write until the buffer is full or enforced to write(fflush(fp) or EOF is encountered(FIN is sent, then exit not _exit is called, the latter will lose all data in the buffer)).
    Ther are three types of buffering performed by the standard I/O library: fully buffered(stdout, stdin), line buffered(stream refer to terminal device) and unbuffered(stderr). We can use the function setvbuf to set these options(see also setbuf, setbuffer, setlinebug). BEST not using the standard I/O for sockets.


你可能感兴趣的:(编程,IO,网络,unix,socket,struct)