网络编程——多种I/O函数

参考

  1. 《TCP/IP网络编程》 尹圣雨

多种I/O函数

send和recv函数

Linux中的send和recv

(1)send

#include <sys/socket.h>

ssize_t send(int sockfd, const void* buf, size_t nbytes, int flags);

成功时返回发送的字节数,失败时返回-1

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

(2)recv

#include <sys/socket.h>

ssize_t recv(int sockfd, void* buf, size_t nbytes, int flags);

成功时返回接收的字符数(收到EOF时返回0),失败时返回-1

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

send和recv函数最后一个参数是可选项,可利用位或(|)运算同时传递多个信息。需要注意的是,不同操作系统对可选项的支持也不同

在这里插入图片描述

MSG_OOB(发送紧急消息)

MSG_OOB可选项用于发送“带外数据”紧急消息

(1)发送紧急消息

#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);
}

(2)接收紧急消息

#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);          // 收到MSG_OOB紧急消息时,操作系统将产生SIGURG信号

    while ((str_len = recv(recv_sock, buf, sizeof(buf) - 1, 0)) != 0)
    {
        if (str_len == -1)
        {
            continue;
        }
        buf[BUF_SIZE] = 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); // 调用了接收紧急消息的recv函数
    buf[str_len] = 0;
    printf("Urgent message: %s \n", buf);
}
void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

其中,fcntl函数用于控制文件描述符,调用处的语句含义为:文件描述符recv_sock指向的套接字引发的SIGURG信号处理进程变为将getpid函数返回值用作ID的进程。因为处理SIGURG信号时必须指定处理信号的进程

需要注意的是,通过MSG_OOB可选项传递数据时不会加快数据传输速度,而且通过信号处理函数urg_handler读取数据时也只能度1个字节。剩余数据只能通过未设置MSG_OOB可选项的普通输入函数读取。因为TCP不存在真正意义上的“带外数据”,真正意义上的带外数据需要通过单独的通信路径高速传输数据

紧急模式工作原理

MSG_OOB的真正意义在于督促数据接收对象尽快处理数据,而且TCP“保持传输顺序”的传输特性依然成立

TCP头中含有如下两种信息:

  1. URG=1:载有紧急消息的数据包
  2. URG指针:紧急指针位于偏移量为xx的位置

指定MSG_OOB选项的数据包本身就是紧急数据包,并通过紧急指针表示紧急消息所在位置(紧急指针指向位置的之前部分就是紧急消息)。除紧急指针的前面1个字节外,数据接收方将通过调用常用输入函数读取剩余部分。即紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息

检查输入缓冲

设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除。因此,MSG_PEEK通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读数据存在与否的函数

(1)发送

#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);
}

(2)接收

#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);
}

仅发送1次的数据被读取了2次,因为第一次调用recv函数时设置了MSG_PEEK可选项

Windows中的send和recv

Windows中并不存在Linux那样的信号处理机制,所以无法使用MSG_OOB可选项的设置,但可以使用select函数解决这一问题

“异常”是不同寻常的程序执行流,因此,收到Out-of-band数据也属于异常。即利用select函数这一特性可以在Windows平台接收Out-of-band数据

(1)发送

#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 hSocket;
    SOCKADDR_IN sendAdr;
    if (argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

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

    hSocket = socket(PF_INET, SOCK_STREAM, 0);
    memset(&sendAdr, 0, sizeof(sendAdr));
    sendAdr.sin_family = AF_INET;
    sendAdr.sin_addr.s_addr = inet_addr(argv[1]);
    sendAdr.sin_port = htons(atoi(argv[2]));

    if (connect(hSocket, (SOCKADDR*)&sendAdr, sizeof(sendAdr)) == SOCKET_ERROR)
    {
        ErrorHandling("connect() error!");
    }

    send(hSocket, "123", 3, 0);
    send(hSocket, "4", 1, MSG_OOB);
    send(hSocket, "567", 3, 0);
    send(hSocket, "890", 3, MSG_OOB);

    closesocket(hSocket);
    WSACleanup();
    return 0;
}

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

(2)接收

#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(AF_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)
                {
                    break;
                    closesocket(hRecvSock);
                }
                else
                {
                    buf[strLen] = 0;
                    puts(buf);
                }
            }
        }
    }

    closesocket(hAcptSock);
    WSACleanup();
    return 0;
}

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

readv和writev函数

通过writev函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以由多个缓冲分别接收。适当使用这2个函数可以减少I/O函数的调用次数

另外,Windows中并没有函数与writev和readv函数直接对应,但可以通过“重叠I/O”(Overlapped I/O)得到相同效果

writev
#include <sys/uio.h>

ssize_t writev(int filedes, const struct iovec* iov, int iovcnt);

成功时返回发送的字节数,失败时返回-1
(1)filedes
表示数据传输对象的套接字文件描述符。但该函数并不只限于套接字,因此,可以像read函数一样向其传递文件或标准输出描述符
(2)iov
iovec结构体数组的地址值,结构体iovec中包含待发送数据的位置和大小信息
(3)iovcnt
向第二个参数传递的数组长度

其中iovec结构体声明如下:

struct iovec
{
	void* iov_base;   // 缓冲地址
	size_t iov_len;    // 缓冲大小
};
示例
#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;
}
readv
#include <sys/uio.h>

ssize_t readv(int filedes, const struct iovec* iov, int iovcnt);

成功时返回接收的字节数,失败时返回-1

(1)filedes
传递接收数据的文件(或套接字)描述符
(2)iov
包含数据保存位置和大小信息的iovec结构体数组的地址值
(3)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;          // 指定只能在buf1存5个字节
    vec[1].iov_base = buf2;
    vec[1].iov_len = BUF_SIZE;
    
    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;
}
使用readv和writev函数的时机

首先,减少函数调用次数也能相应提高性能。但其更大的意义在于减少数据包个数,特别是当关闭了Nagle算法时

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值