我的任务是为学生编写一个使用select()的服务器类。
它是有效的,但我现在愿意改进它很多,并能够使用它在其他个人项目,所以任何批评和改进的想法是欢迎的!
下面是main.cpp:
// 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()的函数指针用于定义在客户端文件描述符上检测到活动时的行为,下面是一个将数据包转发给每个其他客户端的示例:
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() );
}
}以下是服务器实现:
#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;
};发布于 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。
将原始循环重写为算法或范围循环
而不是:
for ( size_t i = 0; i < list.size(); i++ ) {
if ( list[i].fd() != client.fd() )
server.send( list[i], clientPacket, server.getPacketSize() );使用
for ( auto& otherClient : clients ) {
if ( otherClient.fd() != client.fd() )
server.send( otherClient, clientPacket, server.getPacketSize() );适当时
)
当你不需要的时候,不要复制大物体。这适用于packet和所有基于性能原因的vectors,也适用于基于逻辑原因的Client。我不希望在网络代码中复制客户端对象,然后您可能会以不一致的状态结束。
不要将端口指定为string给server。
try {
p = stol( port );
} catch ( const exception & e ) {
p = 1664;
}此代码不应该在server中。如果它碰巧使用了隐藏的默认端口,那么它将不是一个非常通用的服务器。
在这里捕获std::locic_error也会更好。
std::...你甚至忘了可怕的using namespace std;。不管怎样,别这么做。对所有适当的符号使用std::。
您的格式非常不正确。你也有太多多余的新行。
我认为您泄漏文件描述符/打开套接字,因为您没有做任何像int析构函数这样的操作。考虑在代码中使用更多的RAII,以使这些错误更加明显。
https://codereview.stackexchange.com/questions/111310
复制相似问题