首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >选择()服务器实现

选择()服务器实现
EN

Code Review用户
提问于 2015-11-20 10:39:43
回答 1查看 2K关注 0票数 5

我的任务是为学生编写一个使用select()的服务器类。

它是有效的,但我现在愿意改进它很多,并能够使用它在其他个人项目,所以任何批评和改进的想法是欢迎的!

下面是main.cpp:

代码语言:javascript
复制
// example packet
typedef struct packet {

    char       msg[ 4096 ];

} packet;

int                 main( int ac, char * av[] )
{
  packet            shutdownPacket = { "QUIT" };

  Server< packet >  server;

  if ( ac != 2 ) {
    cerr << "Usage : ./server port" << endl;
    return ( EXIT_FAILURE );
  }

  try {

    server.bindSock( av[1] );
    server.listenPort();
    server.setDefaultOnReadCallback( & OnReadCallback ); // this one will be detailed beyond
    server.setOnShutdownPacket( shutdownPacket ); // this packet will be sent to all clients on server quit
    server.startServer();

  } catch ( const exception & e ) {
    cerr << e.what() << endl;
  }

  return ( EXIT_SUCCESS );
}

传递给setDefaultOnReadCallback()的函数指针用于定义在客户端文件描述符上检测到活动时的行为,下面是一个将数据包转发给每个其他客户端的示例:

代码语言:javascript
复制
void         OnReadCallback( Server< header > & server,
                             Client< header > & client )
{
    auto       list = server.getClientList();
    packet   * clientPacket = server.getPacket();

    for ( size_t i = 0; i < list.size(); i++ ) {

        if ( list[i].fd() != client.fd() )
           server.send( list[i], clientPacket, server.getPacketSize() );

    }       
}

以下是服务器实现:

