TCP、UDP数据读写

TCP数据读写

对文件的读写操作readwrite同样适用于socket编程。但socket编程接口提供了几个专门用于socket数据读写的系统调用:

#include <sys/socket.h>
#include <sys/types.h>
ssize recv(int sockfd, void *buf, size_t len, int flag);
ssize send(int sockfd, const void *buf, size_t len, int flag);

recv读取sockfd上的数据。flags参数通常设置为0。recv成功时返回读取到的数据长度 ,他可能小于我们期望的长度len,因此我们可能要多次调用recv,才能读取到完整的数据recv可能返回0,这意味着通信对方已经关闭了。recv出错返回-1
sendsockfd上写入数据。send成功时返回实际写入的数据长度,失败返回-1

flags参数为数据首发提供了额外的控制
在这里插入图片描述

我们使用MSG_OOB选项提供了发送和接受带外数据的方法

发送带外数据:sendtest.c

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <stdlib.h>

int main(int argc, char* argv[]) {
    if (argc <= 2) {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in server_address;
    bzero(&server_address, sizeof(struct sockaddr_in));
    server_address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &server_address.sin_addr);
    server_address.sin_port = htons(port);

    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    assert( sockfd >= 0);
    if (connect(sockfd, (struct sockaddr*)&server_address, sizeof(struct sockaddr_in)) < 0) {
        printf("connection failed\n");
    } else {
        const char* oob_data = "abc";
        const char* normal_data = "123";
        send(sockfd, normal_data, strlen(normal_data), 0);
        send(sockfd, oob_data, strlen(oob_data), MSG_OOB);
        send(sockfd, normal_data, strlen(normal_data), 0);
    }
    
    close(sockfd);
    return 0;
}

接受带外数据:sendtest.c

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>

#define BUF_SIZE    1024
#define BACKLOG     5

int main(int argc, char* argv[]) {
    if (argc <= 2) {
        printf("usage: %s ip_address port_number", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in address;
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(socket >= 0);

    int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(sock, BACKLOG);
    assert(ret != -1);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        char buffer[BUF_SIZE];

        memset(buffer, '\0', BUF_SIZE);
        ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
        printf("got %d bytes of normal data '%s'\n", ret, buffer);

        memset(buffer, '\0', BUF_SIZE);
        ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
        printf("got %d bytes of oob data '%s'\n", ret, buffer);

        memset(buffer, '\0', BUF_SIZE);
        ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
        printf("got %d bytes of normal data '%s'\n", ret, buffer);

        close(connfd);
    }

    close(sock);
    return 0;
}

服务器运行recvtest.c,客户端运行sendtest.c
在这里插入图片描述
这个示例中数据发送的顺序是:“正常数据”——“带外数据”——“正常数据”
接受数据的顺序也是:“正常数据”——“带外数据”——“正常数据”
由此可见客户端发送给服务器的3字节数据abc中,仅有最后一个字符c被服务器当做真正的带外数据接受。并且,服务器对正常数据的接受将被带外数据截断,即前一部分正常数据123ab和后续的正常数据123是不能被一个recv调用全部读出的。

如果我们发送数据的顺序不变,而接受数据变成了:“带外数据”——“正常数据”——“正常数据”——“带外数据”
recvtest.c部分变成了如下写法:

        memset(buffer, '\0', BUF_SIZE);
        ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
        printf("got %d bytes of oob data '%s'\n", ret, buffer);

        memset(buffer, '\0', BUF_SIZE);
        ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
        printf("got %d bytes of normal data '%s'\n", ret, buffer);

        memset(buffer, '\0', BUF_SIZE);
        ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
        printf("got %d bytes of normal data '%s'\n", ret, buffer);

        memset(buffer, '\0', BUF_SIZE);
        ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
        printf("got %d bytes of oob data '%s'\n", ret, buffer);

运行之后发现
在这里插入图片描述
正常数据被截断了无法被一次性接受,带外数据丢失了无法被接收到

UDP数据读写

socket编程接口中用于UDP数据报读写的系统调用是:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

因为UDP通信没有连接概念,所以我们每次读取数据都需要获取发送端的socket地址,即参数src_addr所指内容,addrlen则指定地址长度。
recvfromsendto系统调用也可以用于面向连接(STREAM)的socket的数据读写,只需要吧最后两个参数都设置为NULL以忽略发送端/接收端的socket地址(因为我么已经和对方建立了连接,所以已经知道其socket地址)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值