linux-c语言 制作TCP服务端发送视频流方案、TCP客户端接受流方案

注意只适合本地tcp,走局域网的再见哈

局域网单包发送61440个字节会拆包,read时多为几千个字节,而本地没有那么多限制read几万个字节是没问题的

TCP服务器源码(按帧为单位发送)

、、具体看代码中的解释

    if(connfd>=0){//成功连接TCO客户端
        pthread_mutex_lock(&m);
        unsigned char sendBuf[200*1024];//用于存放帧数据的buffer
        unsigned char *p = (unsigned char*)sendBuf;
        int size1,size2,size3,ret;
        if(cmd==JV_RECORD){//录像流
            RemotePlayHandle_t* frameReaded = (RemotePlayHandle_t*)frame;
            RecordPlay grpc_frame={0};//用于发送录像流的结构体
             //赋值
            memcpy(grpc_frame.fileName,frameReaded->fileName,sizeof(grpc_frame.fileName));
            //文件名
            grpc_frame.chnid = channel;
            //通道号
            memcpy(grpc_frame.nextfname,frameReaded->nextfname,sizeof(grpc_frame.nextfname));
            grpc_frame.secInFile = frameReaded->secInFile;
            memcpy(&grpc_frame.mediaInfo,&frameReaded->mediaInfo,sizeof(grpc_frame.mediaInfo));
            //文件信息
            grpc_frame.frametype = frameReaded->frameReaded.frameType;
            //帧的类型
            grpc_frame.buffer = frameReaded->frameReaded.data;
            //帧的数据
            grpc_frame.len = frameReaded->frameReaded.len;
            //帧数据的长度
            grpc_frame.timestap = frameReaded->frameReaded.timestap;
            //帧的时间戳
            grpc_frame.status = frameReaded->status;
            grpc_frame.speed = frameReaded->speed;
            //回放的倍数
            grpc_frame.bOnlyIFrame = frameReaded->bOnlyIFrame;
            grpc_frame.firstFraMsTime = frameReaded->firstFraMsTime;
            grpc_frame.cntFile = frameReaded->cntFile;
            grpc_frame.curFile = frameReaded->curFile;

            //拷贝数据
            Packet_Head head={0};//包头结构体
            size1 =  sizeof(RecordPlay);//存放录像流信息结构体
            size2 = grpc_frame.len;//帧数据的长度
            size3 = sizeof(Packet_Head)+size1+size2;//该帧的总字节
            head.cmd =cmd;
            head.frame_type = grpc_frame.frametype;
            head.size = size1 + size2;
            memcpy(p,&head,sizeof(Packet_Head));//拷贝包头到单帧包中
            memcpy(p+sizeof(Packet_Head), &grpc_frame, size1);//拷贝录像信息结构体到单帧包中
            memcpy(p+sizeof(Packet_Head)+size1, grpc_frame.buffer, size2);//拷贝帧数据

        }else if(cmd==JV_STREAM){//实时流
            JVFrame_t * frameReaded = (JVFrame_t *)frame;
            StramFrame_t grpc_frame={0};
             //赋值
            grpc_frame.buffer=frameReaded->buffer;
            grpc_frame.ch=channel;
            grpc_frame.frametype = type;
            grpc_frame.len=frameReaded->len;
            grpc_frame.stream_id = sub_stream;
            //子主码流标记
            grpc_frame.timestamp = frameReaded->timestamp;

            //拷贝数据
            Packet_Head head={0};
            size1 =  sizeof(StramFrame_t);
            size2 = grpc_frame.len;
            size3 = sizeof(Packet_Head)+size1+size2;
            head.cmd =cmd;
            head.frame_type = grpc_frame.frametype;
            head.size = size1 + size2;
            memcpy(p,&head,sizeof(Packet_Head));
            //*((JU32*)p) = CMD_HEAD+size1 + size2  ;
            //*((JU8*)(p+PACKET_HEAD)) = cmd;
            memcpy(p+sizeof(Packet_Head), &grpc_frame, size1);
            memcpy(p+sizeof(Packet_Head)+size1, grpc_frame.buffer, size2);
            printf("[lxr_test_grpc][func=%s][LINE=%d][size1=%d][size2=%d][size3=%d]\n",\
            __func__,__LINE__,size1,size2,size3);
        }
		//开始发帧
        if(size3>61440){//TCP一包最大可以发65535字节,当I帧数据比较大时需要分包
            int i=0;
            while(size3>61440){
                ret=write(connfd,p+i,61440);
                i+=61440;
                size3-=61440;
            }
            ret=write(connfd,p+i,size3);
        }else{
            ret=write(connfd,p,sizeof(Packet_Head)+size1+size2);
        }
        pthread_mutex_unlock(&m);
    }

