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

相关推荐
摇滚侠18 小时前
图解三次握手,四次挥手,建立 TCP 连接的过程,上千次 TCP 连接测试,让我看到教科书没写的细节
网络·网络协议·tcp/ip
北京国科云计算20 小时前
什么是IP SSL证书?IP SSL证书和域名SSL证书有什么区别?
tcp/ip·https·ssl
程序猿编码20 小时前
手动清理 TCP TIME-WAIT 套接字:Linux 内核模块的实现与原理
linux·网络·tcp/ip·linux内核·套接字
小李独爱秋20 小时前
计算机网络经典问题透视——搜索引擎的“两大门派”与“武林新秀”
服务器·网络·网络协议·tcp/ip·计算机网络·搜索引擎
_F_y20 小时前
传输层协议:TCP
网络·网络协议·tcp/ip
鲨莎分不晴20 小时前
告别TCP?HTTP/3与QUIC协议如何重塑下一代Web体验
网络协议·tcp/ip·http
找不到、了20 小时前
HTTP 长连接 vs 短连接:从 TCP 优化到 QUIC 时代的演进
网络协议·tcp/ip·http
福尔摩斯张20 小时前
嵌入式硬件篇:常见单片机型号深度解析与技术选型指南
网络·数据库·stm32·单片机·网络协议·tcp/ip·mongodb
Dovis(誓平步青云)21 小时前
《 传输层协议精解:TCP的三次握手与UDP的无连接特性全解析》
网络协议·tcp/ip·udp
2301_773730311 天前
网络编程—TCP传输控制协议
服务器·网络·tcp/ip