SOCKET常用的函数和开发模式(2)

想不到我要打这个文章两次,刚才对自己的电脑太自信,直接在网页上写,果然系统死机了。不得不重复的写这个文章。
我开始佩服那些写blog成千上百万次点击率的达人们,因为要坚持做一件事情很难,尤其是你想讲这件事情做好更难,这个第二篇我写的时间已经是2008年了。上一篇写的是windows socket的使用方法,这个我将写一下windows socket的开发模式,.
使用windows socket进行通信时,个人认为无非分为三种情况
:一对一,多队一,多对多。对于“一对一”这种情况而言,应用程序只需要一个端口对应另外一个对应的端口,是所有开发模式中最简单的一个,所以也无所谓任何的开发模式,暂时不提(当然具体的应用可能和后面提及到的开发模式有关系)。对于“多对一”和“多对多”两种模式来说,应用程序都需要管理多个端口的通信,所以两种的开发模式基本上是一致的。具体应用程序采用“一对一”还是“多对一”,由具体的应用场景来决定的,例如防火墙穿透,端口范围限制等等。下面以“多对一”来介绍一种比较实用安全的开发模式。
1.当系统需要控制多个端口时,最好的做法是用select函数来操作,select函数可以用于判断端口的数据状态.需要一个线程安全的数组来统一管理所有需要监听的端口,然后利用结构fd_set来操作.
 在 程序中负责socket通信的需要有两个线程,一个读线程-用于接受数据,一个写线程-用于发送数据.为了保证
 数据的安全性,还需要有两个线程安全的缓冲区,一个用于将从端口读取到数据存放起来交由应用层来进行处理,减少阻塞一个用于应用层需要发送数据的存放(那么在外部的应用层对应的存在两个操作或者说线程). 给出一个具体的例子程序来说明

void UDPTransfer::doRead()                //接收数据线程函数
{
 int wait_fd ;
 fd_set read_fds;
 struct timeval tv;

 while(_RecData->Running() )
 {
  {
   AutoLock pLock(&m_lock);            //自定义的锁,用于线程安全,下同
   wait_fd = _build_fds(&read_fds);  //建立对应的fd_set
  }

  if( Sock_Wait(wait_fd,&read_fds,0,&tv) <= 0)  //select函数判断,是否存在需要读取的数据
   continue;

  ReadData(&read_fds);
 }
}

int UDPTransfer::_build_fds(fd_set *fds)
{
 int maxfd = -1;
 memset(fds,0,sizeof(fd_set));
 {
  AutoLock pLock(&m_lock);
  for(int i= 0;i<FD_SETSIZE;i++)          //注意FD_SETSIZE这个宏
  {
   if( m_sockToMGArray[i] <= 0 )
    continue;
   int socketID =  m_sockToMGArray[i];
   if(socketID > maxfd)
    maxfd = socketID;
   FD_SET(m_sockToMGArray[i],fds);
  }
 }
 return maxfd;
}

int UDPTransfer::Sock_Wait(int maxfd,fd_set * readfds,fd_set * writefds,struct timeval * ptv)
{
 if( maxfd < 0 )
 {
  if( readfds )
   Thread_Sleep(100);    //自定义线程sleep函数
  return 0;
 }
 ptv->tv_sec = 0;
 ptv->tv_usec = 100 *1000; //100ms  设定阻塞的时间
 return select(maxfd+1,readfds,writefds,0,ptv);
}

int UDPTransfer::ReadData(fd_set *fds)
{
 SOCKET sock;
 int len = 0;
 for( int i=0;i<FD_SETSIZE;i++ )
 {
  {
   AutoLock pLock(&m_lock);
   sock = m_sockToMGArray[i];
  }
  if(sock <= 0)
   continue;
  if( !FD_ISSET(sock,fds) )
   continue;
  len =0;
  memset(__RecBuf,0,sizeof(__RecBuf));
  len = recv(sock,__RecBuf,sizeof(__RecBuf),0);
  if (len>0)
  {
   _pLog->writeLog(5,"UDP Receive Data from Socket[%d],size=[%d]",sock,len);
   _pManager->Ondata(sock,__RecBuf,len);        //将数据放入缓存区
  }
 }
 return 1;
}

上述的运行原理,应该比较清楚了,将所有需要监听socket用m_sockToMGArray来管理(如果不要继续监听了,将对应的socket从m_sockToMGArray中移出,所以需要保证m_sockToMGArray的线程安全性)然后建立对应的fd_set,然后调用select函数来检测所有监听端口的状态.如果有数据到达的端口,则读取,将读取的数据放入缓存区交由应用层来处理.
注意FD_SETSIZE在头文件winsock2.h中定义为64,默认一个fd_set,能够支持64个端口,可以直接修改头文件将这个字修改为更大,但是不建议这么做.如果出现了需要监听socket>64的情况,就新建一对,发送和接收的线程,然后由一个总的线程来管理,这就是所谓的线程池的概念,根据需要可以共享发送和接收的缓冲区.
特别的,因为阻塞方式的connect函数,在网络状态比较差的情况(例如经过VPN等),会等待甚至达到30秒.那么需要在作连接操作的时候作好超时保护.

写tech-blog真的好辛苦的事情,每次不希望自己在敷衍,所以用心写,发现耗时很多.只要自己某一天忘记了一下,权当电子记事本好了. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值