拆包粘包处理
情况1.传来的数据刚好是一个整包
此时的数据刚好能够传递给上层,于是直接给packet,而offset(即还差多少)设置为0
情况2.拆包
情况3.粘包
这样处理过后又回到了拆包的情况
代码复现
准备工作
头文件的编写,调试文件的编写
head.h
包含必要的系统头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
color.h
调试信息带颜色便于分辨,看起来也赏心悦目一点
#ifndef _COLOR_H
#define _COLOR_H
#define NONE "\e[0m" //清除颜色,即之后的打印为正常输出,之前的不受影响
#define BLACK "\e[0;30m" //深黑
#define L_BLACK "\e[1;30m" //亮黑,偏灰褐
#define RED "\e[0;31m" //深红,暗红
#define L_RED "\e[1;31m" //鲜红
#define GREEN "\e[0;32m" //深绿,暗绿
#define L_GREEN "\e[1;32m"//鲜绿
#define BROWN "\e[0;33m" //深黄,暗黄
#define YELLOW "\e[1;33m" //鲜黄
#define BLUE "\e[0;34m" //深蓝,暗蓝
#define L_BLUE "\e[1;34m" //亮蓝,偏白灰
#define PINK "\e[0;35m" //深粉,暗粉,偏暗紫
#define L_PINK "\e[1;35m" //亮粉,偏白灰
#define CYAN "\e[0;36m" //暗青色
#define L_CYAN "\e[1;36m" //鲜亮青色
#define GRAY "\e[0;37m" //灰色
#define WHITE "\e[1;37m" //白色,字体粗一点,比正常大,比bold小
#define BOLD "\e[1m" //白色,粗体
#define UNDERLINE "\e[4m" //下划线,白色,正常大小
#define BLINK "\e[5m" //闪烁,白色,正常大小
#define REVERSE "\e[7m" //反转,即字体背景为白色,字体为黑色
#define HIDE "\e[8m" //隐藏
#define CLEAR "\e[2J" //清除
#define CLRLINE "\r\e[K" //清除行
#endif
debug.h
调试的时候加入-D DBG选项即可显示调试信息
#ifdef DBG
#define DEBUG(fmt,args...) printf(fmt,##args)
#else
#define DEBUG(fmt,args...)
#endif
datatype.h
用于定义接收文件的数据类型,结构体
struct filePacket{
char name[50];//文件名
uint64_t size;//文件大小
char buff[4096];//文件块,故意设置成超过1460字节,这样就会被拆包
};//记得结构体后面一定要加 ' ; ' 不然编译会报错说哪里哪里缺少一个 ' ; '
函数编写
m_socket.c
套接字的创建和客户端的连接
#include "head.h"
#include "datatype.h"
#include "m_socket.h"
int socket_create(int port){
int sockfd;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0){
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr("0.0.0.0");
if(bind(sockfd,(struct sockaddr*)&addr,sizeof(addr))<0){
return -1;
}
if(listen(sockfd,8)<0)return -1;
return sockfd;
}
int socket_connect(const char* ip,int port){
int sockfd;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0){
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr(ip);
if(connect(sockfd,(struct sockaddr*)&addr,sizeof(addr))<0){
return -1;
}
return sockfd;
}
filetransfer.c
文件的接收和发送函数
#include "head.h"
#include "datatype.h"
#include "color.h"
#include "debug.h"
int send_file(int sockfd,const char * name){
DEBUG(BLUE"<debug>: "NONE"file name:%s\n",name);
FILE* fp;
if((fp=fopen(name,"rb"))==NULL){
DEBUG(RED"<error>: "NONE"fopen failed\n");
return -1;
}
struct filePacket packet;
size_t spacket = sizeof(packet);
size_t sbuff = sizeof(packet.buff);
bzero(&packet,spacket);
//start get the file len
fseek(fp,0L,SEEK_END);
packet.size = ftell(fp);
fseek(fp,0L,SEEK_SET);
//end get the file len
//start 获取文件名
char filename[50];
char * p ;
p = strrchr(name,'/');
if(p==NULL){
strcpy(filename,name);
printf("file name :%s\n",filename);
}else{
strcpy(filename,p+1);
}
//end 获取文件名
strcpy(packet.name,filename);
DEBUG(YELLOW"<debug>: "NONE"file name = %s,file size = %ld\n",packet.name,packet.size);
while(!feof(fp)){
size_t rsize = fread(packet.buff,1,sbuff,fp);
if(ferror(fp)){
DEBUG(RED"<error>: "NONE"read occurs error\n");
return -1;
}
ssize_t ssize = send(sockfd,(void*)&packet,spacket,0);
memset(packet.buff,0,sbuff);
DEBUG(YELLOW"<debug>: "NONE"ssize = %ld,rsize=%ld\n",ssize,rsize);
}
return 0;
}
int recv_file(int sockfd){
struct filePacket packet_pre,packet_temp,packet;
size_t spacket = sizeof(struct filePacket);
bzero(&packet_pre,spacket);
bzero(&packet_temp,spacket);
bzero(&packet,spacket);
int offset=0;
int count=0;
uint64_t file_size,total_size=0;
size_t buff_size;
size_t wsize;
FILE *fp;
while(1){
if(offset){
//something left in the packet_temp
memcpy((void*)(&packet),&packet_pre,offset);
}
memset(packet_pre.buff,0,sizeof(packet_pre.buff));
memset(packet_temp.buff,0,sizeof(packet_temp.buff));
//receive a full packet
while(1){
ssize_t rsize = recv(sockfd,(void*)&packet_temp,spacket,0);
printf("rsize=%ld\n",rsize);
if(rsize<=0)break;
if((offset+rsize)==spacket){
DEBUG(BLUE"<debug>: "NONE"receive full packet\n");
memcpy((char*)&packet+offset,&packet_temp,rsize);
offset=0;
break;
}else if((offset+rsize)<spacket){
DEBUG(YELLOW"<debug>: "NONE"receive less packet\n");
memcpy((char*)&packet+offset,&packet_temp,rsize);
offset+=rsize;
}else if((offset+rsize)>spacket){
DEBUG(L_PINK"<debug>: "NONE"receive more packet\n");
int need = spacket-offset;
memcpy((char*)(&packet+offset),(&packet_temp),need);
memcpy((char*)(&packet_pre),&packet_temp+need,rsize-need);
offset = rsize - need;
break;
}
}
//if 1st ,extract filename ,size
if(count==0){
char path [512]={0};
sprintf(path,"%s/%s","./data",packet.name);
DEBUG(YELLOW"<debug>: "NONE"packet.name = %s,packet.size = %ld\n",packet.name,packet.size);
file_size = packet.size;
if((fp=fopen(path,"wb"))==NULL){
DEBUG(RED"<error>: "NONE"fopen error occurs\n");
return -1;
}
}
count++;
buff_size = sizeof(packet.buff);
if(file_size-total_size>buff_size){
wsize = fwrite(packet.buff,1,buff_size,fp);
}else{
wsize = fwrite(packet.buff,1,file_size-total_size,fp);
}
memset(packet.buff,0,buff_size);
memset(packet_temp.buff,0,sizeof(packet_temp.buff));
total_size += wsize;
DEBUG(L_PINK"<debug>: "NONE"total_size = %ld,wsize = %ld,file_size=%ld\n",total_size,wsize,file_size);
if(total_size>=file_size){
DEBUG(YELLOW"<debug>: "NONE"file transfer complete\n");
break;
}
}
fclose(fp);
return 0;
}
客户端和服务端的编写
这个很简单,服务端加一个fork,然后recv_file,客户端直接send_file就行了
服务端
#include "head.h"
#include "m_socket.h"
#include "filetransfer.h"
void check(int argc, int correctValue,char * proname);
int main(int argc,char **argv){
check(argc,2,argv[0]);
int server_listen,sockfd;
if((server_listen=socket_create(atoi(argv[1])))<0){
perror("socket_create");
exit(1);
}
printf("socket listening on port : %d\n",server_listen);
while(1){
if((sockfd=accept(server_listen,NULL,NULL))<0){
perror("accept");
exit(1);
}
pid_t pid;
if((pid=fork())<0){
perror("fork");
exit(1);
}
if(pid){
close(sockfd);
continue;
}
close(server_listen);
int ret = recv_file(sockfd);
if(ret<0){
perror("recv_file");
}
break;
}
return 0;
}
void check(int argc, int correctValue,char * proname){
if(argc!=correctValue){
fprintf(stderr,"USAGE:%s is not correct!\n",proname);
exit(1);
}
}
客户端
#include "head.h"
#include "m_socket.h"
#include "filetransfer.h"
int main(int argc , char ** argv){
int sockfd;
if((sockfd=socket_connect(argv[1],atoi(argv[2])))<0){
perror("connect");
exit(1);
}
send_file(sockfd,argv[3]);
return 0;
}
编写脚本直接编译
gcc server.c m_socket.c filepackage.c -o server -D DBG
gcc client.c m_socket.c filepackage.c -o client -D DBG