面向连接的tcp/ip流程图

面向连接的tcp/ip流程图

在其中,要注意的是:

  1.关于服务端在客户端连接之前

      如果没有客户端连接时,在调用accept()时程序将会出现freeze,即阻塞,而一旦有客户端连接过来,accept将会新建一Socket与客户端的Socket相通,原先Socket继续进入监听状态,等待他人的连接要求。
     该函数调用成功返回一个新产生的Socket对象,否则返回INVALID_SOCKET,该新socket就是一个和一个客户端对话的套接字。有点类似孙悟空对阵天兵天将时,当遇到某个具体敌手来,拔出一根毫毛变出一个孙行者去应付,再来再拔毫毛一样。accept()定义如下:
SOCKET PASCAL FAR accept( SCOKET s, struct sockaddr FAR *addr,int FAR *addrlen );
s:Socket的识别码;
addr:存放来连接的客户端的地址;
addrlen:addr的长度

  当客户端连接上后一直没有断开情况下,如果连接越来越多时,则创建的Socket也越多,其最大上限在listen中已经设置。

2.关于服务端的accept()使用

  accept过后才是真正的和客户端进行交互,在accept时,由于程序会freeze,在调用accept时有多种方法,其中方法有:

*事件处理模式:

     通过WSAAsyncSelect()函数,其异步通知有accept信号来,然后在一个窗体自定义事件中处理accept信号。   

   如下在listen()之后调用:

        WSAAsyncSelect(m_hSocket, m_hWnd, WM_CLIENT_ACCEPT,FD_ACCEPT);  //wm_xxx_xxz自定义消息。

  这样在构建的自定义消息中处理accept()连接请求。如下,OnAccept()单元

       LRESULT CPublicNetSoftDlg::OnAccept(WPARAM wParam,LPARAM lParam)
       {

             。。。。
               if(WSAGETSELECTEVENT(lParam) == FD_ACCEPT)//如果
                {
                         Client = accept(ServerSocket,(LPSOCKADDR)&m_sockServerAddr,0);

                        if (Client == INVALID_SOCKET) 
                            return 0L;
                }

            。。。。。
    }

*线程处理模式:将accept放在线程中让其freeze,一旦来了连接,则自然从freeze中出来进行处理下一步。下面就是直接把accetp放在线程中处于等待状态。

//连接请求队列长度为1,即只允许有一个请求,若有多个请求, 则出现错误,给出错误代码WSAECONNREFUSED。
listen(sock,1);
//开启线程避免主程序的阻塞
AfxBeginThread(Server,NULL);
……

//处理线程,等待客户端连接
UINT Server(LPVOID lpVoid)
{
……
   int nLen = sizeof(SOCKADDR);
   connSocket = accept(ListSocket,(LPSOCKADDR)& sockin,(LPINT)& nLen);
   ……
    WSAAsyncSelect(connSocket,
                 m_hWnd,
                 WM_SOCKET_MSG,
                 FD_READ|FD_CLOSE);
    return 1;
}
  把accept()放到线程中去是因为在执行到该函数时如没有客户连接请求到来,服务器就会停在accept语句上处于等待阻塞,这势必会引起进程的阻塞,虽然也可以通过设置套接字为非阻塞方式使在没有客户等待时可以使accept()函数调用立即返回,但这种轮询套接字的方式会使CPU处于忙等待方式,从而降低程序的运行效率大大浪费系统资源(我觉得做法很多,暂不考虑非阻塞情况)。
      在阻塞工作方式,为其单独开辟一个子线程,将其阻塞控制在子线程范围内而不会造成整个应用程序的阻塞。对于网络事件的响应显然要采取异步选择机制,只有采取这种方式才可以在由网络对方所引起的不可预知的网络事件发生时能马上在进程中做出及时的响应处理,而在没有网络事件到达时则可以处理其他事件,这种效率是很高的。前面那段代码中的WSAAsyncSelect()函数便是实现网络事件异步选择的核心函数。
    第4个参数注册应用程序关心的网络事件,在这里通过FD_READ|FD_CLOSE指定了网络读和网络断开两种事件,当这种事件发生时变会发出由第三个参数指定的自定义消息 WM_SOCKET_MSG,接收该消息的窗口通过第二个参数指定其句柄。

