TCP/IP网络编程笔记Chapter I -12多种I/O函数

1.send & recv函数

(1)Linux中的send & recv

Linux中的send & recv和Windows平台的send & recv并没有差别,函数声明如下

#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
  • 成功返回发送的字节数,失败返回-1
  • sockfd:表示与数据传输对象的连接的套接字文件描述符
  • buf:保存待传输数据的缓冲地址值
  • nbytes:待传输字节数
  • flags:传输数据时指定的可选项信息
#include <sys/socket.h>
ssize_t recv(int sockfd, const void *buf, size_t nbytes, int flags);

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

send函数和recv函数的最后一个参数是收发数据时的可选项,该可选项可利用位(bit)或运算符同时传递多个信息,如下表所示
在这里插入图片描述
其中我们将在后面介绍部分可选项的使用方法

(2)MSG_OOB:发送紧急消息

MSG_OOB可选项就用于创建特殊发送方法和通道以发送紧急消息,下面我们将通过收发数据使用MSG_OOB。
发送端:发送紧急消息4和890

	write(sock, "123", strlen("123"));
    send(sock, "4", strlen("4"), MSG_OOB);
    write(sock, "567", strlen("567"));
    send(sock, "890", strlen("890"), MSG_OOB);

完整代码如下

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
#define BUF_SIZE 30
void error_handling(char *message);
 
