关于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指针应该也可以。};
- 关于socket的整理
- 关于socket的整理
- 关于SOCKET编程的一些整理!
- 关于socket与网络协议的整理
- 关于socket的理解
- 关于socket的困惑
- 关于socket的几个问题
- 关于socket 的accept()
- 关于随机的整理
- 关于iReport的整理
- 关于randn的整理
- 关于CoreData的整理
- 关于Pods的整理
- 关于synchronized的整理
- 关于CSS的整理
- 关于集合的整理
- 关于字符串的整理
- 关于异常的整理
- VC操作XML相关知识
- Android通过代码打开和关闭网络连接
- 浅谈C/C++的浮点数在内存中的存储方式
- oracle pl/sql level妙用
- 西门子PLC S7200远程变量读写
- 关于socket的整理
- android.os.NetworkOnMainThreadException
- Python抓取网页&批量下载文件方法初探(正则表达式+BeautifulSoup)
- 区域填充纹理
- Shell I/O Redirect
- OpenCV学习笔记之--运动物体跟踪的camshift算法
- ListView的item点击显示隐藏的菜单
- ColdFusion 9 / 10 Remote Root xday
- openstack nova 基础知识——libvirt和qemu