TCP显式编码报文长度发送与接收

报文格式最重要的是如何确定报文的边界。常见的报文格式有两种方法,一种是发送端把要发送的报文长度预先通过报文告知给接收端;另一种是通过一些特殊的字符来进行边界的划分。

这篇文章中讲的是发送报文长度的方法。报文类型如下:

第一部分是4个字节大小的消息长度,其目的是将真正发送的字节流的大小显式通过报文告知接收端,第二部分是 4 个字节大小的消息类型,第2部分才是真正需要发送的数据。

发送端

readnMessageByLength.c里边的代码如下:

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<errno.h>
#include<syslog.h>
#include<signal.h>
size_t readn(int fd, void *buffer, size_t length);
size_t read_message(int fd, char *buffer, size_t length);

static int count;

static void sig_int(int signo) {
    printf("\nreceived %d datagrams\n", count);
    exit(0);
}


int main(int argc, char **argv) {
    if (argc != 2) {
        printf("usage: select01 <IPaddress> or <Port>\n");
    }
    int listenfd;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

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

    int on = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    int rt1 = bind(listenfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
    if (rt1 < 0) {
        printf("bind failed");
        exit(errno);
    }

    int rt2 = listen(listenfd, 5);
    if (rt2 < 0) {
        printf("listen failed");
        exit(errno);
    }

    signal(SIGPIPE, SIG_IGN);

    int connfd;
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    if ((connfd = accept(listenfd, (struct sockaddr *) &client_addr, &client_len)) < 0) {
        printf("bind failed");
        exit(errno);
    }

    char buf[128];
    count = 0;

    while (1) {
        int n = read_message(connfd, buf, sizeof(buf));
        if (n < 0) {
            printf("error read message\n");
            exit(errno);
        } else if (n == 0) {
            printf("client closed \n");
            exit(0);
        }
        buf[n] = 0;
        printf("received %d bytes: %s\n", n, buf);
        count++;
    }

    exit(0);

}


size_t readn(int fd, void *buffer, size_t length) {
    size_t count;
    ssize_t nread;
    char *ptr;

    ptr = buffer;
    count = length;
    while (count > 0) {
        nread = read(fd, ptr, count);

        if (nread < 0) {
            if (errno == EINTR)
                continue;
            else
                return (-1);
        } else if (nread == 0)
            break;                /* EOF */

        count -= nread;
        ptr += nread;
    }
    return (length - count);        /* return >= 0 */
}



size_t read_message(int fd, char *buffer, size_t length) {
    u_int32_t msg_length;
    u_int32_t msg_type;
    int rc;

    rc = readn(fd, (char *) &msg_length, sizeof(u_int32_t));
    if (rc != sizeof(u_int32_t))
        return rc < 0 ? -1 : 0;
    msg_length = ntohl(msg_length);

    rc = readn(fd, (char *) &msg_type, sizeof(msg_type));
    if (rc != sizeof(u_int32_t))
        return rc < 0 ? -1 : 0;

    if (msg_length > length) {
        return -1;
    }

    rc = readn(fd, buffer, msg_length);
    if (rc != msg_length)
        return rc < 0 ? -1 : 0;
    return rc;
}

gcc readnMessageByLength.c -o readnMessageByLength编译,./readnMessageByLength 8080运行。

接收端

SendMessageByLength.c里边的代码如下:

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<errno.h>
#include<syslog.h>
#include<signal.h>
int main(int argc, char **argv) {
    if (argc != 3) {
        printf("usage: tcpclient <IPaddress>\n");
        exit(errno);
    }

    int socket_fd;
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[2]));;
    inet_pton(AF_INET, argv[1], &server_addr.sin_addr);

    socklen_t server_len = sizeof(server_addr);
    int connect_rt = connect(socket_fd, (struct sockaddr *) &server_addr, server_len);
    if (connect_rt < 0) {
        fprintf(stderr, "error in connect: %s (%d)\n", strerror(errno), errno);
        exit(errno);
    }

    struct {
        u_int32_t message_length;
        u_int32_t message_type;
        char buf[128];
    } message;

    int n;

    while (fgets(message.buf, sizeof(message.buf), stdin) != NULL) {
        n = strlen(message.buf);
        message.message_length = htonl(n);
        message.message_type = 1;
        if (send(socket_fd, (char *) &message, sizeof(message.message_length) + sizeof(message.message_type) + n, 0) < 0){
                fprintf(stderr, "error in send: %s (%d)\n", strerror(errno), errno);
                exit(errno);
            }
    }
    exit(0);
}

gcc SendMessageByLength.c -o SendMessageByLength编译,./SendMessageByLength 127.0.0.1 8080运行。

相关推荐
{⌐■_■}1 小时前
【go】binary包,大小端理解,read,write使用,自实现TCP封包拆包案例
arm开发·tcp/ip·golang
小疆智控4 小时前
数字化工厂升级引擎:Modbus TCP转Profinet网关助力打造柔性生产系统
服务器·网络·tcp/ip
purrrew4 小时前
【Java ee初阶】IP协议
服务器·网络协议·tcp/ip
Ivy烎4 小时前
【嵌入式笔记】Modbus TCP
笔记·tcp/ip·嵌入式·modbus tcp
IP管家8 小时前
物联网设备远程管理:基于代理IP的安全固件更新通道方案
服务器·网络·物联网·网络协议·tcp/ip·安全·ip
无锡耐特森11 小时前
能源数字化转型关键引擎:Profinet转Modbus TCP网关驱动设备协同升级
网络·tcp/ip·能源
laocooon52385788616 小时前
一台入网的电脑有6要素, 机器名,mac,ip,俺码,网关,dns,分别有什么作用
网络协议·tcp/ip·macos
bjbxkj19 小时前
量子隧穿:PROFINET到Ethernet ip的无损耗协议转换方案转
网络·网络协议·tcp/ip
搬码临时工1 天前
动态域名服务ddns怎么设置?如何使用路由器动态域名解析让外网访问内网?
服务器·网络·tcp/ip·智能路由器·访问公司内网
wudinaniya1 天前
笔记本电脑打开网页很慢,一查ip地址网段不对怎么处理
网络·网络协议·tcp/ip·电脑