关于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、代码
-
-
-
- #include "stdafx.h"
- #include "TcpTran.h"
-
- #pragma region TcpTran类
-
-
-
-
-
- TcpTran::TcpTran()
- {
-
- m_Socket = INVALID_SOCKET;
- }
-
- TcpTran::~TcpTran(){}
- #pragma endregion
-
- #pragma region InitSocketLib函数
-
-
-
-
-
-
- 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函数
-
-
-
-
-
-
-
-
-
- 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;
-
- if( strBindIp.empty() )
- {
-
- sockStruct.sin_addr.S_un.S_addr = INADDR_ANY;
- }
- else
- {
-
- sockStruct.sin_addr.S_un.S_addr = inet_addr(strBindIp.c_str());
- }
-
-
- sockStruct.sin_port = htons(BindPort);
-
- if( SocketType == SOCKETNOBIND )
- {
- if(connect(socketid,(LPSOCKADDR)&sockStruct,sizeof(sockStruct)) == SOCKET_ERROR)
- {
-
- 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
- {
- 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函数
-
-
-
-
-
-
-
-
-
-
-
-
- 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 );
- FD_SET( sock , &readfds );
-
- 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函数
-
-
-
-
-
-
-
-
-
-
-
-
- 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
- {
- continue;
- }
- }
-
- ret = send( sock, &buf[idx], nLeft, flag );
-
- if ( ret <= 0 )
- {return ret;}
-
- nLeft -= ret;
- idx += ret;
- }
- return len;
- }
- #pragma endregion
-
-
-
- #if !defined TCPTRAN_H
- #define TCPTRAN_H
-
- #if _MSC_VER > 1000
- #pragma once
- #endif // _MSC_VER > 1000
-
- #define SOCKETBIND 1 //服务器端监听端口等待客户端来连接通信方式
- #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 );
-
- public:
-
- SOCKET InitSocket( int SocketType, string strBindIp,u_short BindPort,int opt);
-
- SOCKET NetAccept(SOCKET s,struct sockaddr* addr,int* addrlen);
-
- int NetSend(SOCKET sock, const char *buf, int len, int flag,int overtime);
-
- int NetRecv(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();
-
- }
- }
但是,发现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);
-
- }
- 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 pack2;
- s.recv(pack2);
- int i2;
- string s2;
- double d2;
- pack>>i2>>s2>>d2;
-
- };
0 0