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运行。

相关推荐
发光小北4 小时前
EtherNet/IP 转 ModbusTCP 网关现场应用中会遇到什么问题?
服务器·网络·tcp/ip
开源必胜6 小时前
Socket、异步非阻塞 I/O 与 NGINX、Netty 模型
网络·tcp/ip·nginx
沫璃染墨6 小时前
从零入门计算机网络系列(十一):HTTP协议进阶——从重定向原理到GET/POST与Fiddler抓包
linux·服务器·开发语言·网络·c++·tcp/ip·html
箓维8 小时前
TCP Socket的进阶实现
网络·笔记·tcp/ip
星恒随风9 小时前
Linux网络编程:应用层协议究竟是什么?从网络计算器到TCP Socket抽象基类完整实现
linux·网络·笔记·tcp/ip
Escalating_xu13 小时前
【Linux 网络编程】ARP 的具体过程与 ARP 欺骗:一条 IP → MAC 映射,怎样改变数据的去向?
linux·网络·tcp/ip
Escalating_xu14 小时前
【Linux 网络编程】IP 分片与重组:3000 字节的数据报,怎样拆开又拼回来?
linux·网络·tcp/ip
OpenLoong 开源社区14 小时前
打破 NAT 限制,零侵入跨网络通信:基于 WebRTC 的透明 TCP 隧道跨网络通信方案
网络协议·tcp/ip·开源·webrtc·openloong开源社区
Lhan.zzZ15 小时前
用 Qt 构建线程安全的通讯静态库:从线程亲和性到完整工程
c++·vscode·qt·tcp/ip·udp
LCG米1 天前
STM32驱动W5500以太网模块实测:TCP吞吐瓶颈定位、TX指针回绕陷阱与断线重连保活落地
stm32·单片机·嵌入式硬件·tcp/ip