SPClient.cpp
上传用户:liguizhu
上传日期:2015-11-01
资源大小:2422k
文件大小:17k
源码类别:

P2P编程

开发平台:

Visual C++

  1. /*
  2. *  Openmysee
  3. *
  4. *  This program is free software; you can redistribute it and/or modify
  5. *  it under the terms of the GNU General Public License as published by
  6. *  the Free Software Foundation; either version 2 of the License, or
  7. *  (at your option) any later version.
  8. *
  9. *  This program is distributed in the hope that it will be useful,
  10. *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. *  GNU General Public License for more details.
  13. *
  14. *  You should have received a copy of the GNU General Public License
  15. *  along with this program; if not, write to the Free Software
  16. *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  17. *
  18. */
  19. #include "StdAfx.h"
  20. #include "SPClient.h"
  21. #include "CaptureServer.h"
  22. #include "MD5.h"
  23. SPClient::SPClient(CaptureServer* cServer, NormalAddress address, BufferMgr* bufferMgr, LogMgr* log) : m_freeList(8, 1) {
  24. cs = cServer;
  25. m_bufferMgr = bufferMgr;
  26. logFile = log;
  27. addr = address;
  28. isRunning = TRUE;
  29. isLogin = FALSE;
  30. m_socket = INVALID_SOCKET;
  31. sendPointer = NULL;
  32. recvPointer = NULL;
  33. recvOff = 0;
  34. errStr[0] = 0;
  35. lastSentBlockID = 0;
  36. memset(&header, 0, sizeof(header));
  37. readData = 0;
  38.  
  39. DWORD threadID;
  40. hThread = CreateThread(
  41. NULL, 0, (LPTHREAD_START_ROUTINE)(SPClient::RunReceiver), this, 0, &threadID);
  42. }
  43. SPClient::~SPClient() {
  44. isRunning = FALSE;
  45. Disconnect();
  46. if(hThread) {
  47. // 如果一段时间内不能正常停止,就强行终止线程
  48. DWORD ret = WaitForSingleObject(hThread, 5000);
  49. if(ret == WAIT_TIMEOUT) {
  50. TerminateThread(hThread, 0);
  51. }
  52. CloseHandle(hThread);
  53. hThread = NULL;
  54. }
  55. }
  56. BOOL SPClient::SendRegister() {
  57. UINT8 nameSize = cs->cfgData.chnlStr.size();
  58. if(nameSize >= 0xff)
  59. return FALSE;
  60. TCPPacket* packet;
  61. if(!SendBegin(packet, CS2SP_REGISTER))
  62. return FALSE;
  63. //2.write channel name
  64. CopyMoveDes(sendPointer, &nameSize, sizeof(nameSize));
  65. CopyMoveDes(sendPointer, cs->cfgData.chnlStr.data(), nameSize);
  66. //3.write userinfo.
  67. CopyMoveDes(sendPointer, &cs->cfgData.userID, sizeof(UINT));
  68. CopyMoveDes(sendPointer, cs->cfgData.password.data(), MD5_LEN);
  69. //4. ratio ,maxblocksize
  70. UINT maxblockSize = BLOCK_SIZE;
  71. CopyMoveDes(sendPointer, &maxblockSize, sizeof(maxblockSize));
  72. float ratio = cs->GetSpeedInKBPS();
  73. CopyMoveDes(sendPointer, &ratio, sizeof(ratio));
  74. //5. write video & audio
  75.   TVMEDIATYPESECTION videoTV, audioTV;
  76. PBYTE videoData = NULL;
  77. PBYTE audioData = NULL;
  78. do{
  79. delete [] videoData;
  80. delete [] audioData;
  81. Sleep(100);
  82. if(!isRunning) {
  83. m_freeList.Release(packet);
  84. return FALSE;
  85. }
  86. }
  87. while(!cs->GetFormatData(videoTV, videoData, FALSE) || !cs->GetFormatData(audioTV, audioData, TRUE) );
  88. // 检查媒体类型数据是否正确,关键是是否为空
  89. if(videoTV.cbFormat <= 0 && audioTV.cbFormat <= 0) {
  90. MessageBox(cs->parentWindow, "媒体数据为空!请重新启动采集端。", "错误", MB_OK|MB_ICONSTOP);
  91. return FALSE;
  92. }
  93. USHORT channelinfoLen = 2*sizeof(TVMEDIATYPESECTION)+videoTV.cbFormat + audioTV.cbFormat;
  94. CopyMoveDes(sendPointer, &channelinfoLen, sizeof(channelinfoLen));
  95. CopyMoveDes(sendPointer, &videoTV, sizeof(TVMEDIATYPESECTION));
  96. if(videoData)
  97. CopyMoveDes(sendPointer, videoData, videoTV.cbFormat);
  98. CopyMoveDes(sendPointer, &audioTV, sizeof(TVMEDIATYPESECTION));
  99. if(audioData)
  100. CopyMoveDes(sendPointer, audioData, audioTV.cbFormat);
  101. SendEnd(packet);
  102. return TRUE;
  103. }
  104. /**********发送消息的函数**********/
  105. BOOL SPClient::SendBlock() {
  106. if(m_sendList.size() > 10)
  107. return TRUE;
  108. TCPPacket* packet;
  109. if(!SendBegin(packet, CS2SP_BLOCK))
  110. return FALSE;
  111.     
  112. //2.write ID.
  113. CopyMoveDes(sendPointer, &lastSentBlockID, sizeof(lastSentBlockID));
  114. //4.write data
  115. UINT size = 0;
  116. if(!m_bufferMgr->GetBlock(lastSentBlockID, reinterpret_cast<PBYTE>(sendPointer+sizeof(size)), size)) {
  117. // 一定要释放这个packet
  118. m_freeList.Release(packet);
  119. return TRUE;
  120. }
  121. char* hashcode = "";
  122. #ifdef DEBUG
  123. MD5 md5(reinterpret_cast<PBYTE>(sendPointer+sizeof(size)), size);
  124. hashcode = md5.hex_digest();
  125. #endif
  126. // verify block
  127. char* startPos = sendPointer+sizeof(size);
  128. char* tempPos = startPos + 8;
  129. if(lastSentBlockID == 0) {
  130. if(*((UINT*)startPos + 1) != UINT_MAX)
  131. tempPos = startPos + *((UINT*)startPos + 1);
  132. else
  133. tempPos = startPos + size;
  134. }
  135. while(header.size < 1638400) {
  136. //写header信息
  137. if((UINT)readData < sizeof(header)) {
  138. if(startPos+size - tempPos < sizeof(header)-readData){
  139. memcpy((char*)&header + readData, tempPos, startPos+size - tempPos);
  140. readData += startPos+size - tempPos;
  141. break;
  142. }
  143. else {
  144. memcpy((char*)&header + readData, tempPos, sizeof(header)-readData);
  145. tempPos += sizeof(header)-readData;//头的位置tempPos
  146. readData = sizeof(header);
  147. //assert(header.size < 1638400);
  148. if(header.size >= 1638400) {
  149. logFile->StatusOut("Header size %d, blockID %d, offset %d", 
  150. header.size, lastSentBlockID, readData);
  151. break;
  152. }
  153. }
  154. }
  155. if(readData >= sizeof(header)) {
  156. if(startPos+size - tempPos < header.size - readData) {
  157. readData += startPos+size - tempPos;
  158. break;
  159. }
  160. else {
  161. tempPos += header.size - readData;
  162. readData = 0;
  163. }
  164. }
  165. }
  166.     //3.write data size 
  167. CopyMoveDes(sendPointer, &size, sizeof(size));
  168. int firstKeySampleOffset;
  169. memcpy(&firstKeySampleOffset, packet->buf+sizeof(UINT)*3+sizeof(char), sizeof(firstKeySampleOffset));
  170. LONGLONG keySample;
  171. memcpy(&keySample, packet->buf+sizeof(UINT)*3+sizeof(char)+firstKeySampleOffset, sizeof(keySample));
  172. memcpy(&firstKeySampleOffset, packet->buf+sizeof(UINT)*3+sizeof(char), sizeof(firstKeySampleOffset));
  173. if(firstKeySampleOffset == 0)
  174. keySample = 0;
  175. char temp[64];
  176. _i64toa(keySample, temp, 10);
  177. logFile->StatusOut("Queue Block. ID:%d/%d, offset: %d, keysample: %s, hash %s", 
  178. lastSentBlockID, m_bufferMgr->GetMaxBlockID(), firstKeySampleOffset, temp, hashcode);
  179. #ifdef DEBUG
  180. delete [] hashcode;
  181. #endif
  182. sendPointer += size;
  183. SendEnd(packet);
  184. lastSentBlockID++;
  185. return TRUE;
  186. }
  187. BOOL SPClient::SendUpdate(){
  188. TCPPacket* packet;
  189. if(!SendBegin(packet, CS2SP_UPDATE))
  190. return FALSE;
  191. float ratio = cs->GetSpeedInKBPS();
  192. CopyMoveDes(sendPointer, &ratio, sizeof(ratio));
  193. SendEnd(packet);
  194. return TRUE;
  195. }
  196. BOOL SPClient::SendMediaType() {
  197.     MediaData mediaData;
  198.     if(!m_bufferMgr->GetMediaData(lastSentBlockID, mediaData))
  199.         return FALSE;
  200. TCPPacket* packet;
  201. if(!SendBegin(packet, CS2SP_MEDIA_TYPE))
  202. return FALSE;
  203.     UINT size = mediaData.audioType.cbFormat + mediaData.videoType.cbFormat + 
  204.         sizeof(mediaData.audioType) + sizeof(mediaData.videoType);
  205.     CopyMoveDes(sendPointer, &size, sizeof(UINT));
  206.     CopyMoveDes(sendPointer, &mediaData.videoType, sizeof(mediaData.videoType));
  207.     CopyMoveDes(sendPointer, mediaData.videoData, mediaData.videoType.cbFormat);
  208.     CopyMoveDes(sendPointer, &mediaData.audioType, sizeof(mediaData.audioType));
  209.     CopyMoveDes(sendPointer, mediaData.audioData, mediaData.audioType.cbFormat);
  210. logFile->StatusOut("Queue Block media data. ID:%d/%d", lastSentBlockID, m_bufferMgr->GetMaxBlockID());
  211. SendEnd(packet);
  212. return TRUE;
  213. }
  214. CONNECT_RESULT SPClient::Connecting() {
  215. logFile->StatusOut("Connecting to %s:%d.", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  216. CONNECT_RESULT ret = CR_ERROR;
  217. // Create a TCP/IP socket that is bound to the server.
  218. // Microsoft Knowledge Base: WSA_FLAG_OVERLAPPED Is Needed for Non-Blocking Sockets
  219. // http://support.microsoft.com/default.aspx?scid=kb;EN-US;179942
  220. m_socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
  221. if(m_socket == INVALID_SOCKET) {
  222. logFile->StatusErr("Creating socket", WSAGetLastError());
  223. return ret;
  224. }
  225. // 不使用Nagle算法
  226. BOOL bNoDelay = TRUE;
  227. if(setsockopt(m_socket, SOL_SOCKET, TCP_NODELAY, (const char*)&bNoDelay, sizeof(bNoDelay)) == SOCKET_ERROR) {
  228. logFile->StatusErr("Setting UDP socket as TCP_NODELAY", WSAGetLastError());
  229. return ret;
  230. }
  231. // Set this socket as a Non-blocking socket.
  232. ULONG flag = 1;
  233. if(ioctlsocket(m_socket, FIONBIO, &flag) == SOCKET_ERROR) {
  234. logFile->StatusErr("Setting socket as non-blocking", WSAGetLastError());
  235. return ret;
  236. }
  237. // Connect to remote address
  238. if(WSAConnect(m_socket, (sockaddr*)&addr, sizeof(sockaddr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) {
  239. if(WSAGetLastError() != WSAEWOULDBLOCK) {
  240. logFile->StatusErr("Connecting socket", WSAGetLastError());
  241. return ret;
  242. }
  243. else {
  244. ret = CR_WOULDBLOCK;
  245. logFile->StatusOut("%s:%d is blocking.", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  246. }
  247. }
  248. else {
  249. ret = CR_CONNECTED;
  250. logFile->StatusOut("%s:%d is connected.", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  251. }
  252. return ret;
  253. }
  254. void SPClient::Disconnect() {
  255. isLogin = FALSE;
  256. m_bufferMgr->StopSave();
  257. ClearTransferInfo();
  258. m_sendList.clear();
  259. if(m_socket != INVALID_SOCKET) {
  260. ::TE_CloseSocket(m_socket, FALSE);
  261. logFile->StatusOut("SPClient: disconnected from %s:%d.", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  262. m_socket = INVALID_SOCKET;
  263. }
  264. }
  265. BOOL SPClient::BaseRecv() {
  266. int ret = recv(m_socket, recvBuf+recvOff, P2P_BUF_SIZE-recvOff, 0);
  267. if(ret < 0) {
  268. DWORD lastError = ::WSAGetLastError();
  269. if (WSAEWOULDBLOCK != lastError) {
  270. logFile->StatusErr("Receiving data on TCP", lastError);
  271. return FALSE;
  272. }
  273. else
  274. return TRUE;
  275. }
  276. else if(0 == ret) {
  277. logFile->StatusOut("Connection has been disconnected gracefully.");
  278. return FALSE;
  279. }
  280. recvOff += ret;
  281. totalDownBytes += ret;
  282. BOOL retVal = FALSE;
  283. for(;;) {
  284. // because multiple msgs can be received at once.
  285. // keep call parseMsg() till !MSG_COMPLETE
  286. MSG_STATE ms = ParseMsg(); 
  287. switch(ms) {
  288. case MSG_COMPLETE:
  289. continue;
  290. case MSG_UNCOMPLETE:
  291. retVal = TRUE;
  292. break;
  293. case MSG_ERR_SIZE:
  294. sprintf(errStr, "错误的消息大小!");
  295. break;
  296. case MSG_ERR_TYPE: 
  297. sprintf(errStr, "错误的消息类型!");
  298. break;
  299. break;
  300. case MSG_REMOTE_ERR:
  301. // 错误信息已经在errStr中了
  302. break;
  303. default:
  304. sprintf(errStr, "未知错误类型!");
  305. break;
  306. }
  307. if(strlen(errStr) > 0)
  308. logFile->StatusOut("错误: %s", errStr);
  309. break;
  310. }
  311. return retVal;
  312. }
  313. MSG_STATE SPClient::ParseMsg() {
  314. // 如果过小,则不是正常的包
  315. if(recvOff < sizeof(int)+sizeof(BYTE))
  316. return MSG_UNCOMPLETE;
  317. // 把移动指针放到数据的起始地址
  318. recvPointer = recvBuf;
  319. // 读取消息大小
  320. UINT msgSize;
  321. CopyMoveSrc(&msgSize, recvPointer, sizeof(msgSize));
  322. // 读取消息类型
  323. UINT8 msgType;
  324. CopyMoveSrc(&msgType, recvPointer, sizeof(msgType));
  325. // msgSize是否正常
  326. if(msgSize > P2P_BUF_SIZE || msgSize < sizeof(int)+sizeof(BYTE))
  327. return MSG_ERR_SIZE;
  328. // 是否包含完成的消息
  329. if(recvOff < msgSize)
  330. return MSG_UNCOMPLETE;
  331. MSG_STATE ret = MSG_COMPLETE;
  332. switch(msgType) {
  333. case SP2CS_WELCOME:
  334. ret = OnWelcome();
  335. break;
  336. case SP2CS_MSG:
  337. ret = OnMsg();
  338. break;
  339. default:
  340. ret = MSG_ERR_TYPE;
  341. break;
  342. }
  343. // copy left data to start of recvBuf
  344. if(recvOff >= msgSize) {
  345. memcpy(recvBuf, recvBuf+msgSize, recvOff-msgSize);
  346. recvOff -= msgSize;
  347. }
  348. return ret;
  349. }
  350. //解析注册成功消息
  351. MSG_STATE SPClient::OnWelcome() {
  352. UINT startBlockID;
  353. CopyMoveSrc(&startBlockID, recvPointer, sizeof(startBlockID));
  354. lastSentBlockID = startBlockID;
  355. logFile->StatusOut("Start Block %d.", lastSentBlockID);
  356. isLogin = TRUE;
  357. return MSG_COMPLETE;
  358. }
  359. //收到SP_MSG消息
  360. MSG_STATE SPClient::OnMsg() {
  361. // 错误代码
  362. UINT16 errCode;
  363. CopyMoveSrc(&errCode, recvPointer, sizeof(errCode));
  364. // 是否需要断开连接
  365. bool shouldDisconnect;
  366. CopyMoveSrc(&shouldDisconnect, recvPointer, sizeof(shouldDisconnect));
  367. // 根据错误代码处理
  368. switch(errCode) {
  369. case ERR_PROTOCOL_FORMAT:
  370. sprintf(errStr, "协议错误");
  371. break;
  372. case ERR_AUTHORIZATION:
  373. sprintf(errStr, "验证错误");
  374. break;
  375. case ERR_INTERNAL:
  376. sprintf(errStr, "未知错误");
  377. break;
  378. default:
  379. shouldDisconnect = true;
  380. }
  381. if(shouldDisconnect) {
  382. return MSG_REMOTE_ERR;
  383. }
  384. return MSG_COMPLETE;
  385. }
  386. BOOL SPClient::SendPackets() {
  387. if(m_sendList.empty())
  388. return TRUE;
  389. TCPPacket* packet = m_sendList.front();
  390. m_sendList.pop_front();
  391. int ret = send(m_socket, packet->buf+packet->sent, min(packet->size-packet->sent, 40000), 0);
  392. if(SOCKET_ERROR == ret) {
  393. DWORD lastError = ::WSAGetLastError();
  394. if (WSAEWOULDBLOCK == lastError) {
  395. m_sendList.push_front(packet);
  396. }
  397. else {
  398. m_sendList.push_front(packet);
  399. logFile->StatusErr("Sending data on TCP", lastError);
  400. return FALSE;
  401. }
  402. }
  403. else {
  404. totalUpBytes += ret;
  405. logFile->StatusOut("Send data %d of %d", ret, packet->size-packet->sent);
  406. assert(ret <= packet->size-packet->sent);
  407. if(ret == packet->size-packet->sent) {
  408. if(packet->size > 16384)
  409. logFile->StatusOut("Sent Block %d.", *packet->GetBlockID());
  410. // 已经发送完毕,释放Buffer
  411. m_freeList.Release(packet);
  412. }
  413. else { // 尚未发送完毕,下次继续发送
  414. packet->sent += ret;
  415. m_sendList.push_front(packet);
  416. logFile->StatusOut("!!!!!!!!!!!");
  417. }
  418. }
  419. return TRUE;
  420. }
  421. void SPClient::RunReceiver(SPClient* client)
  422. {
  423. timeval timeout; 
  424. DWORD lastSentUpdate = 0;
  425. fd_set read_set, write_set;
  426. DWORD lastManageTime=0, currTime=0;
  427. while(client->isRunning)
  428. {
  429. if(client->m_socket == INVALID_SOCKET)
  430. {
  431. if(!client->m_bufferMgr->ShouldConnect()) {
  432. Sleep(500);
  433. continue;
  434. }
  435. CONNECT_RESULT ret = client->Connecting();
  436. if(ret == CR_WOULDBLOCK) {
  437. // 等待8秒钟,看能否连接上
  438. FD_ZERO(&write_set);
  439. FD_SET(client->m_socket, &write_set);
  440. timeout.tv_sec = 8;
  441. timeout.tv_usec = 0;
  442. int s = select(0, NULL, &write_set, NULL, &timeout);
  443. if(s > 0)
  444. ret = CR_CONNECTED;
  445. }
  446. if(ret == CR_CONNECTED) {
  447. client->lastSentBlockID = 0;
  448. client->readData = 0;
  449. // 可以开始存储数据了
  450. client->m_bufferMgr->StartSave();
  451. }
  452. else {
  453. TE_CloseSocket(client->m_socket, FALSE);
  454. client->m_socket = INVALID_SOCKET;
  455. client->logFile->StatusOut("无法连接SP,请检查网络。");
  456. //MessageBox(NULL, "无法连接SP,请检查网络。然后重新打开本程序。", "错误", MB_OK|MB_ICONINFORMATION);
  457.                 for(int i = 0; i < client->cs->cfgData.reconnectSecond; ++i) {
  458.                     if(!client->isRunning)
  459.                         break;
  460.     Sleep(1000);
  461.                 }
  462. continue;
  463. }
  464. }
  465. if(client->SendRegister()) {
  466. // 开始接收发送数据
  467. while(client->isRunning) {
  468. // 获取当前时间
  469. currTime = timeGetTime();
  470. timeout.tv_sec = 0;
  471. timeout.tv_usec = 20000;
  472. FD_ZERO(&read_set);
  473. FD_SET(client->m_socket, &read_set);
  474. int s = select(0, &read_set, 0, NULL, &timeout);
  475. if(s > 0) {
  476.                     if(FD_ISSET(client->m_socket, &read_set)) {
  477.                         // 接收信息
  478.                         if(!client->BaseRecv()) {
  479.                             for(int i = 0; i < client->cs->cfgData.reconnectSecond; ++i) {
  480.                                 if(!client->isRunning)
  481.                                     break;
  482.                                 Sleep(1000);
  483.                             }
  484.                             break;
  485. }
  486. }
  487. }
  488.   else if(s == SOCKET_ERROR) {
  489. client->logFile->StatusErr("selecting", WSAGetLastError());
  490. Sleep(1); // prevent dead loop
  491. }
  492. if(currTime-lastManageTime >= 10000) {
  493. lastManageTime = currTime;
  494. // 生成传输信息并打印
  495. client->GenerateTransferInfo(TRUE);
  496. client->logFile->StatusOut("Cur: (%.2f/%.2f)KB/s. Avg: (%.2f/%.2f)KB/s. Total: %.2f/%.2fMB.", 
  497. client->currDownSpeed/1024, client->currUpSpeed/1024, 
  498. client->avgDownSpeed/1024, client->avgUpSpeed/1024, 
  499. client->totalDownBytes/1024.f/1024.f, client->totalUpBytes/1024.f/1024.f);
  500. }
  501. if(!client->m_bufferMgr->CheckRecvingSample()) {
  502. client->logFile->StatusOut("No Sample Any More!");
  503. //MessageBox(client->cs->parentWindow, "CaptureServer 20秒钟没有接收到Sample了!", "Sample中断", MB_OK|MB_ICONSTOP);
  504. if(client->cs->parentWindow)
  505. SendMessage(client->cs->parentWindow, WM_NOMORESAMPLE, 0, 0);
  506. }
  507. // 发送数据
  508.                 if(client->isLogin && client->isRunning) {
  509.                     // 目前SP尚不支持这个协议
  510.                     //client->SendMediaType();
  511. client->SendBlock();
  512.                 }
  513. if(currTime-lastSentUpdate > 10000) {
  514. if(!client->SendUpdate())
  515. break;
  516. lastSentUpdate = currTime;
  517. }
  518. if(!client->SendPackets())
  519. break;
  520. }
  521. }
  522. client->Disconnect();
  523. }
  524. }
  525. BOOL SPClient::SendBegin(TCPPacket*& packet, UINT8 msgType) {
  526. packet= m_freeList.Allocate();
  527. if(!packet)
  528. return FALSE;
  529. // 先留着消息大小不写,到最后再写
  530. sendPointer = packet->buf+sizeof(UINT);
  531. CopyMoveDes(sendPointer, &msgType, sizeof(msgType));
  532. return TRUE;
  533. }
  534. void SPClient::SendEnd(TCPPacket*& packet) {
  535. // 消息的大小就是移动的指针减去初始的指针
  536. packet->size = sendPointer-packet->buf;
  537. packet->sent = 0;
  538. memcpy(packet->buf, &packet->size, sizeof(packet->size));
  539. m_sendList.push_back(packet);
  540. }
  541. void SPClient::CopyMoveSrc(void * des, const char *& src, size_t size) {
  542. assert(des && src);
  543. if(!des || !src)
  544. return;
  545. memcpy(des, src, size);
  546. src += size;
  547. }
  548. void SPClient::CopyMoveDes(char *& des, const void * src, size_t size) {
  549. assert(des && src);
  550. if(!des || !src)
  551. return;
  552. memcpy(des, src, size);
  553. des += size;
  554. }