《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数

《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数

send & recv 函数

Linux 平台下的 send 和 recv 函数

#include <sys/socket.h>
ssize_t recv(int sockfd, const void *buf, size_t nbytes, int flags);

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

参数:

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

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

参数:

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

send函数和recv函数的最后一个参数是收发数据时的可选项,该可选项可利用位(bit)或运算符同时传递多个信息,通过下表整理可选项的种类及含义。

可选项(Option)含义sendrecv
MSG_OOB用于传输带外数据(Out-of-band data)OO
MSG_PEEK验证输入缓冲中是否存在接受的数据XO
MSG_DONTROUTE数据传输过程中不参照本地路由(Routing)表,在本地(Local)网络中寻找目的地OX
MSG_DONTWAIT调用 I/O 函数时不阻塞,用于使用非阻塞(Non-blocking)I/OOO
MSG_WAITALL防止函数返回,直到接收到全部请求的字节数XO

不同操作系统对上述可选项的支持也不同。因此,为了使用不同可选项,需对实际开发采用的操作系统有一定了解。

MSG_OOB:发送紧急消息

MSG_OOB是特定于TCP的一个选项,它用于在TCP连接中发送紧急或带外数据(out-of-band data),通常用于中断正常的数据流,它允许发送端发送一些紧急信息,这些信息会绕过正常的数据队列,直接发送给接收端,而接收端也可以优先处理这些带外数据。

下面示例将通过MSG_OOB可选项收发数据。

oob_send.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 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);
}

从上面示例可以看出,紧急消息的传输比即将介绍的接收过程要简单,只需在调用send函数时指定MSG_OOB可选项,接收紧急消息的过程要相对复杂一点

oob_recv.c:

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

上述示例中插入了未曾讲解的fcntl函数调用语句,关于此函数只讲解必要部分,后面还会再讲解此函数

fcntl(recv_sock, F_SETOWN, getpid());

fntcl函数用于控制文件描述符,但上述调用语句的含义为:将文件描述符recv_sock指向的套接字拥有者(F_SETOWN)改为把getpid函数返回值用作ID进程。

可能大家对“套接字拥有者”的概念有些生疏,操作系统实际上创建管理套接字,所以从严格意义上说,操作系统才是套接字的拥有者,只是此处所谓的“拥有者”是指套接字所有事物的主体,

上述描述可简要概括为:文件描述符recv_sock指向的套接字引发的SIGURG信号处理进程变为将getpid函数返回值用作ID的进程。

当然,上述描述中的“处理SIGURG信号”指的是“调用SIGURG信号处理函数”。但之前讲过,多进程可以共同拥有一个套接字描述符。例如,通过调用fork函数创建子进程并同时复制文件描述符。此时如果发生SIGURG信号,应该调用哪个进程的信号处理函数呢?可以肯定的是,不会调用所有进程的信号处理函数。因此,处理SIGURG信号时必须指定处理信号的进程,而getpid函数返回调用此函数的进程ID。上述调用语句指定当前进程为处理SIGURG信号的主体。该程序只创建一个进程,因此,理应由该进程处理SIGURG信号。

编译oob_recv.c并运行:

# gcc oob_recv.c -o oob_recv
# ./oob_recv 8500
123
Urgent message: 4
567
Urgent message: 0
89

编译oob_send.c并运行:

# gcc oob_send.c -o oob_send
# ./oob_send 127.0.0.1 8500

从运行结果可以看出,send 是客户端,recv 是服务端,客户端给服务端发送消息,服务端接收完消息之后显示出来。

注意:每次运行的效果,并不是一样的。

输出结果出乎意料:通过 MSG_OOB 可选项传递数据时只返回 1 个字节,而且也不快。

的确,通过 MSG_OOB 并不会加快传输速度,而通过信号处理函数 urg_handler 也只能读取一个字节。剩余数据只能通过未设置 MSG_OOB 可选项的普通输入函数读取。这是因为 TCP 不存在真正意义上的「外带数据」。实际上,MSG_OOB 中的 OOB 指的是 Out-of-band ,而「外带数据」的含义是:通过完全不同的通信路径传输的数据。