TCP客户端源码(接受一帧为单位)(解决粘包和拆包问题)

、、具体看代码中的解释

while(1)
{
	ret = read(sockfd,recBuf,61440);
	Packet_Head head =  {0};
	memcpy(&head,recBuf,sizeof(Packet_Head));//先把包头读出来
	unsigned int lenTotal = head.size + PACKET_HEAD;
	if(ret>lenTotal){//沾包
		unsigned int readlen=lenTotal,total=ret;
		//写入数据
		write_stream(recBuf,lenTotal,fp,head.cmd);
		unsigned char * recBuf_offset = recBuf;
		while (readlen!=ret)//判断拆包是否完毕
		{
			//偏移到下一包的首地址
			recBuf_offset+=lenTotal;
			memcpy(&head,recBuf_offset,sizeof(Packet_Head));
			lenTotal = head.size + PACKET_HEAD;
			//lenTotal = *((unsigned int*)recBuf_offset)+PACKET_HEAD;
			//判断是否为I帧分包
			if(head.frame_type==JV_FRAME_TYPE_I){
				unsigned int iframe_read=ret-readlen;
				while (iframe_read<lenTotal)//说明粘包中不包含完整I帧需要取包
				{
					ret = read(sockfd,recBuf_offset+iframe_read,61440);
					iframe_read+=ret;
				}
				write_stream(recBuf_offset,lenTotal,fp,head.cmd);
				readlen+=lenTotal;	
			}else{//P帧包
				write_stream(recBuf_offset,lenTotal,fp,head.cmd);
				readlen+=lenTotal;
			}
		}	
	}
	else if(head.frame_type==JV_FRAME_TYPE_I){//判断是否为i帧包
		unsigned int iframe_read=ret;
		while(iframe_read<lenTotal){//I帧分包发送
			ret = read(sockfd,recBuf+iframe_read,61440);
			iframe_read+=ret;
		}
		if(lenTotal!=iframe_read){//出现粘包
			
			unsigned int not_readlen = iframe_read-lenTotal;
			unsigned int readlen=0;
			unsigned char * recBuf_offset=recBuf;
			write_stream(recBuf,lenTotal,fp,head.cmd);//先把完整I帧写入	
			while (not_readlen!=readlen)
			{
				//偏移到下一P帧的首地址
				recBuf_offset+=lenTotal;
				memcpy(&head,recBuf_offset,sizeof(Packet_Head));
				lenTotal = head.size + PACKET_HEAD;
				//lenTotal = *((unsigned int*)recBuf_offset)+4;
				write_stream(recBuf_offset,lenTotal,fp,head.cmd);
				readlen+=lenTotal;	
			}
		}else if(lenTotal==iframe_read){//单包i帧
			write_stream(recBuf,lenTotal,fp,head.cmd);
		}
	}else{//P帧包,p帧包也有可能出现分包,需要继续read,后面再优化
		write_stream(recBuf,lenTotal,fp,head.cmd);
	}
}

