关于socket的整理

来源:互联网 发布:h5 全景相片源码 编辑:程序博客网 时间:2024/05/17 05:58

 对于socket的编程,一直希望能够写一个很好用的封装好了的类。但是,却发现因需求的改变,导致总是封装好的socket无法下次再使用。近几天,又折腾了下socket,故将想到的整理如下。

              该文主要是针对一些资料的整理(个人没那个能力封装好比他们更好的类)。而且到现在为止,也没完全整理出一个比较有实用性的资源。该文主要涉及如下:

              (1)对封装类Socket的感受。

              (2)对封装类TcpTran的感受。

              (3)对Socket接收函数的一个整理。

              (4)对Socket协议封装的一个整理。


一、对封装类Socket的感受

              1、该类链接:原资源、解析资源、介绍

              2、考虑到希望保存网页,其该代码会在后面列出。

              3、感受

                    该类我相信应该是封装的很好的,其主要应该是针对HTTP(我的理解)。但是我的项目是一个基于服务端、客户端传输(基于directshow的音视频聊天系统),然后秉着既然敢开源应该也牛B的想法,直接用。但是用的时候,却发现如下为难的地方:

                   (1)其初始化在构造函数中,如果我想实例化类socket(写了一个SocketManager对socket进行管理),却发现我无法不知道该如何实例化一个全局的socket类实例,因为Socket socket是带参的构造函数,虽然可以在使用的时候实例化,但是如果实例化一个SOCKET当做类SocketManager一个全局的成员变量使用的时候,却只能在SocketManager.h中定义一个Socket*(我定义了一个void*,然后void *pSocket = (void*)&socket这样转换)的变量。因socket无法在构造函数中实例化,使得我必须找到第一个使用它的地方对pSocket进行初始化。

                    (2)如果针对发送的数据是char *的,其我感觉好像还需要自己加一个Send和Recv函数。不知道怎么处理成string,也没明白,其代码为什么要处理成string,没有去仔细深究。


二、对封装类TcpTran的感受

            1、该类链接:资源

            2、代码