真正意义上的 Out-of-band 需要通过单独的通信路径高速传输数据,但是 TCP 不另外提供,只利用 TCP 的紧急模式(Urgent mode)进行传输。

紧急模式的工作原理

MSG_OOB 的真正意义在于督促数据接收对象尽快处理数据。这是紧急模式的全部内容,而 TCP 「保持传输顺序」的传输特性依然成立。TCP 的紧急消息无法保证及时到达,但是可以要求急救。下面是 MSG_OOB 可选项状态下的数据传输过程:

在这里插入图片描述

上图给出的是示例oob_send.c的第32行中调用如下函数后的输出缓冲状态,此处假设已传输之前的数据。

send(sock, "890", strlen("890"), MSG_OOB);

如果将缓冲最左端的位置视作偏移量为0,字符0保存于偏移量为2的位置。另外,字符0右侧偏移量为3的位置存有紧急指针。紧急指针指向紧急消息的下一个位置(偏移量加1),同时向对方主机传递消息:紧急指针指向的偏移量为3之前的部分就是紧急消息。

也就是说,实际只用一个字节表示紧急消息。这一点可以通过下图中用于传输数据的TCP数据包(段)的结构看的更清楚。

在这里插入图片描述

TCP数据包实际包含很多信息,图1-2只标注了与我们主题相关的内容,TCP头含有如下两种信息:

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

指定MSG_OOB选项的数据包本身就是紧急数据包,并通过紧急指针表示紧急消息所在位置,但无法得知紧急消息是字符串890?还是90?亦或是单个0?但这并不重要,如前所述,除紧急指针的前面一个字节外,数据接收方将通过调用常用输入函数读取剩余部分。换言之,紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息。

检查输入缓冲

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

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

发起连接请求,然后发送字符串“123”。

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

程序调用recv函数的同时传递MSG_PEEK可选项,这是为了保证即使不存在待读取数据也不会进入阻塞状态。再次调用recv函数,这次并未设置任何可选项。因此,本次读取的数据将从输入缓冲中删除。

编译peek_recv.c并运行:

# gcc peek_recv.c -o peek_recv
# ./peek_recv 8500
Buffering 3 bytes: 123
Read again: 123

编译peek_send.c并运行:

# gcc peek_send.c -o peek_send
# ./peek_send 127.0.0.1 8500

通过运行结果可以验证,仅发送一次的数据被读取两次,因为第一次调用recv函数时设置了MSG_PEEK选项,以上就是MSG_PEEK可选项的功能。

readv & writev 函数

readv和writev函数的功能可概括为:对数据进行整合传输及发送的函数。也就是说,通过writev函数可以将分散保存在多个缓冲的数据一并发送,通过readv函数可以由多个缓冲分别接收。因此,适当使用这两个函数可以减少I/O函数的调用次数。

#include <sys/uio.h>

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

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

参数:

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

上述函数的第二个参数中出现的数组iovec结构体的声明如下:

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

可以看到,结构体iovec由保存待发送数据的缓冲(char型数组)地址值和实际发送的数据长度信息构成。给出上述函数的调用示例前,先通过下图了解该函数的使用方法。

在这里插入图片描述

writev的第一个参数1是文件描述符,因此向控制台输出数据,ptr是存有待发送数据信息的iovec数组指针。第三个参数为2,因此,从ptr指向的地址开始,共浏览两个iovec结构体变量,发送这些指针指向的缓冲数据。

示例程序:

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

编译writev.c并运行:

# gcc writev.c -o writev
# ./writev
ABC1234
Write bytes: 7

下面介绍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;
    vec[1].iov_base = buf2;
    vec[1].iov_len = BUF_SIZE;
	
	// readv函数的第一个参数为0,因此从标准输入接收数据
    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;
}

编译readv.c并运行:

# gcc readv.c -o readv
# ./readv
I like TCP/IP socket programming
Read bytes: 33
First message: I lik
Second message: e TCP/IP socket programming

