关于socket的整理

来源:互联网 发布:乐易编程网 编辑:程序博客网 时间:2024/05/18 18:45

              对于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、代码

// TcpTran.cpp: implementation of the TcpTran class.////////////////////////////////////////////////////////////////////////#include "stdafx.h"#include "TcpTran.h" #pragma region TcpTran类///////////////////////////////////////////////////////////////////////类名:TcpTran//功能:完成TCP通信///////////////////////////////////////////////////////////////////////构造函数TcpTran::TcpTran(){    //将套接字初始化为无效    m_Socket = INVALID_SOCKET;}//析构函数TcpTran::~TcpTran(){}#pragma endregion #pragma region InitSocketLib函数///////////////////////////////////////////////////////////////////////函数名:TcpTran//功能:初始化SOCKET通信库,要求Winsockt 2//参数:lowver版本的低位;higver版本的高位//返回值:TRUE表示成功,FALSE表示失败/////////////////////////////////////////////////////////////////////BOOL TcpTran::InitSocketLib(int lowver,int higver ){    WORD wVersion =0 ;    int     errret = -1;    WSADATA wsaData;     wVersion = MAKEWORD(lowver,higver);    errret = WSAStartup(wVersion,&wsaData);     if( LOBYTE( wsaData.wVersion) != 2 ||        HIBYTE( wsaData.wVersion) !=2 )    {        MessageBox(NULL,L"winsocket库版本低",L"提示",MB_OK);        return FALSE;    }    return TRUE;}#pragma endregion #pragma region InitSocket函数///////////////////////////////////////////////////////////////////////函数名:InitSocket//功能:根据类型初始化SOCKET资源//参数:SocketType:SOCKETBIND/SOCKETNOBIND 绑定/不绑定本地端口;//      strBindIp:要绑定的IP地址//      BindPort:要绑定的本地端口,若为0表示系统自动产生//      opt:是否支持端口重用//返回值:错误:INVALID_SOCKET;正确:返回可用的SOCKET/////////////////////////////////////////////////////////////////////SOCKET    TcpTran::InitSocket( int SocketType, string strBindIp,u_short BindPort,int opt){    SOCKET socketid = INVALID_SOCKET;    //创建一个能进行网络通信的套接字    socketid = socket(PF_INET,SOCK_STREAM,0);    SOCKADDR_IN sockStruct;    sockStruct.sin_family = AF_INET; //使用TCP/IP协议     if( strBindIp.empty() )    {           //strBindIp为空,则用INADDR_ANY表示SOCKET可接收来自任何IP的消息        sockStruct.sin_addr.S_un.S_addr = INADDR_ANY;    }    else    {        //strBindIp不为空,将其转化为char*后,将IP地址从点分十进制转换为无符号长整型        sockStruct.sin_addr.S_un.S_addr = inet_addr(strBindIp.c_str());        }     //将参数BindPort转换为网络字节序后保存    sockStruct.sin_port = htons(BindPort);    //不绑定端口    if( SocketType == SOCKETNOBIND )    {        if(connect(socketid,(LPSOCKADDR)&sockStruct,sizeof(sockStruct)) == SOCKET_ERROR)        {            //OutputDebugString("连接错误!");            closesocket(socketid);            //关闭已打开的套接字,防止占用内存            shutdown(socketid,2);            //置为无效            socketid = INVALID_SOCKET;        }        m_Socket = socketid;    }    //若绑定本地端口    else if( SocketType == SOCKETBIND )    {        if(bind(socketid,(sockaddr*)&sockStruct,sizeof(sockaddr_in)) == SOCKET_ERROR)        {            //绑定失败则关闭套接字            closesocket(socketid);            //置为无效            socketid = INVALID_SOCKET;         }        else        {   //绑定IP和端口成功,监听来自网络的连接队列。SOMAXCONN指明最大连接数            if( listen(socketid,SOMAXCONN) == SOCKET_ERROR )            {                //监听失败则关闭套接字                closesocket(socketid);                //置为无效                socketid = INVALID_SOCKET;            }        }        m_Socket = socketid;    }    return socketid;}    #pragma endregion #pragma region NetAccept函数SOCKET    TcpTran::NetAccept(SOCKET s,struct sockaddr* addr,int* addrlen){    SOCKET accpsocket  = INVALID_SOCKET;    accpsocket = accept(s,addr,addrlen);    return accpsocket;}#pragma endregion #pragma region NetRecv函数///////////////////////////////////////////////////////////////////////函数名:NetRecv//功能:根据类型初始化SOCKET资源//参数:sock:接收端套接字//      buf:用来存放接收到的数据的缓冲区//      len:接收数据大小//      flag:一般设为0//      overtime:超时时间//      EndMark:结束标记//      soonflag:是否立即返回//返回值:接收到数据的字节数/////////////////////////////////////////////////////////////////////int TcpTran::NetRecv(SOCKET sock, char *buf, int len, int flag , int overtime ,char*EndMark,BOOL soonflag){    int        ret;    int        nLeft = len;    int        idx     = 0;    int        nCount = 0;    fd_set readfds;               //文件描述符    struct timeval  timeout;    timeout.tv_sec = 0;           //设置超时值    timeout.tv_usec = 500;    DWORD s_time = GetTickCount();//返回从操作系统启动到现在经过的毫秒数     while ( nLeft > 0 )    {        //接收消息        MSG msg;        PeekMessage(&msg, NULL,  0, 0, PM_REMOVE) ;        if(msg.message == WM_QUIT)        {return 0;}         FD_ZERO( &readfds );       //将set清零        FD_SET( sock , &readfds ); //将fd加入set        //select管理套接字IO,避免出现无辜锁定        if( select( 0 , &readfds , NULL , NULL , &timeout ) == SOCKET_ERROR )        {return SOCKET_ERROR;}         DWORD e_time = GetTickCount( );        if  ( !FD_ISSET( sock , &readfds ) )        {            if(e_time - s_time > overtime*1000 ) //超时                return SOCKET_TIMEOUT;            else                continue;        }         ret = recv( sock, &buf[idx], nLeft, flag ); //接收数据         if( soonflag == TRUE )        {    return ret;    }         s_time = e_time ; // 只要有数据就重新置初始时间值         if ( ret <= 0 )        {            //错误处理            int        LastError = GetLastError();            if ( ( -1 == ret ) && ( WSAETIMEDOUT      == LastError ) )                continue;            if ( ( -1 == ret ) && ( WSAEWOULDBLOCK      == LastError ) )            {                if ( nCount < 2000 )                {                    Sleep( 10 );                    nCount++;                    continue;                }            }            return ret;        }        nCount    =    0;         nLeft    -= ret;        idx        += ret;         if( EndMark != NULL && idx>5)        {            if( strstr(buf+(idx-5),EndMark) != NULL )            {break;}        }    }    return idx;}#pragma endregion #pragma region NetSend函数///////////////////////////////////////////////////////////////////////函数名:NetSend//功能:用指定的SOCKET发送数据//参数:sock:发送端套接字//      buf:用来存放要发送数据的缓冲区//      len:发送数据大小//      flag:一般设为0//      overtime:超时时间//      EndMark:结束标记//      soonflag:是否立即返回//返回值:实际发送数据的字节数/////////////////////////////////////////////////////////////////////int    TcpTran::NetSend(SOCKET sock, const char *buf, int len, int flag,int overtime){    int        ret;    int        nLeft = len;    int        idx     = 0;     fd_set readfds;    struct timeval  timeout;    timeout.tv_sec = 0;    timeout.tv_usec = 500;    DWORD s_time = GetTickCount();     while ( nLeft > 0 )    {        //向对话框发送关闭消息        MSG msg;        PeekMessage(&msg, NULL,  0, 0, PM_REMOVE) ;        if(msg.message == WM_QUIT)            return 0;         FD_ZERO( &readfds );        FD_SET( sock , &readfds );         int errorret   = select( 0 , NULL, &readfds, NULL , &timeout );         if( errorret == SOCKET_ERROR )        {            OutputDebugString(L"mysendEx SOCKET 错误");            return SOCKET_ERROR;        }         //计算当前时间        DWORD e_time = GetTickCount( );        if  ( !FD_ISSET( sock , &readfds ) )        {            //如果超时,返回            if( e_time - s_time > overtime*1000)             {                OutputDebugString(L"mysendEx发送数据超时");                return 0;            }            else            {    //OutputDebugString("发送数据FD_ISSET 超时");                continue;            }        }         ret = send( sock, &buf[idx], nLeft, flag );         if ( ret <= 0 )        {return ret;}         nLeft    -= ret;        idx        += ret;    }    return len;}#pragma endregion