[cpp] view plain copy
  1. // TcpTran.cpp: implementation of the TcpTran class.  
  2. //  
  3. //////////////////////////////////////////////////////////////////////  
  4. #include "stdafx.h"  
  5. #include "TcpTran.h"  
  6.    
  7. #pragma region TcpTran类  
  8. /////////////////////////////////////////////////////////////////////  
  9. //类名:TcpTran  
  10. //功能:完成TCP通信  
  11. /////////////////////////////////////////////////////////////////////  
  12. //构造函数  
  13. TcpTran::TcpTran()  
  14. {  
  15.     //将套接字初始化为无效  
  16.     m_Socket = INVALID_SOCKET;  
  17. }  
  18. //析构函数  
  19. TcpTran::~TcpTran(){}  
  20. #pragma endregion  
  21.    
  22. #pragma region InitSocketLib函数  
  23. /////////////////////////////////////////////////////////////////////  
  24. //函数名:TcpTran  
  25. //功能:初始化SOCKET通信库,要求Winsockt 2  
  26. //参数:lowver版本的低位;higver版本的高位  
  27. //返回值:TRUE表示成功,FALSE表示失败  
  28. /////////////////////////////////////////////////////////////////////  
  29. BOOL TcpTran::InitSocketLib(int lowver,int higver )  
  30. {  
  31.     WORD wVersion =0 ;  
  32.     int     errret = -1;  
  33.     WSADATA wsaData;  
  34.    
  35.     wVersion = MAKEWORD(lowver,higver);  
  36.     errret = WSAStartup(wVersion,&wsaData);  
  37.    
  38.     if( LOBYTE( wsaData.wVersion) != 2 ||  
  39.         HIBYTE( wsaData.wVersion) !=2 )  
  40.     {  
  41.         MessageBox(NULL,L"winsocket库版本低",L"提示",MB_OK);  
  42.         return FALSE;  
  43.     }  
  44.     return TRUE;  
  45. }  
  46. #pragma endregion  
  47.    
  48. #pragma region InitSocket函数  
  49. /////////////////////////////////////////////////////////////////////  
  50. //函数名:InitSocket  
  51. //功能:根据类型初始化SOCKET资源  
  52. //参数:SocketType:SOCKETBIND/SOCKETNOBIND 绑定/不绑定本地端口;  
  53. //      strBindIp:要绑定的IP地址  
  54. //      BindPort:要绑定的本地端口,若为0表示系统自动产生  
  55. //      opt:是否支持端口重用  
  56. //返回值:错误:INVALID_SOCKET;正确:返回可用的SOCKET  
  57. /////////////////////////////////////////////////////////////////////  
  58. SOCKET    TcpTran::InitSocket( int SocketType, string strBindIp,u_short BindPort,int opt)  
  59. {  
  60.     SOCKET socketid = INVALID_SOCKET;  
  61.     //创建一个能进行网络通信的套接字  
  62.     socketid = socket(PF_INET,SOCK_STREAM,0);  
  63.     SOCKADDR_IN sockStruct;  
  64.     sockStruct.sin_family = AF_INET; //使用TCP/IP协议  
  65.    
  66.     if( strBindIp.empty() )  
  67.     {     
  68.         //strBindIp为空,则用INADDR_ANY表示SOCKET可接收来自任何IP的消息  
  69.         sockStruct.sin_addr.S_un.S_addr = INADDR_ANY;  
  70.     }  
  71.     else  
  72.     {  
  73.         //strBindIp不为空,将其转化为char*后,将IP地址从点分十进制转换为无符号长整型  
  74.         sockStruct.sin_addr.S_un.S_addr = inet_addr(strBindIp.c_str());      
  75.     }  
  76.    
  77.     //将参数BindPort转换为网络字节序后保存  
  78.     sockStruct.sin_port = htons(BindPort);  
  79.     //不绑定端口  
  80.     if( SocketType == SOCKETNOBIND )  
  81.     {  
  82.         if(connect(socketid,(LPSOCKADDR)&sockStruct,sizeof(sockStruct)) == SOCKET_ERROR)  
  83.         {  
  84.             //OutputDebugString("连接错误!");  
  85.             closesocket(socketid);  
  86.             //关闭已打开的套接字,防止占用内存  
  87.             shutdown(socketid,2);  
  88.             //置为无效  
  89.             socketid = INVALID_SOCKET;  
  90.         }  
  91.         m_Socket = socketid;  
  92.     }  
  93.     //若绑定本地端口  
  94.     else if( SocketType == SOCKETBIND )  
  95.     {  
  96.         if(bind(socketid,(sockaddr*)&sockStruct,sizeof(sockaddr_in)) == SOCKET_ERROR)  
  97.         {  
  98.             //绑定失败则关闭套接字  
  99.             closesocket(socketid);  
  100.             //置为无效  
  101.             socketid = INVALID_SOCKET;  
  102.    
  103.         }  
  104.         else  
  105.         {   //绑定IP和端口成功,监听来自网络的连接队列。SOMAXCONN指明最大连接数  
  106.             if( listen(socketid,SOMAXCONN) == SOCKET_ERROR )  
  107.             {  
  108.                 //监听失败则关闭套接字  
  109.                 closesocket(socketid);  
  110.                 //置为无效  
  111.                 socketid = INVALID_SOCKET;  
  112.             }  
  113.         }  
  114.         m_Socket = socketid;  
  115.     }  
  116.     return socketid;  
  117. }      
  118. #pragma endregion  
  119.    
  120. #pragma region NetAccept函数  
  121. SOCKET    TcpTran::NetAccept(SOCKET s,struct sockaddr* addr,int* addrlen)  
  122. {  
  123.     SOCKET accpsocket  = INVALID_SOCKET;  
  124.     accpsocket = accept(s,addr,addrlen);  
  125.     return accpsocket;  
  126. }  
  127. #pragma endregion  
  128.    
  129. #pragma region NetRecv函数  
  130. /////////////////////////////////////////////////////////////////////  
  131. //函数名:NetRecv  
  132. //功能:根据类型初始化SOCKET资源  
  133. //参数:sock:接收端套接字  
  134. //      buf:用来存放接收到的数据的缓冲区  
  135. //      len:接收数据大小  
  136. //      flag:一般设为0  
  137. //      overtime:超时时间  
  138. //      EndMark:结束标记  
  139. //      soonflag:是否立即返回  
  140. //返回值:接收到数据的字节数  
  141. /////////////////////////////////////////////////////////////////////  
  142. int TcpTran::NetRecv(SOCKET sock, char *buf, int len, int flag , int overtime ,char*EndMark,BOOL soonflag)  
  143. {  
  144.     int        ret;  
  145.     int        nLeft = len;  
  146.     int        idx     = 0;  
  147.     int        nCount = 0;  
  148.     fd_set readfds;               //文件描述符  
  149.     struct timeval  timeout;  
  150.     timeout.tv_sec = 0;           //设置超时值  
  151.     timeout.tv_usec = 500;  
  152.     DWORD s_time = GetTickCount();//返回从操作系统启动到现在经过的毫秒数  
  153.    
  154.     while ( nLeft > 0 )  
  155.     {  
  156.         //接收消息  
  157.         MSG msg;  
  158.         PeekMessage(&msg, NULL,  0, 0, PM_REMOVE) ;  
  159.         if(msg.message == WM_QUIT)  
  160.         {return 0;}  
  161.    
  162.         FD_ZERO( &readfds );       //将set清零  
  163.         FD_SET( sock , &readfds ); //将fd加入set  
  164.         //select管理套接字IO,避免出现无辜锁定  
  165.         if( select( 0 , &readfds , NULL , NULL , &timeout ) == SOCKET_ERROR )  
  166.         {return SOCKET_ERROR;}  
  167.    
  168.         DWORD e_time = GetTickCount( );  
  169.         if  ( !FD_ISSET( sock , &readfds ) )  
  170.         {  
  171.             if(e_time - s_time > overtime*1000 ) //超时  
  172.                 return SOCKET_TIMEOUT;  
  173.             else  
  174.                 continue;  
  175.         }  
  176.    
  177.         ret = recv( sock, &buf[idx], nLeft, flag ); //接收数据  
  178.    
  179.         if( soonflag == TRUE )  
  180.         {    return ret;    }  
  181.    
  182.         s_time = e_time ; // 只要有数据就重新置初始时间值  
  183.    
  184.         if ( ret <= 0 )  
  185.         {  
  186.             //错误处理  
  187.             int        LastError = GetLastError();  
  188.             if ( ( -1 == ret ) && ( WSAETIMEDOUT      == LastError ) )  
  189.                 continue;  
  190.             if ( ( -1 == ret ) && ( WSAEWOULDBLOCK      == LastError ) )  
  191.             {  
  192.                 if ( nCount < 2000 )  
  193.                 {  
  194.                     Sleep( 10 );  
  195.                     nCount++;  
  196.                     continue;  
  197.                 }  
  198.             }  
  199.             return ret;  
  200.         }  
  201.         nCount    =    0;  
  202.    
  203.         nLeft    -= ret;  
  204.         idx        += ret;  
  205.    
  206.         if( EndMark != NULL && idx>5)  
  207.         {  
  208.             if( strstr(buf+(idx-5),EndMark) != NULL )  
  209.             {break;}  
  210.         }  
  211.     }  
  212.     return idx;  
  213. }  
  214. #pragma endregion  
  215.    
  216. #pragma region NetSend函数  
  217. /////////////////////////////////////////////////////////////////////  
  218. //函数名:NetSend  
  219. //功能:用指定的SOCKET发送数据  
  220. //参数:sock:发送端套接字  
  221. //      buf:用来存放要发送数据的缓冲区  
  222. //      len:发送数据大小  
  223. //      flag:一般设为0  
  224. //      overtime:超时时间  
  225. //      EndMark:结束标记  
  226. //      soonflag:是否立即返回  
  227. //返回值:实际发送数据的字节数  
  228. /////////////////////////////////////////////////////////////////////  
  229. int    TcpTran::NetSend(SOCKET sock, const char *buf, int len, int flag,int overtime)  
  230. {  
  231.     int        ret;  
  232.     int        nLeft = len;  
  233.     int        idx     = 0;  
  234.    
  235.     fd_set readfds;  
  236.     struct timeval  timeout;  
  237.     timeout.tv_sec = 0;  
  238.     timeout.tv_usec = 500;  
  239.     DWORD s_time = GetTickCount();  
  240.    
  241.     while ( nLeft > 0 )  
  242.     {  
  243.         //向对话框发送关闭消息  
  244.         MSG msg;  
  245.         PeekMessage(&msg, NULL,  0, 0, PM_REMOVE) ;  
  246.         if(msg.message == WM_QUIT)  
  247.             return 0;  
  248.    
  249.         FD_ZERO( &readfds );  
  250.         FD_SET( sock , &readfds );  
  251.    
  252.         int errorret   = select( 0 , NULL, &readfds, NULL , &timeout );  
  253.    
  254.         if( errorret == SOCKET_ERROR )  
  255.         {  
  256.             OutputDebugString(L"mysendEx SOCKET 错误");  
  257.             return SOCKET_ERROR;  
  258.         }  
  259.    
  260.         //计算当前时间  
  261.         DWORD e_time = GetTickCount( );  
  262.         if  ( !FD_ISSET( sock , &readfds ) )  
  263.         {  
  264.             //如果超时,返回  
  265.             if( e_time - s_time > overtime*1000)   
  266.             {  
  267.                 OutputDebugString(L"mysendEx发送数据超时");  
  268.                 return 0;  
  269.             }  
  270.             else  
  271.             {    //OutputDebugString("发送数据FD_ISSET 超时");  
  272.                 continue;  
  273.             }  
  274.         }  
  275.    
  276.         ret = send( sock, &buf[idx], nLeft, flag );  
  277.    
  278.         if ( ret <= 0 )  
  279.         {return ret;}  
  280.    
  281.         nLeft    -= ret;  
  282.         idx        += ret;  
  283.     }  
  284.     return len;  
  285. }  
  286. #pragma endregion  

