【网络组件】TCP连接

来源:互联网 发布:第一个网络实名制国家 编辑:程序博客网 时间:2024/06/05 16:09

     本节主要研究TCP连接TcpConnection的实现;


TcpConnection

 (1)TcpConnection采用C++中shared_ptr来管理;TcpConnection可被用户持有,当TcpConnection析构时,才会close其连接描述符;也就是说即使TCP连接即使已经关闭了,但是用户仍旧持有TcpConnection,TcpConnection的连接描述符仍然不会被关闭;这主要是为了防止串话,假设先前用户仍旧持有TcpConnection,但是连接描述符被其它的TcpConnection使用,那就会造成先前用户持有TcpConnection的串话,它持有的连接描述符又有了新的TCP连接,TcpConnection的连接描述符关闭时,新的TCP连接也将会被关闭;因此程序应该保证TCP连接关闭以后,TcpConnection也应该析构;
(2)本节仅仅独立的解释TcpConnection,TcpConnection与Acceptor、TcpServer和Connecor、TcpAcceptor之间的交互,将会由以后的博客介绍;


状态转换图

(1)初始状态为Connecting;

(2)当TCP连接建立以后,状态从Connecting变成Connected;

(3)当有写关闭以后以后,状态由Connected变成Disconnecting,我们并不能立即执行写关闭;应该设置状态在处于Disconnecting时,将当前的应用层发送缓存数据发送完以后再继续shutdown;

(4)当执行_headClose时,将会到达最后的状态Disconnected;

状态转换图如下:




TcpConnection

TcpConnection声明