void write_stream(unsigned char *recBuf,unsigned int lenTotal,FILE * fp,int cmd){
		//把头部去掉
		unsigned char *head=(unsigned char *)recBuf+PACKET_HEAD;
		switch (cmd)
		{
		case JV_RECORD:{//回放流
			//给回放结构体填充
			RecordPlay * recFrm=(RecordPlay *)(head);
			if(recFrm)
			{
				//给回放流数据填充
				recFrm->buffer = (unsigned char*)recFrm + sizeof(RecordPlay);
				//check判断len的总长度
				if(lenTotal-PACKET_HEAD != recFrm->len + sizeof(RecordPlay))
				{
					printf("bad length:chn=%d,lenTotal=%d,frmLen=%d\n", recFrm->chnid, lenTotal, recFrm->len);
				}
			}
			//写入文件
			write_input_file(cmd,fp,recFrm->buffer,recFrm->len,recFrm->frametype);
			break;
		}
		case JV_STREAM:{//实时流
			StramFrame_t * recFrm=(StramFrame_t *)(head);
			if(recFrm)
			{
				recFrm->buffer = (unsigned char*)recFrm + sizeof(StramFrame_t);
				if(lenTotal-PACKET_HEAD != recFrm->len + sizeof(StramFrame_t))
				{
					printf("bad length:chn=%d,lenTotal=%d,frmLen=%d\n", recFrm->ch, lenTotal, recFrm->len);
				}

			}
			write_input_file(cmd,fp,recFrm->buffer,recFrm->len,recFrm->frametype);
			break;
		}
		default:
			break;
		}
}

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,你可以参考以下的C语言代码来编写TCP服务端来接收客户端信息: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { int listen_fd, conn_fd; struct sockaddr_in serv_addr, client_addr; socklen_t client_addr_len; char buffer[1024]; int n; // 创建一个TCP套接字 listen_fd = socket(AF_INET, SOCK_STREAM, 0); if(listen_fd < 0) { printf("socket error: %s\n", strerror(errno)); exit(1); } // 初始化服务器地址结构 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(9000); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定套接字和地址 if(bind(listen_fd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) { printf("bind error: %s\n", strerror(errno)); exit(1); } // 监听套接字 if(listen(listen_fd, 10) < 0) { printf("listen error: %s\n", strerror(errno)); exit(1); } printf("wait for client...\n"); while(1) { // 接受客户端连接请求 client_addr_len = sizeof(client_addr); conn_fd = accept(listen_fd, (struct sockaddr*) &client_addr, &client_addr_len); if(conn_fd < 0) { printf("accept error: %s\n", strerror(errno)); continue; } printf("client connected from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); while(1) { // 接收客户端发送的数据 memset(buffer, 0, sizeof(buffer)); n = read(conn_fd, buffer, sizeof(buffer)); if(n < 0) { printf("read error: %s\n", strerror(errno)); break; } else if(n == 0) { printf("client closed\n"); break; } printf("received: %s", buffer); // 发送响应给客户端 if(write(conn_fd, buffer, strlen(buffer)) < 0) { printf("write error: %s\n", strerror(errno)); break; } } // 关闭连接 close(conn_fd); } // 关闭监听套接字 close(listen_fd); return 0; } ``` 这段代码展示了如何使用C语言编写一个TCP服务端,它可以监听客户端的连接请求,接收客户端发送的数据,并向客户端发送响应。如果想要编写一个TCP客户端来连接这个服务端,可以参考以下代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { int sock_fd; struct sockaddr_in serv_addr; char buffer[1024]; int n; // 创建一个TCP套接字 sock_fd = socket(AF_INET, SOCK_STREAM, 0); if(sock_fd < 0) { printf("socket error: %s\n", strerror(errno)); exit(1); } // 初始化服务器地址结构 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(9000); serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 连接服务器 if(connect(sock_fd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) { printf("connect error: %s\n", strerror(errno)); exit(1); } while(1) { // 从标准输入读入数据 memset(buffer, 0, sizeof(buffer)); fgets(buffer, sizeof(buffer), stdin); // 发送数据到服务器 if(write(sock_fd, buffer, strlen(buffer)) < 0) { printf("write error: %s\n", strerror(errno)); break; } // 从服务器接收响应数据 memset(buffer, 0, sizeof(buffer)); n = read(sock_fd, buffer, sizeof(buffer)); if(n < 0) { printf("read error: %s\n", strerror(errno)); break; } else if(n == 0) { printf("server closed\n"); break; } printf("received: %s", buffer); } // 关闭套接字 close(sock_fd); return 0; } ``` 这段代码展示了如何使用C语言编写一个TCP客户端,它可以连接到服务器,发送数据给服务器,并接收服务器的响应。在这个例子中,客户端从标准输入读取数据并发送给服务器,服务器将收到的数据原样返回给客户端
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

I&You

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值