首先说明下,本文的Socket传输引用了CBlockingSocket封装类
这个类比较特殊的是Send和Receive的最后一个参数是超时时间,其它与C库里的类似
首先说结构体吧,这里传输的结构体含有八个整型,如下
typedef struct exceptiontypecount{
int img_num;
int ptz_num;
int preset_num;
int video_num;
int device_num;
int total_num;
int total_channel;
int useless;
}ExceptionCount,*pExceptionCount;
关于Socket的创建和连接这里就不说了,参见点击打开链接
这里只说下收发部分的实现
服务端:
memset(counttemp,0,256);//清空缓存
memcpy(counttemp,&ExCount,sizeof(ExCount));//将结构体转为字符串
int countlen = sockCon.Send(counttemp,256,1000);
其中,char countemp[256], ExCount是实例化的结构体。
客户端:
memset(countbuff,0,256);
int len = sClient.Receive(countbuff,256,1000); // 接受故障数据
memset( &count,0,sizeof(count));
memcpy( &count, countbuff, sizeof(count) );
这里的count即为输出的结构体,countbuff同样也是个char[256]。
memset这一步非常重要,如果没有预先清空缓存,就容易出现乱码。
==================================================================================================
字符串:
要传一个500K左右的string字符串,肯定不能直接传,因为Socket传输是有字节上限的,好像是16000+字节
所以我们采用分段传输的策略
服务端:
sockCon.Send(allSize,allNum.GetLength(),100);
if(!(sockCon.Receive(resbuff,2,10)))
{
printf("数据量信息发送失败\n");
return ;
}
else
printf("数据量信息发送成功\n");
temp = AllExDataPtr;
BytesSent = 0;
while(BytesSent<AllExDataSize)//分段传输数据
{
int BytesSentThisTime=sockCon.Send(temp,((AllExDataSize-BytesSent)<256)?(AllExDataSize-BytesSent):256,1000);
BytesSent = BytesSent + BytesSentThisTime;
temp = temp + BytesSentThisTime;
}
其中,AllExDataPtr是指向需要传输的string的一个char*指针,定义为:AllExDataPtr = (char*) AllExData.c_str();
AllSize就是这段数据的字节数,需要首先传到客户端,客户端才可以正常接收
客户端:
int sizelen = sClient.Receive(sizebuff,10,50);//接受数据大小
if(sizelen !=0)
{
char flag[]="1";
sClient.Send(flag,2,10);
}
sizebuff[sizelen]='\0';
exsize = atoi(sizebuff);
printf("数据大小:%d\n",exsize);
extemp = new char[exsize+256];//初始化存储信息的数据,留出冗余空间,以防溢出
exdata = extemp;//新建指针指向起始地址,方便接收后调用
int BytesReceivedThisTime = 0;
while(BytesReceived < exsize)
{
BytesReceivedThisTime = sClient.Receive(temp,256,1000);
strcpy(extemp,temp);
BytesReceived = BytesReceived + BytesReceivedThisTime;
extemp = extemp + BytesReceivedThisTime;
}
std::string out = exdata;
其中out即为要输出的string,这里建了两个指针指向新建的char数组,其中extemp用于接收时的迭代,exdata用于调用该数组
================================================================================================
图片:
要实现的就是收发图片,这里只写客户端发服务端收的例子,其他均类似
客户端:
int ImageSocketClient::UploadFileBySocket(std::string filename, int type)
{
CFileFind Finder;//确保本地有该文件
if (!Finder.FindFile(filename.c_str()))
{
return -1;//文件未找到
}
char inbuff[2];
sockType = SOCKET_UPLOAD; //上传操作标志位
CString str; //将整形标志位转成字符型
str.Format("%d", sockType);
char* flag = str.GetBuffer(0);
if(!m_bConnectOK) //如果Socket没有连接
{
try
{
CSockAddr saClient(m_strSocketAddress,5858);//设IP和端口
m_SocketClient.Create();
//创建套接字
m_SocketClient.Connect(saClient);
//发起连接
m_SocketClient.Send(flag,str.GetLength(),4); // 发送上传标识符
int len=m_SocketClient.Receive(inbuff,2,4);
if(len == 0)
return 0;
else
m_bConnectOK = TRUE; //连接成功
}
catch (CBlockingSocketException* e)
{
delete(e);
return 0;
}
}
else
{
try
{
m_SocketClient.Send(flag,str.GetLength(),4); // 发送上传标识符
int len=m_SocketClient.Receive(inbuff,2,4);
if(len == 0)
return 0;
}
catch (CBlockingSocketException* e)
{
delete(e);
return 0;
}
}
// 获取本地路径
char szPath[MAX_PATH];
GetCurrentDirectory(MAX_PATH, szPath);
CString strpath(szPath);
int idx = strpath.ReverseFind(_T('\\'));
if( idx != strpath.GetLength()-1)//如果最后一个字符不是\(非根目录),则添加\。
{
strpath = strpath+"\\";
}
//设置本地路径+文件名
CString strLocalFile;
strLocalFile.Format("%s%s",strpath,filename.c_str());
CString strRemoteFile;
//服务器保存路径
switch (type)
{
case 1:
strRemoteFile.Format("\\Preset\\%s",filename.c_str());
break;
case 2:
strRemoteFile.Format("\\IMG_Exception\\%s",filename.c_str());
break;
case 3:
strRemoteFile.Format("\\PTZ_Exception\\%s",filename.c_str());
break;
case 4:
strRemoteFile.Format("\\PRESET_Exception\\%s",filename.c_str());
break;
default:
strRemoteFile = filename.c_str();
break;
}
//发送服务器保存路径,方便后续存储
try
{
char inbuff[2];
char * buf = strRemoteFile.GetBuffer(0);
m_SocketClient.Send(buf,MAX_PATH,5);
int len = m_SocketClient.Receive(inbuff,2,5);
if(len == 0)
return 0; //发送失败
}
catch (CBlockingSocketException* e)
{
delete(e);
return 0;
}
FILE * fstream = fopen(strLocalFile,"rb"); // 读取文件数据
if ( NULL == fstream )
{
printf( "打开文件失败,错误码:%d", GetLastError() );
return 0;
}
int nNumRead = 0;
char temp[256];
if(NULL != fstream)
{
WaitForSingleObject(m_hMutex,MutexTime_ImageSocket);
printf("开始上传\n");
while( !feof( fstream ) )//未到文件末尾,则继续发送
{
nNumRead = fread( temp, 1, 256, fstream );
m_SocketClient.Send( temp, nNumRead, 500 );
}
printf("上传成功\n");
fclose( fstream );
ReleaseMutex(m_hMutex);
}
m_SocketClient.Close();
m_SocketClient.Cleanup();
return 1;
}
这里首先传操作标识符,让服务器准备好接收图片,
然后根据文件名和类型确定服务端的图片存储路径,并传给服务端
然后再分段存储图片
服务端:
int ReceiveImage(CBlockingSocket& sockCon)
{
char szPath[MAX_PATH];//获取本地路径
GetCurrentDirectory(MAX_PATH, szPath);
CString strpath(szPath);
// 获取客户端传来的存储路径
char pathtemp[MAX_PATH];
int len = sockCon.Receive( pathtemp,MAX_PATH,5 );
if(len == 0)
return 0;
else // 若获取成功则返回成功标志位
{
char sucflag[2] = "1";
sockCon.Send(sucflag,2,5);
}
CString filepath(pathtemp);
//设置存储文件路径
CString strSaveFile;
strSaveFile.Format("%s%s",strpath,filepath);
printf("存储路径:%s\n",strSaveFile);
CFileFind Finder;//确保本地没有该文件
if (Finder.FindFile(strSaveFile))
{
DeleteFile(strSaveFile);
}
WaitForSingleObject(m_hMutex,MutexTime_ImageSocket);
FILE * fstream = fopen(strSaveFile, "wb" );//打开文件操作
if ( NULL == fstream )
{
return 0;
}
//分段接受并存储文件
char temp[256];
int nNumRead = 0;
printf("开始接收图片\n");
while( true )
{
nNumRead = sockCon.Receive(temp,256,500);//文件分段接收
if ( 0 == nNumRead )//若仍有数据,则持续接受
break;
fwrite( temp, 1, nNumRead, fstream );
}
ReleaseMutex(m_hMutex);
printf("图片接收成功\n");
fclose( fstream );
return 1;
}
最后我再说几条在用CBlockingSocket封装类需要注意的几点
1. 由于Send和Receive函数最后的参数是超时时间,所以在传输大文件时,尽量将其设得高点,我在传输大图片时就遇到过超时而只传一半的情况。
2. 若客户端和服务端有多次Send和Receive交互,对应的Send和Receive之间的接受字节大小(即第二个参数)一定要对应。
3. 在连续两个Send或Receive时一定要注意发送方和接收方的字节长度,若有偏差则很可能会将Send的字符串分割到两个Receive中去。
4. 全局变量指针在新开的线程中无法调用,报错为Bad Ptr,只能在线程中定义新指针。