namespace Net{class EventLoop;class Event;class TcpConnection final : public std::enable_shared_from_this<TcpConnection>{public:  TcpConnection(const TcpConnection&) = delete;  TcpConnection& operator=(const TcpConnection&) = delete;  TcpConnection(int connfd, EventLoop* loop, const InetAddress& peerAddr, const InetAddress& localAddr);  ~TcpConnection();  enum State { Connecting, Connected, Disconnecting, Disconnected};  void setState(State state)  {    _state = state;  }  //连接接收到信息时,执行的用户函数的回调  void setMessageCallback(const MessageCallback& cb)  {    _messageCallback = cb;  }  //连接建立或断开时,执行的用户函数的回调  void setConnectionCallback(const ConnectionCallback& cb)  {    _connectionCallback = cb;  }  //连接断开时,执行的TcpSever的removeConnection函数  typedef std::function<void(TcpConnectionPtr)> CloseConnectionCallback;  void setCloseConnectionCallback(const CloseConnectionCallback& cb)  {    _closeConnectionCallback = cb;  }  void connectEstablished();  void send(const void* buf, size_t len);  void send(const std::string& message);  void shutdown();  bool connected() const  {    return _state == Connected;  }  int connfd() const  {    return _connfd;  }  EventLoop* loop() const  {    return  _loop;  }  Buffer& inputBuffer()  {    return _inputBuffer;  }private:  void _sendInLoop(const void* buf, size_t len);  void _sendInLoopByMessage(const std::string& message);  void _shutdownInLoop();  void _connectEstablishedInLoop();  void _handleRead();  void _handleWrite();  void _handleClose();  void _handleError();  int _connfd;  EventLoop* _loop;  std::unique_ptr<Event> _connEvent;  InetAddress _peerAddr;  InetAddress _localAddr;  State _state;  Buffer _inputBuffer;    //read from connfd  Buffer _outputBuffer;   //write to connfd  MessageCallback _messageCallback;  ConnectionCallback _connectionCallback;  CloseConnectionCallback _closeConnectionCallback;};typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;}
说明几点:

(1)_inputBuffer,_outputBuffer分别是该Tcp连接的应用层接收缓冲和发送缓冲;_peerAddr,_localAddr分别为TCP连接的远程地址对和本地地址对;

(2) _handleRead();handleWrite();_handleClose();_handleError();分别是监听到相对应的事件类型后,执行的回调函数;

(3)State _state;表示TCP的连接状态,状态分别有enum State { Connecting, Connected, Disconnecting, Disconnected};对应上述的状态转换图;

(4)Tcp连接还通过发送数据send函数,以及写关闭函数shutdown;当调用写系列的函数并不是本身TCP连接的IO线程时,会将对应的回调放入的IO线程中执行,因此分别有
_sendInLoop(const void* buf, size_t len);  _sendInLoopByMessage(const std::string& message); _shutdownInLoop();系列函数;

(5)connectEstablished()为TCP连接建立的外部调用函数,同样有对应的IO线程回调函数_connectEstablishedInLoop();

(6)  MessageCallback _messageCallback为TCP连接接收到数据后执行的用户回调函数,此时用户可能有足够的数据宝可以处理;ConnectionCallback _connectionCallback;为TCP连接建立和断开时都需要执行的回调函数,来让用户处理对持有TCP连接的逻辑;

(7)CloseConnectionCallback _closeConnectionCallback为TCP连接断开时,TcpServer需要执行的回调函数,为保证线程安全,需要在TcpServer本身的线程中进行处理;


TcpConnection构造和析构

TcpConnection::TcpConnection(int fd, EventLoop* loop_, const InetAddress& peerAddr, const InetAddress& localAddr):    _connfd(fd),    _loop(loop_),    _connEvent(new Event(_connfd, _loop)),    _peerAddr(peerAddr),    _localAddr(localAddr),    _state(Connecting){  assert(_state == Connecting);  _connEvent->setReadCallback(std::bind(&TcpConnection::_handleRead, this));  _connEvent->setWriteCallback(std::bind(&TcpConnection::_handleWrite, this));  _connEvent->setErrorCallback(std::bind(&TcpConnection::_handleError, this));  _connEvent->setCloseCallback(std::bind(&TcpConnection::_handleClose, this));}//if we do not do this, when client close fd, it recives ACK, enters FIN_WAIT_2, and the server is in CLOSE_WAIT;TcpConnection::~TcpConnection(){  if (_connfd >= 0)    ::close(_connfd);  LOG_TRACE << "TcpConnection::~TcpConnection()";}

说明几点:

(1)TcpConnection的构造是在其他线程中调用,为了保证线程安全性,TcpConnection本身被管理的IO线程此时还并没有真正接管此TcpConnection,要一直等到connectEstablished时;

(2)TcpConnection的析构函数中,我们需要close到连接本身,发送方和接收方可能处于通信死锁的状态;


IO线程接管TCP连接

void TcpConnection::connectEstablished()   //thread safe{  _loop->queueInLoop(std::bind(&TcpConnection::_connectEstablishedInLoop, this));}void TcpConnection::_connectEstablishedInLoop(){  setState(Connected);  _connEvent->enableReading();  if (_connectionCallback)    _connectionCallback(shared_from_this());}

说明几点:

(1)为了保证线程安全性,connectEstablished()仅仅是 _loop->queueInLoop对_connectEstablishedInLoop()的一层包装;


发送数据

void TcpConnection::send(const void* buf, size_t len){  if (_state == Connected)    {      if (_loop->isInThreadLoop())        {          //  LOG_TRACE << "is InThreadLoop";          _sendInLoop(buf, len);        }      else        {          // LOG_TRACE << "is not InThreadLoop";          //we must copy a swap, otherwise the buf in other thread stack will lose          std::string message(static_cast<const char*>(buf), len);    //impotant          _loop->runInLoop(std::bind(&TcpConnection::_sendInLoopByMessage, this, message));        }    }}void TcpConnection::send(const std::string& message){  send(message.c_str(), message.size());}void TcpConnection::_sendInLoopByMessage(const std::string& message){  _sendInLoop(message.c_str(), message.size());}void TcpConnection::_sendInLoop(const void* buf, size_t len){  LOG_TRACE << "_sendInLoop, fd: " << _connEvent->fd();  _outputBuffer.append(buf, len);  if (!_connEvent->isWriting())    _connEvent->enableWriting();}

说明几点:

(1)send函数提供两个接口,参数分别是以char*和string;为了保证线程安全性,使用_sendInLoop,将写数据在IO线程中执行,否则如果直接将数据添加到应用层发送缓冲时,将会和IO线程本身操作应用层发送缓冲争用;

(2)在send(const void* buf, size_t len)中,其他线程发送的数据地址A,我们需要首先拷贝一份,因为当IO线程执行本身的_sendInLoop函数时,其他线程发送的数据地址A可能已经无效,因此需要拷贝;

(3)只有连接仍处于Connected时,才允许发送数据,否则其他状态Disconnected(连接已关闭),Disconnecting(写关闭)都不能继续发送数据;


写关闭

void TcpConnection::shutdown(){  LOG_TRACE << "_shutdown, fd: " << _connEvent->fd();  if (_state == Connected)    {      setState(Disconnecting);    //写关闭设置      _loop->runInLoop(std::bind(&TcpConnection::_shutdownInLoop, this));    }}void TcpConnection::_shutdownInLoop(){  if (!_connEvent->isWriting())    {      sockets::shutdownWrite(_connfd);    //we should send all bytes in application, we can not shutdownWrite in this time    }}

说明几点:

(1)写关闭时,我们需要将应用层发送缓冲的数据发送完后,才会真正的写关闭;因此当连接仍然有数据在写时,仅仅是设置状态setState(Disconnecting);当连接没有数据写时   if (!_connEvent->isWriting())才会shutdownWrite;因此我们在连接可写_handClose中,应用层发送缓冲数据为空后要检查Disconnecting状态,看是否需要写关闭;


连接可写

void TcpConnection::_handleWrite(){  LOG_TRACE << "_handleWrite, fd: " << _connEvent->fd();  if (_connEvent->isWriting())    {      ssize_t n = ::write(_connfd, _outputBuffer.beginRead(), _outputBuffer.used());  //write to connection      if (n > 0)        {          _outputBuffer.setReadIndex(n);          if (_outputBuffer.used() == 0)            {              _connEvent->disableWriting();              //if state is Disconnecting and all application data has sended, we should do _shutdownInLOOp              if (_state == Disconnecting)                _shutdownInLoop();            }        }      else        {          LOG_SYSERR << "write error: "  << sockets::strError(errno);        }    }}

说明几点:

(1)当应用层发送缓冲的数据为空时,我们要关闭POLLOUT事件,此时用户可能执行了写关闭,通过判断状态Disconnecting来执行写关闭;


连接可读

void TcpConnection::_handleRead(){  LOG_TRACE << "_handleRead, fd: " << _connEvent->fd();  ssize_t n = _inputBuffer.readFd(_connfd);  if (n > 0)    {      _messageCallback(shared_from_this(), &_inputBuffer);    //should thread safe    }  else if (n == 0)    {      _handleClose();    }  else    {      _handleError();    }}

说明几点:

(1)在_handleRead()中,将内核接收缓存的内容读取到应用层接收缓冲中,然后执行用户提供的_messageCallback回调,让用户来处理读取的数据;

(2)当然连接读也有可能是关闭或错误,读取的值分别是0和-1,根据对应的读取值进行相对应的函数处理;


连接关闭

void TcpConnection::_handleClose(){  LOG_TRACE << "_handleClose, fd: " << _connEvent->fd();  _loop->assertInThreadLoop();  assert(_state == Connected || _state == Disconnecting);  setState(Disconnected);  _connEvent->disableAll();  _loop->removeEvent(_connEvent.get());  if (_connectionCallback)    _connectionCallback(shared_from_this());    //should thread safe  _closeConnectionCallback(shared_from_this());   //should thread safe}

说明几点:

(1)首先setState(Disconnected);设置状态,_connEvent->disableAll()连接的事件监听也会关闭,并从_loop的epoll监听事件中移除;

(2)连接断开时执行用户的回调_connectionCallback,让用户来处理持有的TcpConnection连接,要保证线程安全;

(3)最后_closeConnectionCallback执行TcpServer的移除TcpConnection连接函数,要保证线程安全;


连接错误

void TcpConnection::_handleError(){  LOG_TRACE << "_handleError, fd: " << _connEvent->fd();  int err = sockets::socketError(_connfd);  LOG_ERROR << "socket fd: "<< _connEvent->fd() << " error: " << sockets::strError(err);}

说明几点:

(1)连接的错误并不影响连接的正常关闭,仅仅是将出错的信息写入日至;

0 0
原创粉丝点击