// TcpTran.h: interface for the TcpTran class.////////////////////////////////////////////////////////////////////////#if !defined TCPTRAN_H#define TCPTRAN_H#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#define SOCKETBIND1      //服务器端监听端口等待客户端来连接通信方式#define SOCKETNOBIND 2     //服务器端主动连接客户端通信方式#define SOCKET_TIMEOUT -100//套接字超时#include "winsock2.h"#pragma comment (lib,"ws2_32.lib")#include <string>using namespace std;#pragma region TcpTran类class TcpTran  {public:TcpTran();         //构造函数virtual ~TcpTran();//析构函数public:static BOOL InitSocketLib(int lowver,int higver );//初始化Winsock API连接库文件public:// 初始化Socket函数SOCKETInitSocket( int SocketType, string strBindIp,u_short BindPort,int opt);// 针对本地监听的处理函数SOCKETNetAccept(SOCKET s,struct sockaddr* addr,int* addrlen);// 向服务器/客户端发送数据/命令intNetSend(SOCKET sock, const char *buf, int len, int flag,int overtime);    // 接收从客户端/服务器端发来的数据/命令intNetRecv(SOCKET sock, char *buf, int len, int flag , int overtime,char*EndMark,BOOL soonflag=FALSE);private:SOCKET m_Socket;//私有套接字成员变量};#pragma endregion#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实现服务端接收多个连接请求,并且保证服务端界面可移动。

             我开始采取的方式:

