TCP/IP Network Programming Chapter Thirteen: Various IO Functions

send&recv function

send&recv in Linux

Although the send&recv function was introduced in Chapter 1, it was introduced based on the Windows platform at that time. This section will introduce
the send&recv function under the Linux platform. In fact, there is no difference between the two.

#include <sys/socket.h>
ssize_t send(int sockfd, const void * buf, size_t nbytes, int flags);
//成功时返回发送的字节数,失败时返回-1。

       sockfd  //表示与数据传输对象的连接的套接字文件描述符
       buf     //保存待传输数据的缓冲地址值
       nbytes  //待传输的字节数
       flags   //传输数据时指定的可选项信息

Compared with the send function in Windows in Chapter 1, the above function has some differences in the declared structure name. However, the order, meaning, and usage of the parameters are exactly the same, so the actual difference is not big. Next, introduce the recv function, which
is not much different from the Windows recv function.

#include <sys/socket.h>
ssize_t recv(int sockfd, void * buf, size_t nbytes, int flags);
//成功时返回接收的字节数(收到EOF时返回0),失败时返回-1。
   sockfd   //表示数据接收对象的连接的套接字文件描述符
   buf      //保存接收数据的缓冲地址值    
   nbytes    //可接收的最大字节数
   flags     //接收数据时指定的可选项信息

The last parameter of the send function and recv function is optional when sending and receiving data. This option can convey multiple messages in bits or at the same time. Organize the types and meanings of optional items through Table 13-1

MSG_DONTROUTE Specifies not to use routing to look up the destination address (for local communication)
MSG_OOB Send or receive out-of-band data
MSG_PEEK View data in the receive queue without removing it from the queue
MSG_WAITALL In a receive operation, if the requested number of bytes is insufficient, block the thread until the data is completely received
MSG_TRUNC If the receive buffer is insufficient to hold the incoming data, truncate the data without reporting an error
MSG_CTRUNC If the control information is insufficient to hold the incoming data, truncate the control data without reporting an error

In addition, different operating systems support the above options differently.

MSG_OOB: Send urgent message

For the understanding of emergency news, some people may compare it to a critically injured patient in a hospital who needs emergency first aid, so he needs to speed up the treatment. If this understanding is not in place, the following problems may arise. If it is an urgent message, can it exchange positions with the previous non-urgent message, and the urgent message should be processed in advance? If it is an urgent message, can its transmission speed be accelerated? If it is an urgent message, is the meaning of the internal message itself urgent?

In fact, the above conjectures are all wrong. The urgent message here is just a flag bit at the TCP level (TCP header). That is to say, the significance of this urgent message is to urge the processing of the message, rather than a message with a limited form of emergency transmission. And due to the sequential transmission of TCP and the characteristics of the physical link layer. It is impossible to get special treatment in order and speed. Then its final result is actually to deal with it in an urgent way.

check input buffer

By setting the MSG_PEEK option and MSG_DONTWAIT option, to verify whether there is received data in the input buffer. When the MSG_PEEK option is set and the recv function is called, the data in the input buffer will not be deleted even if it is read. Therefore, this option is usually used in conjunction with MSG_DONTWAIT to call functions that verify the existence of data to be read in a non-blocking manner. The results of using the MSG_PEEK and MSG_DONTWAIT options are given below by way of example.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define BUFSIZE 30
void error_handling(char *message);