int main(int argc, char *argv[])
{
    int sock;
    struct sockaddr_in recv_adr;
    if (argc != 3) {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
 
    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    recv_adr.sin_port = htons(atoi(argv[2]));
 
    if (connect(sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1)
        error_handling("connect() error!");
 
    write(sock, "123", strlen("123"));
    send(sock, "4", strlen("4"), MSG_OOB);
    write(sock, "567", strlen("567"));
    send(sock, "890", strlen("890"), MSG_OOB);
    close(sock);
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

接收端:收到MSG_OOB紧急信号时,该信号将产生SIGURG信号,并调用注册的信号处理函数,且在信号处理函数内部调用了紧急消息的recv函数。
部分代码:

fcntl(recv_sock, F_SETOWN, getpid());
state = sigaction(SIGURG, &act, 0);

void urg_handler(int signo)
{
    int str_len;
    char buf[BUF_SIZE];
    str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_OOB);
    buf[str_len] = 0;
    printf("Urgent message: %s \n", buf);
}

其中fcntl函数用于控制文件描述符,fcntl(recv_sock, F_SETOWN, getpid());指定当前进程为处理SIGURG信号的主体,防止多个进程进行响应。
完整代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
 
#define BUF_SIZE 30
void error_handling(char *message);
void urg_handler(int signo);
 
int acpt_sock;
int recv_sock;
 
int main(int argc, char *argv[])
{
    struct sockaddr_in recv_adr, serv_adr;
    int str_len, state;
    socklen_t serv_adr_sz;
    struct sigaction act;
    char buf[BUF_SIZE];
    if (argc != 2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
 
    act.sa_handler = urg_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
 
    acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    recv_adr.sin_port = htons(atoi(argv[1]));
 
    if (bind(acpt_sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1)
        error_handling("bind() error");
    listen(acpt_sock, 5);
 
    serv_adr_sz = sizeof(serv_adr);
    recv_sock = accept(acpt_sock, (struct sockaddr *)&serv_adr, &serv_adr_sz);
 
    fcntl(recv_sock, F_SETOWN, getpid());
    state = sigaction(SIGURG, &act, 0);
 
    while ((str_len = recv(recv_sock, buf, sizeof(buf), 0)) != 0)
    {
        if (str_len == -1)
            continue;
        buf[str_len] = 0;
        puts(buf);
    }
    close(recv_sock);
    close(acpt_sock);
    return 0;
}
 
void urg_handler(int signo)
{
    int str_len;
    char buf[BUF_SIZE];
    str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_OOB);
    buf[str_len] = 0;
    printf("Urgent message: %s \n", buf);
}
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

在这里插入图片描述
实际结果却让我们十分吃惊,MSG_OOB可选项传递数据时只返回一个字节且传输数据时不会加快传输速度。MSG_OOB得真正意义是让TCP在紧急模式进行传输。
MSG_OOB真正的意义在于督促数据接收对象尽快处理数据。如下图将缓冲最左端的位置视作偏移量为0,字符0保存于偏移量为2的位置。另外,字符0右侧偏移量为3的位置存有紧急指针。紧急指针指向紧急消息的下一个位置(偏移量加1),同时向对方主机传递消息:紧急指针指向的偏移量为3之前的部分就是紧急消息。也就是说,实际只用一个字节表示紧急消息。
在这里插入图片描述

(3)MSG_PEEK & MSG_DONTWAIT

MSG_PEEK选项和MSG_DONTWAIT验证输入缓冲中是否存在接收的数据。设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除。因此,MSG_PEEK选项通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读数据存在与否的函数。下面验证MSG_PEEK & MSG_DONTWAIT的作用。
peek_send.c用于发送字符串123

//peek_send.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
void error_handling(char *message);
 
int main(int argc, char *argv[])
{
    int sock;
    struct sockaddr_in send_adr;
    if (argc != 3) {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
 
    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&send_adr, 0, sizeof(send_adr));
    send_adr.sin_family = AF_INET;
    send_adr.sin_addr.s_addr = inet_addr(argv[1]);
    send_adr.sin_port = htons(atoi(argv[2]));
 
    if (connect(sock, (struct sockaddr *)&send_adr, sizeof(send_adr)) == -1)
        error_handling("connect() error!");
 
    write(sock, "123", strlen("123"));
    close(sock);
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

peek_recv.c调用两次recv函数,第一次recv(recv_sock, buf, sizeof(buf) - 1, MSG_PEEK | MSG_DONTWAIT),第二次recv(recv_sock, buf, sizeof(buf) - 1, 0),从下方的输出结果可以看出,仅发送一次的数据可以被读取两次,说明MSG_PEEK )读取了输入缓冲的数据也不会删除。

//peek_recv.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
#define BUF_SIZE 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);
}

在这里插入图片描述

2.readv & writev函数

readv和writev函数是对数据进行整合传输及发送的函数。也就是说,通过writev函数可以将分散保存在多个缓冲的数据一并发送,通过readv函数可以由多个缓冲分别接收

(1)readv函数

#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
  • 成功时返回发送的字节数,失败时返回-1
  • filedes:表示数据传输对象的套接字文件描述符,但该函数并不只限于套接字,因此,可以像read函数一样向其传递文件或标准输出描述符
  • iov:iovec结构体数组的地址值,结构体iovec中包含待发送数据的位置和大小信息
  • iovcnt:向第二个参数传递的数组长度
struct iovec
{
    void      *iov_base;    //缓冲地址
    size_t    iov_len;      //缓冲大小
};

下面举例说明writev函数的使用方法
在这里插入图片描述
writev的第一个参数1是文件描述符,因此向控制台输出数据,ptr是存有待发送数据信息的iovec数组指针。第三个参数为2,因此,从ptr指向的地址开始,共浏览两个iovec结构体变量,发送这些指针指向的缓冲数据。接下来仔细观察图中iovec结构体数组,ptr[0](数组第一个元素)的iov_base指向以A开头的字符串,同时iov_len为3,故发送ABC,而ptr[1](数组的第二个元素)的iov_base指向数字1,同时iov_len为4,故发送1234。

#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);//1表示向控制台输出数据
    puts("");
    printf("Write bytes: %d \n", str_len);
    return 0;
}

在这里插入图片描述

(2)writev函数

readv函数和writev函数参数相同,功能相反

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

使用如下,从标准输入读取固定的字节数

#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;			//接收数据的大小已指定为5
    vec[1].iov_base = buf2;
    vec[1].iov_len = BUF_SIZE;	//剩余数据将保存到vec[1]中注册的缓冲
 
    str_len = readv(0, vec, 2);	//0表示从标准输入接收数据
    printf("Read bytes: %d \n", str_len);
    printf("First message: %s \n", buf1);
    printf("Second message: %s \n", buf2);
    return 0;
}

在这里插入图片描述

(3)总结

  • 需要传输的数据分别位于不同缓冲(数组)时,可以通过一次writev函数
  • 需要将输入缓冲中的数据读入不同位置时,可以利用一次readv函数
  • 关闭了Nagle算法,若使用writev函数将所有数据一次性写入输出缓冲,效率更高
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值