void ClickBtn(){socket = InitSocket();unsigned ret;_beginthreadex(0,0,ServerReceivingThrd,(void*)(&socket),0,&ret);}unsigned __stdcall SocketManager::ServerReceivingThrd(LPVOID lpParam){SOCKET so = *((SOCKET*)lpParam);while(1){SOCKET serverSocket = accept();if(serverSocket == INVALID_SOCKET){continue;}unsigned ret;_beginthreadex(0,0,ServerReceivingThrd,(void*)(&serverSocket),0,&ret);}}unsigned __stdcall  SocketManager::ServerReceivingLoop(void* a) {while(1){recv();//处理recv后的数据}}

    但是,发现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。以防以后需要,特整理。代码类似如下:

while(1){num = recv(*phttpsock , recvbuf , MAXBUF , 0);if(MAXBUF == num){//如果获得了数据,重新分配一块空间bufForDecode = (char *)realloc(bufForDecode, (size+num) * sizeof(char));if(!bufForDecode){MessageBox("内存分配失败");exit(0);}memcpy((char*)(size+bufForDecode), recvbuf, num);size+=num;memset(recvbuf , 0 ,MAXBUF);//Sleep(1000);}else if(num < 0 || num == 0){break;}else{//如果获得了数据,重新分配一块空间,且知道这块数据比最大分配的要小bufForDecode = (char *)realloc(bufForDecode, (size+num) * sizeof(char));memcpy((char*)(size+bufForDecode), recvbuf, num);size+=num;memset(recvbuf , 0 ,MAXBUF);Sleep(1000);break;}}
                 其原理就是:接收数据,重新分配内存。


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

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

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

class Package{char* m_p;int m_size;public:Package( ) {m_size = 0;m_p = new char[1024];}Package& operator<<(int i){memcpy(m_p+m_size,(char*)&i,sizeof(i));m_size+=sizeof(int);return *this;}Package& operator<<(string s){int n = s.size();memcpy(m_p+m_size,(char*)&n,sizeof(int));m_size+=sizeof(int);memcpy(m_p+m_size,s.c_str(),n);m_size+=n;return *this;}Package& operator>>(int& i){memcpy((char*)&i,m_p+m_size,sizeof(i));m_size+=sizeof(int);return *this;}};void Test{Package pack;int i = 300;string s = "good";double d = 3.14;pack<< i << s << d;s.send(pack); //但是此处无法从Package转换为char*。我在想是否可以这样s.send((char*)&pack);还未验证//recvPackage pack2;s.recv(pack2);int i2;string s2;double  d2;pack>>i2>>s2>>d2;  //其没有封装>>string的。此处貌似会报错。我在想如果将数据放到最后,直接返回this指针应该也可以。};


原创粉丝点击