其响应函数如下:

void CNetServerView::OnSocket(WPARAM wParam,LPARAM lParam)
{
    int iReadLen=0;
   int message=lParam & 0x0000FFFF;
   switch(message)
   {
     case FD_READ:     //读事件发生。此时有字符到达,需要进行接收处理
        char cDataBuffer[MTU*10];
        //通过套接字接收信息
        iReadLen = recv(newskt,cDataBuffer,MTU*10,0);
       //将信息保存到文件
        if(!file.Open("ServerFile.txt",CFile::modeReadWrite))
             file.Open("E:ServerFile.txt",
                 CFile::modeCreate|CFile::modeReadWrite);
         file.SeekToEnd();
         file.Write(cDataBuffer,iReadLen);
         file.Close();
         break;
         case FD_CLOSE://网络断开事件发生。此时客户机关闭或退出。
             ……//进行相应的处理
                break;
         default:
               break;
      }
}

   对于recv和send的处理一般就是在事件中处理,通过WSAAsySelect()来传递信号,这种方式形成一种固定写socket方式,比如有的人喜欢把recv和send各自放入一个线程中通过轮询+阻塞模式,或者采用事件通知模式,一般来说采用I/O模型是较为专业的做法。

3.客户端的connect()

    客户端连接时存在阻塞现象,就是程序在connect会出现freeze,一般可以容忍。但若想通过超时设置来解决这个问题,可采用在vckbase中,对于connect()超时的处理办法。不过觉得有时调用封装好的socket,直接是指connectionTimeout属性倒是简单的方法。

WSADATA wsd;
SOCKET cClient;
int ret;
struct sockaddr_in server;
hostent *host=NULL;

if(WSAStartup(MAKEWORD(2,0),&wsd))