由运行结果可知,通过第7行声明的vec数组保存了数据。

哪种情况适合使用readv和writev函数?实际上,能使用该函数的所有情况都适用。例如:需要传输的数据分别位于不同缓冲(数组)时,需要多次调用write函数,此时可以通过一次writev函数调用来提高效率。同样,需要将输入缓冲中的数据读入不同位置时,可以不必多次调用read函数,而是利用一次readv函数就能大大提高效率。

即使从C语言角度来看,减少函数调用次数也能相应提高性能。但其更大的意义在于减少数据包个数,假设为了提高效率而在服务端明确禁止了Nagle算法,其实writev函数在不采用Nagle算法时更有价值。

在这里插入图片描述

上述示例中待发送的数据分别存在三个不同的地方,此时如果使用write函数则需要三次函数调用。但若为提高速度而关闭了Nagle算法,则极有可能通过三个数据包传递数据。反之,若使用writev函数将所有数据一次性写入输出缓冲,则很有可能仅通过一个数据包传输数据。所以writev函数和readv函数非常有用。

再考虑一种情况:将不同位置的数据按照发送顺序移动(复制)到一个大数组,并通过一次write函数调用进行传输。这种方式是否与调用writev函数的效果相同?当然!但使用writev函数更为便利。

因此,如果遇到writev函数和readv函数的适用情况,请一定要优先考虑writev和readv函数。

基于 Windows 的实现

基于 Windows 的紧急消息处理程序

Windows 并不存在 Linux 那样的信号处理机制,也就是没有 sigaction 函数之类的。在 Windows 中无法完成对 MSG_OOB 可选项的事件处理,我们通过 select 函数解决这一问题。

之前讲过,select 函数的 3 种监视对象:

  • 是否存在套接字接收数据?
  • 无需阻塞传输数据的套接字有哪些?
  • 哪些套接字发生了异常?

异常是不同寻常的程序执行流,因此,收到 Out-of-band 数据也属于异常,可以利用这一特性接收紧急消息。

oob_send_win.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

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

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    SOCKET sock;
    SOCKADDR_IN serverAddr;

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

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

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET)
        ErrorHanding("socket() error!");

    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serverAddr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        ErrorHanding("connect() error!");

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

    closesocket(sock);
    WSACleanup();

    return 0;
}

oob_recv_win.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define BUF_SIZE 30

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

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    SOCKET serverSock, clientSock;
    SOCKADDR_IN serverAddr, clientAddr;
    int clientAddrSize;
    char message[BUF_SIZE];
    int strLen;

    TIMEVAL timeout;
    fd_set read, readCopy, except, exceptCopy;
    int result;

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

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

    serverSock = socket(PF_INET, SOCK_STREAM, 0);
    if (serverSock == INVALID_SOCKET)
        ErrorHanding("socket() error!");

    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(atoi(argv[1]));

    if (bind(serverSock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        ErrorHanding("bind() error!");

    if (listen(serverSock, 5) == SOCKET_ERROR)
        ErrorHanding("listen() error!");

    clientAddrSize = sizeof(clientAddr);
    clientSock = accept(serverSock, (SOCKADDR *)&clientAddr, &clientAddrSize);
    if (clientSock == INVALID_SOCKET)
        ErrorHanding("accept() error!");

    FD_ZERO(&read);
    FD_ZERO(&except);
    FD_SET(clientSock, &read); // 注册服务器端套接字文件描述符
    FD_SET(clientSock, &except);

    while (1)
    {
        readCopy = read;
        exceptCopy = except;
        timeout.tv_sec = 5, timeout.tv_usec = 0;

        if ((result = select(0, &readCopy, 0, &exceptCopy, &timeout)) == SOCKET_ERROR)
            break;

        if (result == 0)
            continue;

        if (FD_ISSET(clientSock, &exceptCopy))
        {
            strLen = recv(clientSock, message, BUF_SIZE - 1, MSG_OOB);
            message[strLen] = '\0';
            printf("Urgent message: %s\n", message);
        }
        if (FD_ISSET(clientSock, &readCopy))
        {
            strLen = recv(clientSock, message, BUF_SIZE - 1, 0);
            if (strLen == 0)
            {
                closesocket(clientSock);
                break;
            }
            else
            {
                message[strLen] = '\0';
                puts(message);
            }
        }
    }

    closesocket(serverSock);
    WSACleanup();

    return 0;
}

编译:

gcc oob_recv_win.c -lwsock32 -o oobServ

gcc oob_send_win.c -lwsock32 -o oobClnt

运行结果(服务器端):

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 13>oobServ 9190
Urgent message: 40
12356789

基于 Windows 的输入缓冲检查程序

Windows 没有 MSG_DONTWAIT,但似乎没有影响。

peek_send_win.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

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

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    SOCKET sock;
    SOCKADDR_IN serverAddr;

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

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

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET)
        ErrorHanding("socket() error!");

    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serverAddr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        ErrorHanding("connect() error!");

    send(sock, "123", 3, 0);

    closesocket(sock);
    WSACleanup();

    return 0;
}

