回射服务器_packet实现(包头存len)

本文详细介绍了如何实现一个回射服务器,特别是在包头中存储数据长度的处理方式。从服务器端和客户端的角度出发,阐述了数据传输的关键步骤和技术要点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务器端:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<sys/mman.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<errno.h>

//服务端程序

//定义包头
struct packet
{
    int len;
    char buf[1024];
};

//因为read函数每次读取的字节数长度不确定,所以我们封装一个函数
//让函数每次都读取确定大小的字节数
ssize_t readn(int fd, void *buf, size_t count)
{
    //我要接收count个字节数
    size_t      nleft = count;//还剩下的需要读取的字节数
    ssize_t     nread;//已经接收的字节数
    char *bufp = (char*)buf;

    while(nleft >0)//只要还有字节没接收
    {
        if( ( nread = read(fd, bufp, nleft) ) < 0 )
        {
            if(errno == EINTR)//被信号中断,不退出,继续接收
                continue;
            else
                return -1;
        }
        else if(nread == 0)//对等方关闭了
        {
            return count - nleft;
        }
        bufp += nread;
        nleft -= nread;
    }
    return count - nleft ;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
    //我要发送count个字节数
    size_t      nleft = count;//还剩下的需要发送的字节数
    ssize_t     nwritten;//已经发送的字节数
    char *bufp = (char*)buf;

    while(nleft >0)
    {
        if( ( nwritten = write(fd, bufp, nleft) ) < 0 )
        {
            if(errno == EINTR)//被信号中断,不退出,继续接收
                continue;
            else
                return -1;
        }
        else if(nwritten == 0)//对等方关闭了
        {
            continue;
        }
        bufp += nwritten;
        nleft -= nwritten;
    }
    return count - nleft ;
}

//子进程的处理函数
void do_server(int conn)
{
    struct packet recvbuf;
    while(1)
    {
        memset(&recvbuf, 0, sizeof(recvbuf));
        int ret = readn(conn, &recvbuf.len, 4);//如果接收到的字节数小于1024,那么就会在readn中一直循环
        if(0 <  ret)
        {
            puts("网络中接收到数据");
        }
        else if( -1 == ret)
        {
            puts("接收失败");
            return;
        }
        else
        {
            puts("client_close");
            break;
        }

        int n = ntohl(recvbuf.len);
        ret = readn(conn, recvbuf.buf, n);
        if( -1 == ret)
        {
            puts("接收失败");
            return;
        }
        else if(ret <n)
        {
            puts("client_close");
            break;
        }
        fputs(recvbuf.buf,stdout );
        writen(conn, &recvbuf, 4+n);
    }
    return ;
}

int main()
{
    //1 创建套接字
    int listenfd ; //创建监听套接字
    listenfd = socket(PF_INET, SOCK_STREAM, 0);//最后一个参数可以为0,IPPROTO_TCP
    if( listenfd<0 )
    {
        perror("listenfd socket");
        exit(1);
    }

    //2 绑定一个地址
    //将ipv4强制转换为通用地址
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET; 		//设置地址族
    servaddr.sin_port = htons(5188);	//将端口号转为网络字节序,s表示两个字节

    //地址的初始化,以下三种方式均可
    // servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//本机的任意地址
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //inet_aton("127.0.0.1", &servaddr.sin_addr);
    //设置REUSEADDR在服务器为TIME_WAIT状态时,还允许重启
    int on = 1;
    if( (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) <0 )
    {
        puts("setsockopt err");
        exit(1);
    }
    //进行绑定
    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0 )
    {
        perror("bind");
        exit(1);
    }
    if( listen(listenfd, SOMAXCONN) <0 )//SOMAXCONN队列的最大值
    {
        perror("listen()");
        exit(1);
    }

    //3 accept获取队列中的套接字
    struct sockaddr_in peeraddr;
    socklen_t peerlen = sizeof(peeraddr);
    int conn;//已连接套接字

    pid_t pid;
    while(1)
    {
        if ( (conn =  accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen) ) < 0 )
        {
            perror("accept");
            exit(1);
        }
        else
        {
            printf("conn: %d\n", conn);
            printf("ip = %s, port = %d\n", inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port) );
            puts("accepted");

            pid = fork();
            if(-1==pid )
            {
                perror("fork()");
            }
            if( 0 == pid)//子进程 关闭监听套接字
            {
                puts("子进程创建成功\n");
                close(listenfd);
                do_server(conn);
                close(conn);
                return 0;
            }
            else//父进程 关闭连接套接字
            {
                close(conn);
            }
        }
    }
    close(listenfd);
    return 0 ;
}

客户端:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<sys/mman.h>

#include <arpa/inet.h>
 #include <netinet/in.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<errno.h>

//客服端 程序

//定义包头
struct packet
{
    int len;
    char buf[1024];
};