int main(int argc, char *argv[]){
    int acpt_sock, recv_sock;
    struct sockaddr_in acpt_adr, recv_adr;
    int str_len, state;
    socklen_t recv_adr_sz;
    char buf[BUF_SIZE];
    if(argc!=2){
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    acpt_sock=socket(PF_INET, SOCK_STREAM,0);
    memset(&acpt_adr, 0, sizeof(acpt_adr));
    acpt_adr.sin_family=AF_INET;
    acpt_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    acpt_adr.sin_port=htons(atoi(argv[1]));

    if(bind(acpt_sock, (struct sockaddr*)&acpt_adr, sizeof(acpt_adr))==-1)
          error_handling("bind() error");
    listen(acpt_sock,5);

    recv_adr_sz=sizeof(recv_adr);
    recv_sock=accept(acpt_sock, (struct sockaddr*)&recv_adr, &recv_adr_sz);

    while(1){
         str_len=recv(recv_sock, buf, sizeof(buf)-1, MSG_PEEK|MSG_DONTWAIT);
         if(str_len>0)
         break;
    }

    buf[str_len]=0;
    printf("Buffering %d bytes: %s \n",str_len, buf);
    str_len=recv(recv_sock,buf, sizeof(buf)-1,0);
    buf[str_len}=0;
    printf("Read again %s \n", buf);
    close(acpt_sock);
    close(recv_sock);
    return 0;
}

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n',stderr);
    exit(1);
}

It can be verified by running the results that the data sent only once is read twice, because the MSG_PEEK option is set when the recv function is called for the first time.

readv&writev function

The readv&writev functions introduced in this section help to improve the efficiency of data communication. First introduce how to use these functions, and then discuss
their reasonable application scenarios.

Use the readv&writev function

The functions of the readv&writev function can be summarized as follows: "A function for integrating and sending data." That is
to say, the data stored in multiple buffers can be sent together through the writev function, and multiple buffers can be received separately through the readv function. Therefore, proper use of these two functions can reduce the number of IO function calls. The following first introduces the writev function.

#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec * iov, int iovcnt);
//成功时返回发送的字节数,失败时返回-1。
    filedes  //表元数据传输对象的套接字文件描述符。但该函数并不只限于套接字
    iov      //iovec结构体数组的地址值,结构体iovec中含待发送数据的位置和大小信息
    iovcnt   //向第二个参数传递的数组长度

The declaration of the array iovec structure appearing in the second parameter of the above function is as follows.

struct iovec{
     void *iov_base;//缓冲地址
     size_t iov_len;//缓冲大小
}

It can be seen that the structure iovec is composed of the address value of the buffer (char array) storing the data to be sent and the length information of the actually sent data.

I believe that you have mastered the usage and characteristics of the writev function, and an example will be given next.

#include<stdio.h>
#include<sys/uio.h>
int main(int argc, char *argv[]){
    struct iovec vec[2];
    char buf1[]="ABCDEFG";
    char buf2[]="1234567";
    int str_len;
    vec[0].iov_base=buf1;
    vec[0].iov_len=3;
    vec[1].iov_base=buf2;
    vec[1].iov_len=4;

    str_len=writev(1, vec,2);
    puts("");
    printf("Write bytes: %d \n",str_len);
    return 0;
}

The following introduces the readv function, which is just the opposite of the writev function.

#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec * iov, int iovcnt);
//成功时返回接收的字节数,失败时返回-1。
 filedes  //传递接收数据的文件(或套接字)描述符。
 iov      //包含数据保存位置和大小信息的iovec结构体数组的地址值。
 iovcnt   //第二个参数中数组的长度

We have already learned the writev function, so the usage of the readv function is given directly through examples.

#include <stdio.h>
#include <sys/uio.h> 
#define BUF_SIZE 100
int main(int argc, char *argv[]){
    struct iovec vec[2];
    char buf1[BUF_SIZE]={0,};
    char buf2[BUF_SIZE]={0,};
    int str_len;

    vec[0].iov_base=buf1;
    vec[0].iov_len=5;
    vec[1].iov_base=buf2;
    vec[1].iov_len=BUF_SIZE;

    str_len=readv(0, vec,2);
    printf("Read bytes: %d \n", str_len);
    printf("First message: %s \n", buf1);
    printf("Second message: %s \n",buf2);
    return 0;
}

Reasonable use of readv&writev functions

