65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 select() 套接字函数的可扩展客户端/服务器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.23/5 (19投票s)

2007年8月17日

CPOL

2分钟阅读

viewsIcon

130945

downloadIcon

4419

一篇关于使用 select() 函数创建可扩展客户端/服务器应用程序的文章

Screenshot - Client1.jpg

引言

本文是我关于 IOCP 的文章的延续。 在那篇文章中,我演示了使用 I/O 完成端口创建可扩展客户端/服务器应用程序。 在本文中,我将演示如何使用 select() 函数创建可扩展客户端/服务器应用程序。 在此实现中,客户端和服务器发送和显示简单的字符串消息。

select() 函数与同步套接字一起工作,不需要创建线程。

我在这里提供的客户端 clientselect.exe 与我早期文章中的 clientiocp.exe 相同。 我只是将其重命名了。 此客户端的代码可以在我之前的文章中找到。

使用代码

select() 函数允许开发人员将套接字分配到三个不同的集合中,并监控套接字的 state 变化。 我们可以根据套接字的 status 处理套接字。 为套接字创建的三个集合是

  • 读取集合 – 检查此组中的套接字是否可读。 当
    • 监听套接字上存在待处理的连接时
    • 套接字上收到数据时
    • 连接关闭或终止时
  • 写入集合 – 检查此组中的套接字是否可写。 当可以在套接字上发送数据时,套接字将被认为可写
  • 异常集合 – 检查此组中的套接字是否存在错误。

这些集合使用 fd_set 结构实现。 fd_set 的定义可以在 winsock2.h 中找到。

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

以下是可以用来操作集合的宏。 这些宏的源代码可以在 winsock2.h 中找到。

  • FD_CLR – 从集合中移除套接字
  • FD_ISSET – 帮助识别套接字是否属于指定的集合
  • FD_SET – 将套接字分配给指定的集合
  • FD_ZERO – 重置集合

对于此实现,客户端信息将存储在 CClientContext 结构的单链表中。

class CClientContext  //To store and manage client related information

{
private:
     int               m_nTotalBytes;
     int               m_nSentBytes;
     SOCKET            m_Socket;  //accepted socket

     char              m_szBuffer[MAX_BUFFER_LEN];
     CClientContext   *m_pNext; //this will be a singly linked list


public:
     //Get/Set calls

     void SetTotalBytes(int n)
     {
          m_nTotalBytes = n;
     }

     int GetTotalBytes()
     {
          return m_nTotalBytes;
     }

     void SetSentBytes(int n)
     {
          m_nSentBytes = n;
     }

     void IncrSentBytes(int n)
     {
          m_nSentBytes += n;
     }

     int GetSentBytes()
     {
          return m_nSentBytes;
     }

     void SetSocket(SOCKET s)
     {
          m_Socket = s;
     }

     SOCKET GetSocket()
     {
          return m_Socket;
     }

     void SetBuffer(char *szBuffer)
     {
          strcpy(m_szBuffer, szBuffer);
     }

     void GetBuffer(char *szBuffer)
     {
          strcpy(szBuffer, m_szBuffer);
     }

     char* GetBuffer()
     {
          return m_szBuffer;
     }

     void ZeroBuffer()
     {
          ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);
     }

     CClientContext* GetNext()
     {
          return m_pNext;
     }

     void SetNext(CClientContext *pNext)
     {
          m_pNext = pNext;
     }

     //Constructor

     CClientContext()
     {
          m_Socket =  SOCKET_ERROR;
          ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);
          m_nTotalBytes = 0;
          m_nSentBytes = 0;
          m_pNext = NULL;
     }

     //destructor

     ~CClientContext()
     {
          closesocket(m_Socket);
     }
};

以下是 InitSets() 函数。 此函数将初始化集合。 将套接字分配给适当的集合。 在调用 select() 函数之前将调用此函数。

//Initialize the Sets

