C++中用Socket实现对结构体、长字符串和图片的传输

来源:互联网 发布:程序员老黄历源码 编辑:程序博客网 时间:2024/05/09 03:36

首先说明下,本文的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;elsem_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,只能在线程中定义新指针。


原创粉丝点击