代码语言:javascript
复制
  #pragma once

  #include <vector>
  #include <string>
  #include <stdexcept>
  #include <iostream>
  #include <algorithm>

  #include "ServerSSL.h"

  template <class T> class    Client;
  template <class T> class    Server;

  template <typename T>
  using callback = void (*)( Server<T> & server, Client<T> & client );

  template <class T>
  class    Server {

  private:

    bool                   _working;

    SOCKET                 _listenFd;
    int                    _fdMax;
    vector< Client< T > >  _selectFds;

    fd_set                 _initFds;
    fd_set                 _readFds;

    T                    * _packet;
    T                      _onShutDownPacket;
    pSize                  _packetSize;

    ServerSSL              _ssl; // i won't detail the implementation here but the server uses SSL

    callback<T>            _defaultOnReadCallback;

  public:

    Server()
      {
        _working = true;
        _listenFd = INVALID_SOCKET;
        _packetSize = sizeof( T );
        _packet = new T;
        _defaultOnReadCallback = NULL;
        memset( _packet, 0, _packetSize );
        memset( & _onShutDownPacket, 0, _packetSize );
        FD_ZERO( & _initFds );
      }

    ~Server()
      {
        delete _packet;
      }

    void                  bindSock( const string & port )
    {
      struct addrinfo       *res;
      struct addrinfo       *tmp;
      struct addrinfo       hints;
      SOCKET                sockfd;
      int                   err;

      memset( & hints, 0, sizeof( struct addrinfo ) );
      hints.ai_family = AF_UNSPEC;
      hints.ai_socktype = SOCK_STREAM;
      hints.ai_protocol = IPPROTO_TCP;
      hints.ai_flags = AI_PASSIVE;

      size_t p;
      try {
         p = stol( port );
      } catch ( const exception & e ) {
         p = 1664;
      }

      err = getaddrinfo( NULL, to_string( p ).c_str(), & hints, & res );
      if ( err != 0 ) {
        freeaddrinfo( res );
        throw runtime_error( gai_strerror( err ) );
      }

      for ( tmp = res; tmp != NULL; tmp = tmp->ai_next )
        {
           sockfd = socket( tmp->ai_family, tmp->ai_socktype, tmp->ai_protocol );
           if ( sockfd != INVALID_SOCKET ) {
             if ( bind( sockfd, tmp->ai_addr, tmp->ai_addrlen ) == 0 ) {
                break;
              }
            }
           closesocket( sockfd );
        }

      freeaddrinfo( res );
      if ( tmp == NULL )
        throw runtime_error( "Unable to bind socket" );

      _listenFd = sockfd;
    }

    void                  listenPort( const int backlog = 42 )
    {
      if ( listen( _listenFd, backlog ) == SOCKET_ERROR )
        throw runtime_error( neterror( errno ) );
    }

    void                  startServer( void )
    {
      FD_SET( _listenFd, & _initFds );
      _fdMax = _listenFd;
      _selectFds.push_back( Client<T>( _listenFd ) );
      for ( ; _working ; )
        {
           _readFds = _initFds;
           if ( select( _fdMax + 1, & _readFds, NULL, NULL, NULL ) == -1 ) {
               notifyServerQuit();
               throw runtime_error( neterror( errno ) );
           }
           for ( size_t i = 0; i < _selectFds.size(); i++ ) {

              if ( FD_ISSET( _selectFds[i].fd(), & _readFds ) ) {
                 if ( _selectFds[i].fd() == _listenFd )
                     acceptClient();
                 else
                     onReadCallback( _selectFds[i] );
              }
           }
        }
      notifyServerQuit();
    }

    void                  shutdown( void )
    {
      _working = false;
    }

    void                  setOnShutdownPacket( T packet )
    {
      _onShutDownPacket = packet;
    }

    void                  setDefaultOnReadCallback( const callback< T > defaultCallback )
    {
      _defaultOnReadCallback = defaultCallback;
    }

    SOCKET                fd( void ) const
    {
      return _listenFd;
    }

    T *                   getPacket( void ) const
    {
      return _packet;
    }

    pSize                 getPacketSize( void ) const
    {
      return _packetSize;
    }

    vector< Client< T > > getClientList( void ) const
      {
         return _selectFds;
      }

    int                   send( const Client<T> & client, void * packet, const size_t size ) const
    {
      return _ssl.send( client.ssl(), packet, size );
    }

    int                   recv( const Client<T> & client, void * packet, const size_t size ) const
    {
      return _ssl.recv( client.ssl(), packet, size );
    }

  private:

    void                  acceptClient( void )
    {
      SOCKADDR_STORAGE    addr;
      SOCKET              nsockfd;
      socklen_t           addrlen;

      addrlen = sizeof( addr );
      memset( & addr, 0, addrlen );
      nsockfd = accept( _listenFd, ( SOCKADDR * ) & addr, & addrlen );

      if ( nsockfd == SOCKET_ERROR ) {
        cerr << "accept " << neterror( errno ) << endl;
        return ;
      }

      _selectFds.push_back( Client< T >( nsockfd, _ssl.new_connexion(), _defaultOnReadCallback ) );
      FD_SET( nsockfd, & _initFds );
      if ( nsockfd > _fdMax )
        _fdMax = nsockfd;

      cout << "Connection accepted" << endl;
    }

    void                  onReadCallback( Client<T> & client )
    {
      memset( _packet, 0, _packetSize );
      int len = _ssl.recv( client.ssl(), _packet, _packetSize );

      if ( len <= 0 )
        closeConnection( len, client );
      else
        client.work( *this, client );
    }

    void                  closeConnection( const size_t recvRet, const Client<T> & client )
    {
      if ( recvRet == 0 )
        cout << "Connection closed" << endl;
      else {
        cerr << "recv " << neterror( errno ) << endl;
      }

      if ( closesocket( client.fd() ) == SOCKET_ERROR )
        cerr << "close " << neterror( errno ) << endl;
      _ssl.delete_connexion( client.ssl() );

      FD_CLR( client.fd(), & _initFds );
      _selectFds.erase( remove_if( _selectFds.begin(),
                        _selectFds.end(),
                        [client] ( const Client<T> & cl )
                        { return cl.fd() == client.fd(); } ),
                       _selectFds.end() );
    }

    void                  notifyServerQuit( void )
    {
      cout << _onShutDownPacket.cmd << endl;
      for ( size_t i = 0; i < _selectFds.size(); i++ ) {

        if ( _selectFds[i].ssl() ) {
           _ssl.send( _selectFds[i].ssl(),
                      & _onShutDownPacket,
                      _packetSize );
           _ssl.delete_connexion( _selectFds[i].ssl() );
        }

        if ( closesocket( _selectFds[i].fd() ) == SOCKET_ERROR )
            cerr << "close " << neterror( errno ) << endl;

      }
    }

  };

  /*
  ** CLIENT CLASS
  */

  template <class T>
  class    Client {

  private:

    SOCKET    _fd;
    SSL     * _ssl;

    callback<T>  _defaultOnReadCallback;

  public:

    Client( const SOCKET fd, SSL * ssl = NULL, const callback<T> job = NULL ) : _fd( fd )
      {
        _defaultOnReadCallback = job;
        setOnReadCallback( job );
        _ssl = ssl;
        if ( _ssl != NULL ) {
           SSL_set_fd( _ssl, _fd );
           if ( SSL_accept( _ssl ) == -1 )
              throw runtime_error( ERR_reason_error_string( ERR_get_error() ) );
        }
      }
    ~Client() {}

    SOCKET    fd( void ) const
    {
      return _fd;
    }

    SSL *     ssl( void ) const
    {
      return _ssl;
    }

    void      setOnReadCallback( const callback<T> task = NULL )
    {
      if ( task == NULL )
        work = _defaultOnReadCallback;
      else
        work = task;
    }

    callback<T>  work;

  };