void InitSets(SOCKET ListenSocket) 
{
     //Initialize

     FD_ZERO(&g_ReadSet);
     FD_ZERO(&g_WriteSet);
     FD_ZERO(&g_ExceptSet);

     //Assign the ListenSocket to Sets

     FD_SET(ListenSocket, &g_ReadSet);
     FD_SET(ListenSocket, &g_ExceptSet);

     //Iterate the client context list and assign the sockets to Sets

     CClientContext   *pClientContext  = GetClientContextHead();
     while(pClientContext)
     {
          if(pClientContext->GetSentBytes() < pClientContext->GetTotalBytes())
          {
               //We have data to send

               FD_SET(pClientContext->GetSocket(), &g_WriteSet);
          }
          else
          {
               //We can read on this socket

               FD_SET(pClientContext->GetSocket(), &g_ReadSet);
          }

          //Add it to Exception Set

          FD_SET(pClientContext->GetSocket(), &g_ExceptSet); 

          //Move to next node on the list

          pClientContext = pClientContext->GetNext();
     }
}

以下是 AcceptConnections() 函数。 此函数将使用 select() 监控套接字。 它还将根据套接字的 status 处理套接字。

//This function will loop on while it will manage multiple clients 

//using select()

void AcceptConnections(SOCKET ListenSocket)
{
     while (true)
     {
          InitSets(ListenSocket);
          if (select(0, &g_ReadSet, &g_WriteSet, &g_ExceptSet, 0) > 0) 
          {
               //One of the socket changed state, let's process it.


               //ListenSocket?  Accept the new connection

               if (FD_ISSET(ListenSocket, &g_ReadSet)) 
               {
                    sockaddr_in ClientAddress;
                    int nClientLength = sizeof(ClientAddress);

                    //Accept remote connection attempt from the client

                    SOCKET Socket = accept(ListenSocket, 
                    (sockaddr*)&ClientAddress, &nClientLength);
                    if (INVALID_SOCKET == Socket)
                    {
                         printf("\nError occurred while accepting socket: 
                        %ld.", GetSocketSpecificError(ListenSocket));
                    }

                    //Display Client's IP

                    printf("\nClient connected from: %s", inet_ntoa
                        (ClientAddress.sin_addr)); 

                    //Making it a non blocking socket

                    u_long nNoBlock = 1;
                    ioctlsocket(Socket, FIONBIO, &nNoBlock);
                    CClientContext   *pClientContext  = new CClientContext;
                    pClientContext->SetSocket(Socket);

                    //Add the client context to the list

                    AddClientContextToList(pClientContext);
               }

               //Error occurred for ListenSocket?

               if (FD_ISSET(ListenSocket, &g_ExceptSet)) 
               {
                    printf("\nError occurred while accepting socket: %ld.", 
                    GetSocketSpecificError(ListenSocket));
                    continue;
               }

               //Iterate the client context list to see if 

               //any of the socket there has changed its state

               CClientContext   *pClientContext  = GetClientContextHead();
               while (pClientContext)
               {
                    //Check in Read Set

                    if (FD_ISSET(pClientContext->GetSocket(), &g_ReadSet))
                    {
                         int nBytes = recv(pClientContext->GetSocket(), 
                           pClientContext->GetBuffer(), MAX_BUFFER_LEN, 0);
                         if ((0 == nBytes) || (SOCKET_ERROR == nBytes))
                         {
                              if (0 != nBytes) //Some error occurred, 

                              //client didn't close the connection

                              {
                                   printf("\nError occurred while 
                                receiving on the socket: %d.", 
                                GetSocketSpecificError
                                (pClientContext->GetSocket()));
                              }

                              //In either case remove the client from list

                              pClientContext = DeleteClientContext
                                (pClientContext);
                              continue;
                         }

                         //Set the buffer

                         pClientContext->SetTotalBytes(nBytes);
                         pClientContext->SetSentBytes(0);

                         printf("\nThe following message was received: %s", 
                            pClientContext->GetBuffer());
                    }

                    //Check in Write Set

                    if (FD_ISSET(pClientContext->GetSocket(), &g_WriteSet))
                    {
                         int nBytes = 0;
                         if (0 < (pClientContext->GetTotalBytes() - 
                            pClientContext->GetSentBytes()))
                         {
                              nBytes = send(pClientContext->GetSocket(), 
                            (pClientContext->GetBuffer() + 
                            pClientContext->GetSentBytes()), 
                            (pClientContext->GetTotalBytes() - 
                            pClientContext->GetSentBytes()), 0);
                              if (SOCKET_ERROR == nBytes)
                              {
                                   printf("\nError occurred while 
                                sending on the socket: %d.", 
                                GetSocketSpecificError
                                (pClientContext->GetSocket()));
                                   pClientContext = DeleteClientContext
                                    (pClientContext);
                                   continue;
                              }

                              if (nBytes == 
                                (pClientContext->GetTotalBytes() - 
                                pClientContext->GetSentBytes()))
                              {
                                   //We are done sending the data, 

                              //reset Buffer Size

                                   pClientContext->SetTotalBytes(0);
                                   pClientContext->SetSentBytes(0);
                              }
                              else
                              {
                                   pClientContext->IncrSentBytes(nBytes);
                              }
                         }
                    }

                    //Check in Exception Set

                    if (FD_ISSET(pClientContext->GetSocket(), &g_ExceptSet))
                    {
                         printf("\nError occurred on the socket: %d.", 
                           GetSocketSpecificError(pClientContext->GetSocket()));
                         pClientContext = DeleteClientContext(pClientContext);
                         continue;
                    }

                    //Move to next node on the list

                    pClientContext = pClientContext->GetNext();
               }//while

          }
          else //select

          {
               printf("\nError occurred while executing select(): %ld.", 
                            WSAGetLastError());
               return; //Get out of this function

          }
     }
}

我创建了一个函数 GetSocketSpecificError() 来获取特定套接字上的错误。 使用 select() 时,我们不能依赖 WSAGetLastError() ,因为多个套接字可能存在错误,并且我们需要特定于套接字的错误。

//When using select() multiple sockets may have errors

//This function will give us the socket specific error

//WSAGetLastError() can't be relied upon

int GetSocketSpecificError(SOCKET Socket)
{
     int nOptionValue;
     int nOptionValueLength = sizeof(nOptionValue);

     //Get error code specific to this socket

     getsockopt(Socket, SOL_SOCKET, SO_ERROR, (char*)&nOptionValue, 
                                            &nOptionValueLength);

     return nOptionValue;
}

以下是链表操作函数。 这些将用于处理 CClientContext 单链表。

//Get the head node pointer

CClientContext* GetClientContextHead()
{
     return g_pClientContextHead;
}

//Add a new client context to the list

void AddClientContextToList(CClientContext *pClientContext)
{
     //Add the new client context right at the head

     pClientContext->SetNext(g_pClientContextHead);
     g_pClientContextHead = pClientContext;
}

//This function will delete the node and will return the next node of the list

CClientContext * DeleteClientContext(CClientContext *pClientContext)
{
     //See if we have to delete the head node

     if (pClientContext == g_pClientContextHead) 
     {
          CClientContext *pTemp = g_pClientContextHead;
          g_pClientContextHead = g_pClientContextHead->GetNext();
          delete pTemp;
          return g_pClientContextHead;
     }

     //Iterate the list and delete the appropriate node

     CClientContext *pPrev = g_pClientContextHead;
     CClientContext *pCurr = g_pClientContextHead->GetNext();

     while (pCurr)
     {
          if (pCurr == pClientContext)
          {
               CClientContext *pTemp = pCurr->GetNext();
               pPrev->SetNext(pTemp);
               delete pCurr;
               return pTemp;
          }

          pPrev = pCurr;
          pCurr = pCurr->GetNext();
     }

     return NULL;
}

屏幕截图

Screenshot - Server1.jpg

Screenshot - Client1.jpg

Screenshot - Server2.jpg

Screenshot - Client2.jpg

历史

  • 2007 年 8 月 7 日:初始发布
© . All rights reserved.