peek_recv_win.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define BUF_SIZE 30

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

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    SOCKET serverSock, clientSock;
    SOCKADDR_IN serverAddr, clientAddr;
    int clientAddrSize;
    char message[BUF_SIZE];
    int strLen;

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

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

    serverSock = socket(PF_INET, SOCK_STREAM, 0);
    if (serverSock == INVALID_SOCKET)
        ErrorHanding("socket() error!");

    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(atoi(argv[1]));

    if (bind(serverSock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        ErrorHanding("bind() error!");

    if (listen(serverSock, 5) == SOCKET_ERROR)
        ErrorHanding("listen() error!");

    clientAddrSize = sizeof(clientAddr);
    clientSock = accept(serverSock, (SOCKADDR *)&clientAddr, &clientAddrSize);

    while (1)
    {
        strLen = recv(clientSock, message, BUF_SIZE - 1, MSG_PEEK);
        if (strLen > 0)
            break;
    }
    message[strLen] = '\0';
    printf("Buffering %d bytes: %s\n", strLen, message);

    strLen = recv(clientSock, message, BUF_SIZE - 1, 0);
    message[strLen] = '\0';
    printf("Read again: %s\n", message);

    closesocket(clientSock);
    closesocket(serverSock);
    WSACleanup();

    return 0;
}

编译:

gcc peek_recv_win.c -lwsock32 -o peekServ

gcc peek_send_win.c -lwsock32 -o peekClnt

运行结果(服务器端):

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 13>peekServ 9190
Buffering 3 bytes: 123
Read again: 123

习题

(1)下面关于MSG_OOB可选项的说法错误的是?

a. MSG_OOB指传输Out-of-band数据,是通过其他路径高速传输数据。
b. MSG_OOB指通过其他路径高速传输数据,因此,TCP中设置该选项的数据先到达对方主机。
c. 设置MSG_OOB使数据先到达对方主机后,以普通数据的形式和顺序读取。也就是说,只是提高了传输速度,接收方无法识别这一点。
d. MSG_OOB无法脱离TCP的默认数据传输方式。即使设置了MSG_OOB,也会保持原有传输顺序。该选项只用于要求接收方紧急处理。

答:b、c。

(2)利用readv&writev函数收发数据有何优点?分别从函数调用次数和I/O缓冲的角度给出说明。

readv和writev函数的功能可概括为:对数据进行整合传输及发送的函数。也就是说,通过writev函数可以将分散保存在多个缓冲的数据一并发送,通过readv函数可以由多个缓冲分别接收。因此,适当使用这两个函数可以减少I/O函数的调用次数。

(3)通过recv函数见证输入缓冲是否存在数据时(确认后立即返回),如何设置recv函数最后一个参数中的可选项?分别说明各可选项的含义。

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

(4)可在Linux平台通过注册时间处理函数接收MSG_OOB数据。那Windows中如何接受?请说明接收方法。

MSG_OOB数据的接收,在select函数中属于异常数据,既在Windows中可以通过异常处理来接收Out-of-band数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值