EN

回答 1

Code Review用户

回答已采纳

发布于 2015-11-21 10:58:55

低级网络代码是非常棘手的

不幸的是,有了网络,各种各样的事情都会出错。例如,您可能会遇到一个SIGPIPE,它会使您的程序异步崩溃。有关如何处理此问题的讨论,请参阅这个问题和答案。

此外,您也不能保证一次收到完整的数据包,但这是隐藏在您的ssl实现中,所以我不能真正评论。

您似乎在错误处理上花了一些心思或调试,这是一个很好的开始。但在当前状态下,我无法真正判断您的错误处理是否足够。

考虑到网络代码是非常丑陋和难以正确的,我建议使用一个已建立的库的生产代码,如boost::asio。不幸的是,即使是那些似乎并不总是照顾SIGPIPE

利用继承

而不是setDefaultOnReadCallback,您可以在Server中创建一个纯虚拟方法,并继承特定的服务器来实现读取回调。这将导致代码更精简。

此外,请重新考虑回调方法的参数,以包含实际的数据包,而不必询问服务器,如果您不定期地运行,服务器将开始严重失败。

封装包

例如,您的数据包并不总是完整的。允许将任意类型的数据包作为数据包似乎相当危险,但又不能保证它们被完全传输。另一种方法是只提供最大数据包大小作为模板参数,然后将数据包数据/大小封装为类似于vector<char>的类型。

您也应该避免复制包。您可以删除“复制器”和“赋值运算符”,或者将其设置为“私有”,并提供显式版本。

避免使用复杂的名称

vector< Client< T > > _selectFds;是个非常糟糕的名字。

list = server.getClientList();

应该是

clients = server.getClients();

或者始终如一地忽略get

避免原始循环

将原始循环重写为算法或范围循环

而不是:

代码语言:javascript
复制
for ( size_t i = 0; i < list.size(); i++ ) {
        if ( list[i].fd() != client.fd() )
           server.send( list[i], clientPacket, server.getPacketSize() );

使用

代码语言:javascript
复制
for ( auto& otherClient : clients ) {
    if ( otherClient.fd() != client.fd() )
       server.send( otherClient, clientPacket, server.getPacketSize() );

适当时

传递/返回(const)引用(

)

当你不需要的时候,不要复制大物体。这适用于packet和所有基于性能原因的vectors,也适用于基于逻辑原因的Client。我不希望在网络代码中复制客户端对象,然后您可能会以不一致的状态结束。

使用sane接口

不要将端口指定为stringserver

代码语言:javascript
复制
  try {
     p = stol( port );
  } catch ( const exception & e ) {
     p = 1664;
  }

此代码不应该在server中。如果它碰巧使用了隐藏的默认端口,那么它将不是一个非常通用的服务器。

在这里捕获std::locic_error也会更好。

使用std::...

你甚至忘了可怕的using namespace std;。不管怎样,别这么做。对所有适当的符号使用std::

修复您的缩进

您的格式非常不正确。你也有太多多余的新行。

确保您的清理正确

我认为您泄漏文件描述符/打开套接字,因为您没有做任何像int析构函数这样的操作。考虑在代码中使用更多的RAII,以使这些错误更加明显。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/111310

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档