[cpp] view plain copy
  1. // TcpTran.h: interface for the TcpTran class.  
  2. //  
  3. //////////////////////////////////////////////////////////////////////  
  4. #if !defined TCPTRAN_H  
  5. #define TCPTRAN_H  
  6.   
  7. #if _MSC_VER > 1000  
  8. #pragma once  
  9. #endif // _MSC_VER > 1000  
  10.   
  11. #define SOCKETBIND  1      //服务器端监听端口等待客户端来连接通信方式  
  12. #define SOCKETNOBIND 2     //服务器端主动连接客户端通信方式  
  13. #define SOCKET_TIMEOUT -100//套接字超时  
  14.   
  15. #include "winsock2.h"  
  16. #pragma comment (lib,"ws2_32.lib")  
  17. #include <string>  
  18. using namespace std;  
  19.   
  20. #pragma region TcpTran类  
  21. class TcpTran    
  22. {  
  23. public:  
  24.     TcpTran();         //构造函数  
  25.     virtual ~TcpTran();//析构函数  
  26.   
  27. public:  
  28.     static BOOL InitSocketLib(int lowver,int higver );//初始化Winsock API连接库文件  
  29.   
  30. public:  
  31.     // 初始化Socket函数  
  32.     SOCKET  InitSocket( int SocketType, string strBindIp,u_short BindPort,int opt);  
  33.     // 针对本地监听的处理函数  
  34.     SOCKET  NetAccept(SOCKET s,struct sockaddr* addr,int* addrlen);  
  35.     // 向服务器/客户端发送数据/命令  
  36.     int     NetSend(SOCKET sock, const char *buf, int len, int flag,int overtime);  
  37.     // 接收从客户端/服务器端发来的数据/命令  
  38.     int     NetRecv(SOCKET sock, char *buf, int len, int flag , int overtime,char*EndMark,BOOL soonflag=FALSE);   
  39.       
  40. private:  
  41.     SOCKET m_Socket;//私有套接字成员变量   
  42. };  
  43. #pragma endregion  
  44.   
  45. #endif //TCPTRAN_H~  

