一、Socket类
Socket类用于封装fd、bind、listen、accept等操作
- Socket.h
#pragma once
#include "noncopyable.h"
class InetAddress;
//封装Socket fd
class Socket : noncopyable
{
public:
explicit Socket(int sockfd)
:sockfd_(sockfd)
{}
~Socket();
int fd()const { return sockfd_; }
void bindAddress(const InetAddress &localaddr);
void listen();
int accept(InetAddress *peeraddr);
void shutdownWrite();
//数据直接发送,不做TCP缓冲
void setTcpNoDelay(bool on);
void setReuseAddr(bool on);
void setReusePort(bool on);
void setKeepAlive(bool on);
private:
const int sockfd_;
};
- Socket.cc
shutdown函数
#include "Socket.h"
#include "Logger.h"
#include "InetAddress.h"
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<strings.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
Socket::~Socket()
{
close(sockfd_);
}
// 将服务器本地ip port绑定到listenfd
void Socket::bindAddress(const InetAddress &localaddr)
{
if(0 != ::bind(sockfd_, (sockaddr*)localaddr.getSockAddr(),sizeof(sockaddr_in)))
{
LOG_FATAL("bind sockfd:%d fail \n",sockfd_);
}
}
void Socket::listen()
{
if(0 != ::listen(sockfd_,1024))
{
LOG_FATAL("listen sockfd:%d fail \n",sockfd_);
}
}
int Socket::accept(InetAddress *peeraddr)
{
sockaddr_in addr;
socklen_t len;
bzero(&addr,sizeof addr);
int connfd = ::accept(sockfd_, (sockaddr*)&addr, &len);
if(connfd >= 0)
{
peeraddr->setSockAddr(addr);
}
return connfd;
}
// tcp socket是全双工通信,这是关闭监听套接字sockfd_写端
void Socket::shutdownWrite()
{
if(::shutdown(sockfd_, SHUT_WR) < 0)
{
LOG_ERROR("sockets::shutdownWrite");
}
}
//数据直接发送,不做TCP缓冲
void Socket::setTcpNoDelay(bool on)
{
int optval = on ? 1 : 0;
// TCP_NODELAY禁用Nagle算法,TCP_NODELAY包含在 <netinet/tcp.h>
::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof optval);
}
void Socket::setReuseAddr(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
}
void Socket::setReusePort(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof optval);
}
void Socket::setKeepAlive(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof optval);
}
二、Acceptor类
Acceptor工作在mainReactor,用于监听新用户的连接,将与客户端通信的fd打包成Channel,muduo采用轮询算法找一个subloop,将其唤醒(每一个loop都有一个wakeupfd,wakeupfd是通过linux的API eventfd创建的,一个带有线程间通知机制的fd),把打包好的Channel给subloop
- Acceptor.h
#pragma once
#include "noncopyable.h"
#include "Socket.h"
#include "Channel.h"
#include<functional>
class EventLoop;
class InetAddress;
class Acceptor : noncopyable
{
public:
using NewConnectionCallback = std::function<void(int sockfd, const InetAddress&)>;
Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport);
~Acceptor();
void setNewConnectionCallback(const NewConnectionCallback &cb)
{
newConnectionCallback_ = cb;
}
bool listenning()const { return listenning_; }
void listen();
private:
void handleRead();
EventLoop *loop_; //Acceptor用的就是用户定义的baseLoop,也称作mainLoop
Socket acceptSocket_;
Channel acceptChannel_;
NewConnectionCallback newConnectionCallback_;
bool listenning_;
};
- 成员变量
acceptSocket_
:封装了listenfd,用于监听新用户的连接 - 成员变量
acceptChannel_
:封装了listenfd,以及感兴趣的事件events和发生的事件revents - 成员变量
loop_
:通过事件循环监听listenfd,也就是mainLoop - 成员变量
newConnectionCallback_
:如果一个客户端连接成功,Acceptor返回一个Channel给TcpServer,TcpServer会通过轮询唤醒一个subLoop,并把新客户端的Channel给subLoop,而这些事情都是交给一个回调函数做的,即newConnectionCallback_ - 成员函数handleRead:服务器的listenfd发生读事件调用,即新用户连接
- Acceptor.cc
socket函数第二个参数type
SOCK_NONBLOCK
:非阻塞形式,没有事件发生立刻返回
SOCK_CLOEXEC
:fork之后子进程会继承父进程所有的文件描述符,此时子进程会关闭这些继承来的文件描述符
#include "Acceptor.h"
#include "Logger.h"
#include "InetAddress.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
//创建listenfd
static int createNonblocking()
{
int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
if(sockfd < 0)
{
LOG_FATAL("%s:%s:%d listen socket create err:%d \n", __FILE__, __FUNCTION__, __LINE__,errno);
}
return sockfd;
}
Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
:loop_(loop)
,acceptSocket_(createNonblocking()) // Socket的构造函数需要一个int参数,createNonblocking()返回值就是int
,acceptChannel_(loop,acceptSocket_.fd()) // 第一个参数就是Channel所属的EventLoop
,listenning_(false)
{
acceptSocket_.setReuseAddr(true);
acceptSocket_.setReusePort(true);
acceptSocket_.bindAddress(listenAddr);// bind
/**当我们TcpServer调用start方法时,就会启动Acceptor.listen()方法
* 有新用户连接时,要执行一个回调,这个方法会将和用户连接的fd打包成Channel,wakeup subloop,
* 然后交给subloop,下面就是注册包装了listenfd的Channel发生读事件后,需要执行的回调函数,
* Acceptor只管理封装了listenfd的Channel,只需要注册读事件的回调
**/
//在listenfd还没有发生事件的时候,我们就给他预先注册一个事件回调,当真正listenfd有客户端连接的话,
//底层反应堆会帮我们调用这个回调,即event对应的事件处理器
acceptChannel_.setReadCallBack(std::bind(&Acceptor::handleRead, this));
}
Acceptor::~Acceptor()
{
acceptChannel_.disableAll();
acceptChannel_.remove();
}
void Acceptor::listen()
{
listenning_ = true; //开始监听
acceptSocket_.listen(); // 调用底层的::listen
//将acceptChannel_注册到poller里面,poller才能帮忙监听是否有事件发生,
//如果有事件发生,channel就会调用事先被注册的readcallback
//就会执行到handleRead函数,这个函数就会拿到新用户连接的fd以及客户端的ip+port
//直接执行回调
acceptChannel_.enableReading();
}
//listenfd有事件发生了,就是有新用户连接了
void Acceptor::handleRead()
{
InetAddress peerAddr; //客户端地址
int connfd = acceptSocket_.accept(&peerAddr);
if(connfd >= 0)
{
if(newConnectionCallback_) // 轮询找到subloop,唤醒并分发当前新客户端connfd的Channel
{
newConnectionCallback_(connfd,peerAddr);
}
else
{
::close(connfd);// 如果没有设置新用户连接的回调操作,就关闭连接
}
}
else //accept出错
{
LOG_ERROR("%s:%s:%d accept err:%d \n", __FILE__, __FUNCTION__, __LINE__,errno);
if(errno == EMFILE)//当前进程没有可用的fd再分配
{
/**出现这种错误
* 1.调整当前文件描述符的上限
* 2.单台服务器已经不足以支撑现有的流量,需要做集群或者分布式部署
**/
LOG_ERROR("%s:%s:%d sockfd reached limit\n", __FILE__, __FUNCTION__, __LINE__);
}
}
}