In the previous article, we implemented a readn function to read fixed bytes of data to avoid the problem of sticking packets. If the length of each field in the application layer protocol is fixed, it is very convenient to use readn for reading. For example, a protocol for uploading files on the client is designed. The first 12 bytes indicate the file name, and the file names exceeding 12 bytes are truncated. The file names below 12 bytes are supplemented with '\ 0, the file content starts from 13th bytes. After uploading all the file content, close the connection. The server can call readn to read 12 bytes and create a file based on the file name, then, read the file content and save the disk in a loop. The condition at the end of the loop is that read returns 0.
Protocols with fixed field lengths are often not flexible enough to adapt to new changes. The fields of the TFTP protocol mentioned above are variable-length, with the separator '\ 0'. The file name can be any length. Then, let's look at several option fields such as blksize, the tftp protocol does not specify that the value from the MTH byte to the nth byte is the value of blksize, the option description "blksize" is combined with its value "512" to form a variable length field.
Therefore, common application-layer protocols all contain variable-length fields. The separators between fields use line breaks '\ n', which is more common than' \ 0', such as HTTP protocol. It is inconvenient to use readn to read the variable long field protocol. Therefore, we implement a Readline function similar to fgets.
First, let's look at a system function Recv similar to read.
# Include <sys/types. h>
# Include <sys/socket. h>
Ssize_t Recv (INT sockfd, void * Buf, size_t Len, int flags );
The Recv function is similar to the READ function, but can only read socket descriptors, rather than general file descriptors, with one additional flag parameter.
The flags parameter has two important parameters: MSG_OOB, which is the option for reading out-of-band data. The TCP Header has a 16-bit emergency pointer value. The other is msg_peek, that is, data is returned from the buffer but the buffer is not cleared. This is different from read.
The following uses the encapsulated Recv function to implement the Readline function:
C ++ code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
/* Recv () can only read and write sockets, rather than general file descriptors */ Ssize_t recv_peek (INT sockfd, void * Buf, size_t Len) { While (1) {Int ret = Recv (sockfd, Buf, Len, msg_peek); // the buffer is not cleared after the flag is read. If (ret =-1 & errno = eintr) Continue; Return ret; } } /* Return if '\ n' is read, and a row can contain a maximum of maxline characters */ Ssize_t Readline (INT sockfd, void * Buf, size_t maxline) { Int ret; Int nread; Char * bufp = Buf; Int nleft = maxline; Int COUNT = 0; While (1) { Ret = recv_peek (sockfd, bufp, nleft ); If (Ret <0) Return ret; // If the returned value is smaller than 0, the operation fails. Else if (ret = 0) Return ret; // If 0 is returned, the connection is closed.
Nread = ret; Int I; For (I = 0; I <nread; I ++) { If (bufp [I] = '\ n ') { Ret = readn (sockfd, bufp, I + 1 ); If (Ret! = I + 1) Exit (exit_failure ); Return ret + count; } } If (nread> nleft) Exit (exit_failure ); Nleft-= nread; Ret = readn (sockfd, bufp, nread ); If (Ret! = Nread) Exit (exit_failure ); Bufp + = nread; Count + = nread; } Return-1; |
In the Readline function, we first peat the number of characters in the buffer zone and then check whether the linefeed '\ n' exists. If so, we can use readn to connect to the linefeed to read it together, if the bufp does not exist, read the preceding data into the bufp and move the location of the bufp to the beginning of the while loop, when we call readn to read data, the buffer will be cleared, because readn calls the READ function. Note that, if '\ n' is read for the second time, count is used to save the number of characters read for the first time, and the returned RET must be added with the original data size.
Using the Readline function can also be considered as a solution to the problem of sticking to the package, that is, ending with '\ n' as a message. For the server side, you can change the do_service function based on the previous fork program as follows:
C ++ code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Void do_echoser (INT conn) { Char recvbuf [1024]; While (1) { Memset (recvbuf, 0, sizeof (recvbuf )); Int ret = Readline (Conn, recvbuf, 1024 ); If (ret =-1) Err_exit ("Readline error "); Else if (ret = 0) // disable the client { Printf ("client close \ n "); Break; }Fputs (recvbuf, stdout ); Writen (Conn, recvbuf, strlen (recvbuf )); } } |
The client changes are similar. I will not repeat them again, and the test output is also normal.
Refer:
Linux C Programming one-stop learning
Chapter 1 TCP/IP details
UNP