Which situation is suitable to use readv and writev functions? In fact, all situations where this function can be used apply. For example, when the data to be transmitted is located in different buffers (arrays), the write function needs to be called multiple times. At this time, the alternative operation can be called through one write function, which will of course improve efficiency. Similarly, when the data in the input buffer needs to be read into different locations, the read function can be called multiple times, but the efficiency can be greatly improved by using the readv function once. Even from a C language perspective, reducing the number of function calls can improve performance accordingly. But its greater significance lies in reducing the number of packets.

Windows-based implementation

We completed the data exchange through the send function and the recv function in the previous Windows example, so we only need to add
the example related to setting options. There is a point to consider here:
"There is no signal handling mechanism in Windows like Linux does."

Therefore, event handling for this optional item cannot be completed in Windows, and other methods need to be considered. We solve this problem through the select function. The three monitoring objects of the select function mentioned earlier are as follows.
□Is there a socket to receive data?
□What are the sockets that do not need to block data transmission?
□Which sockets are abnormal?
Among them, Chapter 12 does not explain the "exceptional socket" separately. An "exception" is an unusual flow of program execution. Therefore, receiving Out-of-band data is also abnormal. That is to say, using this feature of the select function can receive Out-of-band data on the Windows platform, refer to the following example.

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#define BUF_SIZE 30
void ErrorHandling(char *message);

int main(int argc, char *argv[]){
    WSADATA wsaData;
    SOCKET hAcptSock,hRecvSock;
    SOCKADDR_IN recvAdr;
    SOCKADDR_IN sendAdr;
    int sendAdrsize, strLen;
    char buf[BUF_SIZE];
    int result;
    fd_set read, except, readcopy, exceptcopy;
    struct timeval timeout;

    if(argc!=2) {
         printf("Usage : %s <port>\n", argv[0]);
         exit(1);
    }

    if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
        ErrorHandling("WSAStartup() error!");

    hAcptSock=socket(PF_INET,SOCK_STREAM,0);
    memset(&recvAdr, 0, sizeof(recvAdr));
    recvAdr.sin_family=AF_INET;
    recvAdr.sin_addr.s_addr=htonl(INADDR_ANY);
    recvAdr.sin_port=htons(atoi(argv[1]));

    if(bind(hAcptsock,(SOCKADDR*)&recvAdr,sizeof(recvAdr))==SOCKET_ERROR)
        ErrorHandling("bind() error");
    if(listen(hAcptSock,5)==SOCKET_ERROR)
        ErrorHandling("listen() error");

    sendAdrsize=sizeof(sendAdr);
    hRecvSock=accept(hAcptSock,(SOCKADDR*)&sendAdr, &sendAdrSize);
    FD_ZERO(&read);
    FD_ZERO(&except);
    FD_SET(hRecvSock,&read);
    FD_SET(hRecvSock,&except);

    while(1){
        readcopy=read;
        exceptcopy=except;
        timeout.tv_sec=5;
        timeout.tv_usec=0;

        result=select(0, &readcopy,0,&exceptCopy,&timeout);
        if(result>0){
             if(FD_ISSET(hRecvSock,&exceptCopy)){//发生异常
                  strLen=recv(hRecvSock, buf,BUF_SIZE-1, MSG_OOB);
                  buf[strLen]=0;
                  printf("Urgent message: %s \n",buf);
             }
             if(FD_ISSET(hRecvSock, &readcopy)){//正常读取事件
                  strLen=recv(hRecvSock,buf,BUF_SIZE-1, 0);
                  if(strLen==0){
                  closesocket(hRecvSock);
                  break;
                  }
             else{
                  buf[strLen]=0;
                  puts(buf);
             }
             }
        }
    }
closesocket(hAcptSock);
WSACleanup();
return 0;
}

void ErrorHandling(char *message){
     fputs(message, stderr);
     fputc('\n',stderr);
     exit(1);
}

The functions of writev and readv functions in Linux can be realized by "overlapping IO" in Windows.

Guess you like

Origin blog.csdn.net/Reol99999/article/details/131762356