注意:e_time - s_time 是DWORD, typedef unsigned long DWORD; ,overtime*1000 是int , 所以两边不一致, 类型不一样,只是警告。改为:
 e_time - s_time > (DWORD)(overtime*1000) 

            3、感受

            (1)我认为的错误:

                      在函数NetRecv中,对于当中用到的recv函数,当输入的准备接收的数据大于实际接收的数据的时候,程序会卡死,一直到时间超时,然后返回无效。我则在recv后面加了个判断。if(ret < len){return ret;//当请求接收的数据长度大于实际接收的数据的时候,说明数据已经接收结束(写这个的时候,突然想起,如果多接收几次,那么返回的肯定就不是ret了,应该是 ( (接收次数 - 1) * len + ret)了,这个逻辑还得去修改下}

                      另外就是,参数overtime的传递,容易传递0,以为是设置时间代码自己管理。但是明显这个程序中,时间不能这样,不然会永远返回超时。

             (2)今天处理程序遇到的一个问题

             需要实现的一个问题:用MFC实现服务端接收多个连接请求,并且保证服务端界面可移动。

             我开始采取的方式:

[cpp] view plain copy
  1. void ClickBtn()  
  2. {  
  3.     socket = InitSocket();  
  4.     unsigned ret;  
  5.     _beginthreadex(0,0,ServerReceivingThrd,(void*)(&socket),0,&ret);  
  6. }  
  7.   
  8. unsigned __stdcall SocketManager::ServerReceivingThrd(LPVOID lpParam)  
  9. {  
  10.     SOCKET so = *((SOCKET*)lpParam);  
  11.     while(1)  
  12.     {  
  13.         SOCKET serverSocket = accept();  
  14.         if(serverSocket == INVALID_SOCKET)  
  15.         {  
  16.             continue;  
  17.         }  
  18.         unsigned ret;  
  19.         _beginthreadex(0,0,ServerReceivingThrd,(void*)(&serverSocket),0,&ret);  
  20.     }  
  21.       
  22. }  
  23.   
  24. unsigned __stdcall  SocketManager::ServerReceivingLoop(void* a)   
  25. {  
  26.     while(1)  
  27.     {  
  28.         recv();  
  29.         //处理recv后的数据  
  30.     }  
  31. }  

    但是,发现accept总是无法正常接收到连接。

              首先,我测试在ClickBtn中不用_beginthreadex(0,0,ServerReceivingThrd,(void*)(&socket),0,&ret);来调用ServerRecvivingThrd函数,而是直接调用该函数。发现accept都能正常接收连接。

             然后,我发现,在函数ServerReceivingThrd中接收的so在accept使用的时候,会变。我不懂为什么新建线程,这个so会改变。线程函数_beginthreadex不是资源都是独立的吗?求解释。所以,后来就将InitSocket从ClickBtn函数中调到ServerReceving的while(1)前面。发现,accept能够正常接收连接,but why?而我暂时认为对于函数_beginthreadex函数是在创建线程之后的该线程对应的资源是独立的。而传递过来的socket不是这个线程创建,而是主线程(此处指点击按钮后执行的线程)创建,所以可能在主线程处理结束之后,资源被释放回收了。所以导致那个socket会变。

             那么如果是这样,我就有必要要创建一个关于线程与当前socket对应关系的管理了。在客户端发送数据的时候肯定也需要用到。但是,怎么做?先找找网上的教程。


三、对Socket接收函数的一个整理

           对Socket接收函数,没有处理过大的,如几十G的数据,所以对于有些大牛封装成头部+数据+尾部的结构(说的更复杂),没有太明白。我对数据大的处理方式的理解是:第一次发送数据大小,后面发送要处理的数据。但这里主要针对的是发送的数据不是特别大(几M左右),但是又不想处理成先接收数据大小后接收数据的流程,采用的一种比较认为不好的办法(以前参考别人的),即进行类存的realloc。以防以后需要,特整理。代码类似如下:

[cpp] view plain copy
  1. while(1)  
  2. {  
  3.     num = recv(*phttpsock , recvbuf , MAXBUF , 0);  
  4.     if(MAXBUF == num)  
  5.     {  
  6.         //如果获得了数据,重新分配一块空间  
  7.         bufForDecode = (char *)realloc(bufForDecode, (size+num) * sizeof(char));  
  8.         if(!bufForDecode)  
  9.         {  
  10.             MessageBox("内存分配失败");  
  11.             exit(0);  
  12.         }  
  13.         memcpy((char*)(size+bufForDecode), recvbuf, num);  
  14.         size+=num;  
  15.         memset(recvbuf , 0 ,MAXBUF);  
  16.     //  Sleep(1000);  
  17.     }  
  18.     else if(num < 0 || num == 0)  
  19.     {  
  20.         break;  
  21.     }  
  22.     else  
  23.     {  
  24.         //如果获得了数据,重新分配一块空间,且知道这块数据比最大分配的要小  
  25.         bufForDecode = (char *)realloc(bufForDecode, (size+num) * sizeof(char));  
  26.         memcpy((char*)(size+bufForDecode), recvbuf, num);  
  27.         size+=num;  
  28.         memset(recvbuf , 0 ,MAXBUF);  
  29.         Sleep(1000);  
  30.         break;  
  31.     }  
  32. }  
                 其原理就是:接收数据,重新分配内存。


四、对Socket协议封装的一个整理

            对于socket的send和recv函数。喜欢采用传递一个结构体指针的方式传递数据。不得不承认,比较好用。但是感觉也存在一个问题,就是这个结构体会因为应用的不同而需要进行修改。而我希望能够针对所有的数据都能够进行一个通用的办法。

             偶然看到一个这样的处理方式:

[cpp] view plain copy
  1. class Package  
  2. {  
  3.     char* m_p;  
  4.     int m_size;  
  5. public:  
  6.     Package( )   
  7.     {  
  8.         m_size = 0;  
  9.         m_p = new char[1024];  
  10.     }  
  11.     Package& operator<<(int i)  
  12.     {  
  13.         memcpy(m_p+m_size,(char*)&i,sizeof(i));  
  14.         m_size+=sizeof(int);  
  15.         return *this;  
  16.     }  
  17.   
  18.     Package& operator<<(string s)  
  19.     {  
  20.         int n = s.size();  
  21.         memcpy(m_p+m_size,(char*)&n,sizeof(int));  
  22.         m_size+=sizeof(int);  
  23.         memcpy(m_p+m_size,s.c_str(),n);  
  24.         m_size+=n;  
  25.         return *this;  
  26.     }  
  27.   
  28.     Package& operator>>(int& i)  
  29.     {  
  30.         memcpy((char*)&i,m_p+m_size,sizeof(i));  
  31.         m_size+=sizeof(int);  
  32.         return *this;  
  33.     }  
  34. };  
  35.   
  36. void Test  
  37. {  
  38.     Package pack;  
  39.     int i = 300;  
  40.     string s = "good";  
  41.     double d = 3.14;  
  42.     pack<< i << s << d;  
  43.     s.send(pack); //但是此处无法从Package转换为char*。我在想是否可以这样s.send((char*)&pack);还未验证  
  44.     //recv  
  45.     Package pack2;  
  46.     s.recv(pack2);  
  47.     int i2;  
  48.     string s2;  
  49.     double  d2;  
  50.     pack>>i2>>s2>>d2;  //其没有封装>>string的。此处貌似会报错。我在想如果将数据放到最后,直接返回this指针应该也可以。  
  51.   
  52. };  

0 0