【网络组件】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)连接的错误并不影响连接的正常关闭,仅仅是将出错的信息写入日至;
- 【网络组件】TCP连接
- 网络连接-TCP
- 20150802-网络连接-TCP
- 网络基础---TCP连接
- 网络基础---TCP连接
- 网络基础---TCP连接
- 查看TCP网络连接情况
- C#网络通信:TCP连接
- 简单的TCP网络连接
- 【网络组件】接受连接Acceptor
- 网络连接测试tcp v1.0.2 是什么
- 面向连接(TCP)的网络编程
- 网络编程基础:使用TCP连接
- C# tcp/ip 网络建立连接
- TCP--简单浏览器(能连接网络)
- libcurl网络连接使用tcp/ip
- 网络编程——TCP连接
- TCP的网络连接建立过程
- Spring MVC 4.1.3 + MyBatis 零基础搭建Web开发框架(注解模式哦)
- 面向对象的三个基本特征
- 公钥和私钥
- Mysql INSERT、REPLACE、UPDATE的区别
- 【Zookeeper】Linux Zookeeper配置
- 【网络组件】TCP连接
- 用户自定义控件拖拽失败问题
- git基本操作命令
- android:imeOptions
- Openstack I版 结合 Ceph 分布式存储 部署安装(五)
- 获取无线路由Ip地址
- jquery设置元素的readonly和disabled
- 人生苦短,我用Python 学习笔记——第二天
- 从现在开始,坚持记录我的学习点滴