//因为read函数每次读取的字节数长度不确定,所以我们封装一个函数
//让函数每次都读取确定大小的字节数
ssize_t readn(int fd, void *buf, size_t count)
{
    //我要接收count个字节数
    size_t      nleft = count;//还剩下的需要读取的字节数
    ssize_t     nread;//已经接收的字节数
    char *bufp = (char*)buf;

    while(nleft >0)
    {
        if( ( nread = read(fd, bufp, nleft) ) < 0 )
        {
            if(errno == EINTR)//被信号中断
                continue;
            else
                return -1;
        }
        else if(nread == 0)//对等方关闭了
        {
            return count - nleft;
        }
        bufp += nread;
        nleft -= nread;
    }
    return count - nleft ;
}

 ssize_t writen(int fd, const void *buf, size_t count)
{
    //我要发送count个字节数
    size_t      nleft = count;//还剩下的需要发送的字节数
    ssize_t     nwritten;//已经发送的字节数
    char *bufp = (char*)buf;

    while(nleft >0)
    {
        if( ( nwritten = write(fd, bufp, nleft) ) < 0 )
        {
            if(errno == EINTR)//被信号中断
                continue;
            else
                return -1;
        }
        else if(nwritten == 0)//对等方关闭了
        {
            continue;
        }
        bufp += nwritten;
        nleft -= nwritten;
    }
    return count - nleft ;
}

//客户端接收函数
//接收到的数据为packet,先读取长度,再读取数据
void recvpacket(int sock, struct packet *recvbufaddr)
{

    int ret = readn(sock, &(*recvbufaddr).len , 4);//
    if(0 <  ret)
        {
            puts("网络中接收到数据");
        }
        else if( -1 == ret)
        {
            puts("接收失败");
            return;
        }
        else
        {
            puts("client_close");
            return;
        }

        int n = ntohl((*recvbufaddr).len);
        ret = readn(sock, (*recvbufaddr).buf, n);
        if( -1 == ret)
        {
            puts("接收失败");
            return;
        }
        else if(ret <n)
        {
            puts("client_close");
            return ;
        }
        fputs( (*recvbufaddr).buf,stdout );
}

int main()
{
	//1 创建套接字
	int sock ;
	sock = socket(AF_INET, SOCK_STREAM, 0);//最后一个参数可以为0,IPPROTO_TCP
	if( sock<0 )
	{
		perror(" socket");
		exit(1);
	}

	//2 设置服务器地址
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET; 		//设置地址族
	servaddr.sin_port = htons(5188);	//将端口号转为网络字节序,s表示两个字节
    //地址的设置,以下两种方式均可
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //inet_aton("127.0.0.1", &servaddr.sin_addr);

    //3 进行连接
    if( connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr) ) < 0 )
    {
        perror("connect");
        exit(1);
    }
    else
    {
        puts("connected");
    }

    struct packet sendbuf;
    struct packet recvbuf;
    memset(&sendbuf, 0 ,sizeof(sendbuf));
    memset(&recvbuf, 0, sizeof(recvbuf));
    while( ( fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin)) != NULL )
    {
        int n = strlen(sendbuf.buf);
        sendbuf.len = htonl(n) ;

        //发送数据
        writen(sock, &sendbuf, 4+n );

        //接收数据
        recvpacket(sock, &recvbuf);
        memset(&sendbuf, 0, sizeof(sendbuf));
        memset(&recvbuf, 0, sizeof(sendbuf));
    }
    close(sock);
    return 0 ;
}

bool RTSPH265Sender::writePacket(AVPacket* pkt) { // H265 RTP 分片规则 (RFC 7798) const int RTP_MTU = 1400; uint8_t *nal_data = pkt->data; int nal_size = pkt->size; // 检查NAL单元类型(必须为AP或FU-A分片类型) if (nal_size < 2) return false; // 非法NAL单元 uint8_t nal_type = (nal_data[0] >> 1) & 0x3F; // 提取H265 NAL类型(6 bits) // 需要分片:NAL单元头占2字节(H265特性) if (nal_size > RTP_MTU) { // 跳过NAL头(2字节),从payload开始分片 uint8_t *payload_data = nal_data + 2; int payload_size = nal_size - 2; int max_payload_size = RTP_MTU - 3; // RTP头(12) + FU Indicator(1) + FU Header(1) = 14,MTU预留 // 分片数量计算 int num_fragments = (payload_size + max_payload_size - 1) / max_payload_size; int offset = 0; // 构造分片包 for (int i = 0; i < num_fragments; i++) { AVPacket fragment_pkt; av_new_packet(&fragment_pkt, RTP_MTU); // --- RTP头 --- // 此处应填充正确的序列号、时间戳(需与原始包一致) fragment_pkt.stream_index = pkt->stream_index; // --- FU Indicator --- // forbidden_zero_bit(1) | nal_type(6) => 类型设为FU分片标识49(0x31) fragment_pkt.data[0] = (nal_data[0] & 0x81) | (49 << 1); // 保留原NAL头第1位(F)和第8位(LayerId) // --- FU Header --- uint8_t fu_header = 0; if (i == 0) { fu_header |= (1 << 7); // S (Start) 标志 fu_header |= (nal_type & 0x3F); // 原始NAL类型(低6位) } else if (i == num_fragments - 1) { fu_header |= (1 << 6); // E (End) 标志 fu_header |= (nal_type & 0x3F); } else { fu_header |= (nal_type & 0x3F); } fragment_pkt.data[1] = fu_header; // --- Payload分片 --- int frag_size = (i == num_fragments - 1) ? (payload_size - offset) : max_payload_size;
03-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值