在项目中,需要客户端从服务器端下载一些配置文件。由于这些文件个头不是很大,最大的也不超过2M,另外,传输是在局域网中进行的,为了传输速度和节省资源,采用了UDP传输。
UDP传输有对等传输,有C/S方式的传输,项目中选用的是C/S传输。
Client头文件代码
#ifndef _CUDP_SEND_FILE
#define _CUDP_SEND_FILE
#ifndef _CUDP_SEND_FILE_EXP
#define _CUDP_SEND_FILE_EXP extern "C" __declspec(dllimport)
#endif
/** szFileName 文件的名字:如apr.xml
szPathFile:保存到本地的文件的路径
return : 是否获取文件是成功的
*/
_CUDP_SEND_FILE_EXP bool GetFile(const char* szFileName, const char* szPathFile, unsigned short local_port,
const char* remote_ip, unsigned short remote_port);
#endif
Client源文件
#include "StdAfx.h"
#define _CUDP_SEND_FILE_EXP extern "C" __declspec(dllexport)
#include "UdpSendFile.h"
#include <Winsock2.h>
#pragma comment(lib, "WS2_32.lib")
#include<iostream>
using std::cout;
using std::endl;
#define RECV_BUF 1024 * 1024
#define MAX_BAG_SIZE 2000
_CUDP_SEND_FILE_EXP bool GetFile(const char* szFileName, const char* szPathFile,unsigned short local_port,
const char* remote_ip, unsigned short remote_port)
{
if(szFileName == NULL || szPathFile == NULL || remote_ip == NULL)
return false;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
WSACleanup( );
return false;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return false;
}
SOCKET client_socket = socket(PF_INET,SOCK_DGRAM,0);
if(client_socket == SOCKET_ERROR)
{
int nError = WSAGetLastError();
WSACleanup( );
return false;
}
SOCKADDR_IN local_addr;
memset(&local_addr, 0 ,sizeof(SOCKADDR));
local_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local_addr.sin_family=AF_INET;
local_addr.sin_port=htons(local_port);
int nBindingError = 0;
if(SOCKET_ERROR == bind(client_socket, (SOCKADDR*)&local_addr,sizeof(SOCKADDR)))
{
nBindingError = WSAGetLastError();
WSACleanup( );
return false;
}
SOCKADDR_IN scp_addr;
int nAddressLen = sizeof(scp_addr);
memset(&scp_addr, 0 , nAddressLen);
scp_addr.sin_addr.S_un.S_addr=inet_addr(remote_ip);
scp_addr.sin_family=AF_INET;
scp_addr.sin_port=htons(remote_port);
sendto(client_socket,szFileName,strlen(szFileName) + 1,0,(SOCKADDR*)&scp_addr,nAddressLen);
//
timeval outtime ; // 超时结构
FD_SET fdr = {1, client_socket};
outtime.tv_sec = 10;
outtime.tv_usec = 0;
int nSelectRet;
nSelectRet=::select(0, &fdr, NULL, NULL, &outtime); //检查可读状态
if(SOCKET_ERROR==nSelectRet)
{
int nErrorCode = WSAGetLastError();
closesocket(client_socket);
WSACleanup( );
return false;
}
if(nSelectRet==0) //超时发生,无可读数据
{
int nErrorCode = WSAGetLastError();
cout<<"客户端-超时-超时-"<<"error code is"<<nErrorCode<<endl;
}
else
{
int recvCount = 0;
char* recv = new char[RECV_BUF];
memset(recv,0,RECV_BUF);
if(SOCKET_ERROR == recvfrom(client_socket,recv, RECV_BUF,0, (SOCKADDR*)&scp_addr, &nAddressLen))
{
int nErrorCode = WSAGetLastError();
if(WSAEMSGSIZE == nErrorCode)
{
cout<<"数据包太大,超出缓存区"<<endl;
}
else if(WSAECONNABORTED == nErrorCode)
{
cout<<"超时"<<endl;
}
else if(WSAEFAULT == nErrorCode)
{
cout<<"fromLen没有被初始化,Unit中不会对此进行要求"<<endl;
}
else if(WSAESHUTDOWN == nErrorCode)
{
cout<<"本地套接字被关闭"<<endl;
}
}
int recvNumber = atoi(recv);
char tempRecBuff[MAX_BAG_SIZE];
char* pResult = recv;
for(int ii = 0; ii < recvNumber; ++ii)
{
nSelectRet=::select(0, &fdr, NULL, NULL, &outtime); //检查可读状态
if(SOCKET_ERROR==nSelectRet)
{
int nErrorCode = WSAGetLastError();
closesocket(client_socket);
WSACleanup( );
return false;
}
if(nSelectRet==0) //超时发生,无可读数据
{
int nErrorCode = WSAGetLastError();
cout<<"客户端-超时-超时-"<<"error code is"<<nErrorCode<<endl;
}
else
{
memset(tempRecBuff,0,MAX_BAG_SIZE);
if(SOCKET_ERROR == recvfrom(client_socket,tempRecBuff, MAX_BAG_SIZE,0, (SOCKADDR*)&scp_addr, &nAddressLen))
{
int nErrorCode = WSAGetLastError();
if(WSAEMSGSIZE == nErrorCode)
{
cout<<"数据包太大,超出缓存区"<<endl;
}
else if(WSAECONNABORTED == nErrorCode)
{
cout<<"超时"<<endl;
}
else if(WSAEFAULT == nErrorCode)
{
cout<<"fromLen没有被初始化,Unit中不会对此进行要求"<<endl;
}
else if(WSAESHUTDOWN == nErrorCode)
{
cout<<"本地套接字被关闭"<<endl;
}
}
int nLen = strlen(tempRecBuff);
memcpy(pResult,tempRecBuff,nLen);
//
//::sendto(client_socket,"o",2,0,(SOCKADDR*)&scp_addr,nAddressLen);
pResult += nLen;
cout << ii <<endl;
}
}
cout<<recv<<endl;
FILE* fp = fopen(szPathFile,"w");
fprintf(fp,recv);
fflush(fp);
fclose(fp);
delete []recv;
}
closesocket(client_socket);
WSACleanup();
return true;
}
服务器端代码
#include "stdafx.h"
//无连接套接字
#include<stdio.h>
#include<WinSock2.h>
#include<iostream>
#include<Windows.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
#define MTU 1024
bool StartUDPScp(int& nErrorCode);
int _tmain(int argc, _TCHAR* argv[])
{
int ErrorCode = 0;
StartUDPScp(ErrorCode);
system("pause");
return 0;
}
bool StartUDPScp(int& nErrorCode)
{
unsigned short local_port(6000);
unsigned short rev_size(1024);
WORD wVersionRequested;
WSADATA wsaData;
int WSStartUpErr;
wVersionRequested = MAKEWORD( 1, 1 );
WSStartUpErr = WSAStartup( wVersionRequested, &wsaData );
if ( WSStartUpErr != 0 ) {
nErrorCode = WSStartUpErr;
return false;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 )
{
WSACleanup();
return false;
}
/**创建套接字
*/
SOCKET host_sock = socket(AF_INET, SOCK_DGRAM, 0);
/**创建三元素
*/
SOCKADDR_IN host_addr;//大小和SOCKADDR的大小是相同的,都是16
host_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
host_addr.sin_family = AF_INET;
host_addr.sin_port = htons(local_port);
if(bind(host_sock,(sockaddr*)&host_addr,sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
nErrorCode = WSAGetLastError();
WSACleanup();
return false;
}
char* pRev_Buf = new char[rev_size];
while(1){
sockaddr scu_addr;
int fromLen = sizeof(sockaddr);
timeval outtime ; // 超时结构
FD_SET fdr = {1, host_sock};
outtime.tv_sec = 5;
outtime.tv_usec = 0;
int nSelectRet;
nSelectRet=::select(0, &fdr, NULL, NULL, &outtime); //检查可读状态
if(SOCKET_ERROR==nSelectRet)
{
nErrorCode = WSAGetLastError();
closesocket(host_sock);
delete []pRev_Buf;
return false;
}
if(nSelectRet==0) //超时发生,无可读数据
{
nErrorCode = WSAGetLastError();
cout<<"服务端-超时-超时-"<<"error code is"<<nErrorCode<<endl;
}
else
{
if(SOCKET_ERROR == recvfrom(host_sock,pRev_Buf, rev_size,0, &scu_addr, &fromLen))//实际上这里的长度是不用初始化的,在linux中是这样的,但windows中,必须要进行初始化
{
nErrorCode = WSAGetLastError();
if(WSAEMSGSIZE == nErrorCode)
{
cout<<"数据包太大,超出缓存区"<<endl;
}
else if(WSAECONNABORTED == nErrorCode)
{
cout<<"超时"<<endl;
}
else if(WSAEFAULT == nErrorCode)
{
cout<<"fromLen没有被初始化,Unit中不会对此进行要求"<<endl;
}
else if(WSAESHUTDOWN == nErrorCode)
{
cout<<"本地套接字被关闭"<<endl;
}
delete []pRev_Buf;
return false;
}
SOCKADDR_IN* scu_addr_in = (SOCKADDR_IN*)(&scu_addr);
//判断是否是授权的客户端进行连接
cout<<"从"<<inet_ntoa(scu_addr_in->sin_addr)<<":"<<scu_addr_in->sin_port<<"收到的数据是:"<<pRev_Buf<<endl;
//进行发送实验
char fileName[256];
memset(fileName,0,256);
sprintf(fileName,"F:\\%s",pRev_Buf);
FILE* fp = fopen(fileName, "r");
if(fp)
{
fseek(fp,0,SEEK_END);
int filelength = ftell(fp);
filelength++; //加入一个空格
int fileOrignalLength = filelength;
rewind(fp);
char* pBuff = new char[filelength];
char* pOriginBuff = pBuff;
memset(pBuff, 0, filelength);
while(!feof(fp))
{
fgets(pBuff,filelength,fp);
int nlen = strlen(pBuff);
filelength -= nlen;
pBuff += nlen;
}
fclose(fp);
//分批发送
int n = fileOrignalLength / MTU;
int nn = fileOrignalLength % MTU;
int nT = n + ((nn > 0) ? 1 : 0);
cout << "has " << nT << "bag"<<endl;
char* temp = pOriginBuff;
//
char szSendNum[64];
memset(szSendNum, 0, sizeof(szSendNum));
sprintf(szSendNum,"%d",nT);
//
::sendto(host_sock,szSendNum,strlen(szSendNum) + 1,0,(sockaddr*)&scu_addr,sizeof(SOCKADDR_IN));
for(int ii = 0; ii < n;++ii)
{
::sendto(host_sock,temp,MTU,0,(sockaddr*)&scu_addr,sizeof(SOCKADDR_IN));
temp += MTU;
Sleep(2);
//::recvfrom(host_sock,pRev_Buf, rev_size,0, &scu_addr, &fromLen);
}
::sendto(host_sock,temp,nn,0,(sockaddr*)&scu_addr,sizeof(SOCKADDR_IN));
//
delete []pOriginBuff;
}
}
}
delete []pRev_Buf;
if(0 != closesocket(host_sock))
{
nErrorCode = WSAGetLastError();
return false;
}
return true;
};
在实际的应用中,如果发送的频率太高,很容易丢包。 采用了Sleep的方式,但这种方式不是解决问题的好方法,但确实是一个有效的方法。