{

   return 0;

}
cClient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(cClient == INVALID_SOCKET){return 0;}
//set Recv and Send time out
int TimeOut=6000; //设置发送超时6秒
if(::setsockopt(cClient,SOL_SOCKET,SO_SNDTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR){
     return 0;
}
TimeOut = 6000;//设置接收超时6秒
if(::setsockopt(cClient,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR){
return 0;
}
//设置非阻塞方式连接
unsigned long ul = 1;
ret = ioctlsocket(cClient, FIONBIO, (unsigned long*)&ul);
if(ret==SOCKET_ERROR)  return 0;

//连接
server.sin_family = AF_INET;
server.sin_port = htons(25);
server.sin_addr .s_addr = inet_addr((LPCSTR)pSmtp);
if(server.sin_addr.s_addr == INADDR_NONE){return 0;}

//运行这里将不会阻塞,而是直接运行下去,通过select中设置的 timeval结构参数设定连接超时处理。

connect(cClient,(const struct sockaddr *)&server,sizeof(server));

//select 模型,即设置超时
struct timeval timeout ;
fd_set r;

FD_ZERO(&r);
FD_SET(cClient, &r);
timeout.tv_sec = 15; //连接超时15秒
timeout.tv_usec =0;
ret = select(0, 0, &r, 0, &timeout);            //超时socket将关闭
if ( ret <= 0 )
{
         ::closesocket(cClient);
          return 0;
}
//一般非锁定模式套接比较难控制,可以根据实际情况考虑 再设回阻塞模式,又把状态设置为 阻塞状态。
unsigned long ul1= 0 ;
ret = ioctlsocket(cClient, FIONBIO, (unsigned long*)&ul1);
if(ret==SOCKET_ERROR){
         ::closesocket (cClient);
          return 0;
}

 

4.select()

a. 当你希望服务器监听连接服务请求,而又不想通过轮询的方式,则理想的方式是调用select().它运行你把程序本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现了活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了程序的选出是随机变化的,而不必由程序本身对输入进行测试而浪费cpu开销,

    在socket编程中,select函数一般在非阻塞的socket中,用来检查socket缓冲区中是否有数据可读,或是否可以写数据到socket缓冲区。  
  有时,select()也被用来当作延时函数使用。sleep()延时会释放cpu,用select的话,可以在占用cpu的情况下,延时。

   select()是用来进行多路转接的函数。它可以同时等待n(n大于等于1)个文件描述字或者socket套接口。只要它等待的任意描述字准备好或者等待时间超过了设定时间程序就往下执行。可以防止进程长时间阻塞,占用资源。

b.简单说法:

  如果你要发数据用select(sock+1,&s,NULL,NULL,NULL);  
     if(FD_ISSET(sock,&s)   ,你可以发了。send   it  
  如果你要收数据用select(sock+1,NULL,&s,NULL,NULL);  
  if(FD_ISSET(sock,&s)   ,你可以收了。recv   it  

socket默认情况下是阻塞的,除非你用WSAAsyncSelect   OR   select   就变成NOBBLOCKING,  
  将阻塞设为非阻塞如下:  
  int   opt=1;  
  ioctlsocket(sock,FIONBIO,&opt)  
  若opt=0就是阻塞的了

 

c.用法一

  fd_set m_readfds;  
  fd_set m_exceptfds;  
  timeval m_tmOut;  
  m_tmOut.tv_sec =   120;     //接收时间如果超过120秒,即认为网络连接已经中断,  
  m_tmOut.tv_usec =   0;      //客户端应该定时每40秒发送一次空闲信号,以防止被误认为是网络连接中断。  
  FD_ZERO(   &m_readfds   );  
  FD_ZERO(   &m_exceptfds   );  
  FD_SET(   m_scSocket,   &m_exceptfds   );  
  FD_SET(   m_scSocket,   &m_readfds   );  
  int CNet::Receive(   char   *   szBuff,   int   iSize   )  
  {  
        int   iRet;  
        if(   m_ntType   ==   _NET_SERVER_   )  
        {  
              iRet   =   select(   m_scSocket   +   1,   &m_readfds,   NULL,   &m_exceptfds,   &m_tmOut   );  
              if(   iRet   ==   0   )  
             {  
                   m_iError =   13; //超时  
                   return   -2;  
             }  
             if(   iRet   ==   SOCKET_ERROR   )  
            {  
                 GetLastError(   );  
                  return   -1;  
            }  
            if(   FD_ISSET(   m_scSocket,   &m_exceptfds   )   )  
            {  
                  m_iError =   14; //连接被终止  
                  return   -1;  
           }  
      }  
      iRet   =   recv(   m_scSocket,   szBuff,   iSize,   0   );  
      if(   iRet   ==   0   )  
     {  
               m_iError =   14; //连接被终止  
              return   -1;  
    }  
   if(   iRet   ==   SOCKET_ERROR   )  
   {  
          GetLastError(   );  
          return   -1;  
   }  
    return   iRet;  
  }  

用法二

int   recvex_sock(SOCKET   sock,   void*   buf,   int   len,   int   sec)  
  {  
                  int   rs;  
                  fd_set   fd;  
                  struct   timeval   tv;  
                  memset(&tv,   0,   sizeof(tv));  
                  if   (   sec   >   0   )  
                                  tv.tv_sec   =   sec;  
                  FD_ZERO(&fd);  
                  FD_SET(sock,   &fd);  
                  rs   =   select(sock   +   1,   &fd,   0,   0,   sec   >=   0   ?   &tv   :   NULL);  
                  if   (   rs   ==   0   )  
                                  return   SOCKET_TIMEOUT;  
                  if   (   rs   <   0   )  
                                  return   SOCKET_ERROR;  
                  if   (   !FD_ISSET(sock,   &fd)   )  
                                  return   SOCKET_ERROR;  
                  return   (recv_sock(sock,   buf,   len));  
  } 

你可能感兴趣的:(面向连接的tcp/ip流程图)