In the previous article, we implemented a READN function to read fixed byte data in order to avoid the problem of sticky packets. If the length of each field in the application layer protocol is fixed, it is very convenient to read it with READN. For example, to design a client upload file protocol, specify the first 12 bytes of the file name, more than 12 bytes of file name truncation, less than 12 bytes of file name with ' "", starting from the 13th byte is the content of the file, upload all the file content after closing the connection, the server can first call Readn read 12 bytes, Creates a file based on the filename, and then invokes the read read file contents in a loop with the disk, and the condition of the loop ending is read returns 0.
Fixed field length protocols are often inflexible and difficult to adapt to new changes. The various fields of the TFTP protocol mentioned earlier are variable in length, to ' blksize ' as the separator, file name can be arbitrarily long, and then look at several options fields such as the TFTP protocol does not specify from m bytes to nth byte is blksize value, but the description of the option "Blksize" Make a variable-length field with its value "512".
Therefore, the common application-layer protocols are those with variable-length fields, and the delimiters between fields are more common than ' n ', such as the HTTP protocol, with a newline. A protocol with variable length fields is very inconvenient to read with READN, for which 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 the socket descriptor, not the general file descriptor, and a single flag parameter.
Flags parameters are more important than two, one is Msg_oob, that is, the option to read Out-of-band data, TCP head has an emergency pointer 16-bit value. The other is Msg_peek, which returns data from the buffer without emptying the buffer, which is different from read.
The following uses the encapsulated recv function to implement the ReadLine function:
/* RECV () can only read and write sockets, not general file descriptors */ssize_t recv_peek (int sockfd, void *buf, size_t len) {while (1) { int ret = recv (SOCKFD, buf, Len, Msg_peek);
After the flag bit is set, the buffer is not cleared if (ret = 1 && errno = = eintr) continue;
return ret;
}/* read ' \ n ' returns, a line of up to 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; Returns less than 0 to indicate failure else if (ret = 0) return ret;
Return 0 indicates that the other side closes the connection 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 "peep" with Recv_peek. How many characters are there in the buffer now, then see if there is a newline character ' \ n ', and if so, read with the READN connected newline character and, if not, read the preceding data into the BUFP, And move the BUFP position, back to the while loop, and then from the current BUFP position, note that when we call READN read the data, that part of the buffer will be emptied, because READN called the read function, it should also be noted that if the second read the ' \ N ', you first save the number of characters you read first, and then return the RET with the original data size.
Using the ReadLine function can also be considered a solution to the problem of sticky packets, which is to use ' \ n ' to end as a message. For the server side, you can change the Do_service function on the basis of the previous fork program as follows:
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) //client closes
{
printf ("Client close\n");
break;
Fputs (Recvbuf, stdout);
Writen (conn, Recvbuf, strlen (RECVBUF));
}
The changes to the client are similar, and the test output is normal again.