C++socket基础进阶:Select与封装
admin
2024-04-02 05:35:00
0

之前用C#做服务器没搞明白于是从笔者比较熟悉的C++开始入手从头学了一遍,整理一下笔记。
资料来源于《网络多人游戏架构与编程》第三章,这本书讲的很明白,比起网上每篇博客都在介绍的原理,这本书更偏向于代码实现。
代码应该没什么大问题,看懂逻辑的话能自己封装的更好。

上一篇介绍Socket基础函数的在这。

阻塞和非阻塞I/O

开启非阻塞模式

默认情况下,socket操作是阻塞模式,但可以由如下函数转变为非阻塞模式,非阻塞模式的socket被要求执行一个需要阻塞的操作时,它将立刻返回-1,还设置了错误代码errno或WSAGetLastError,分别返回EAGAIN或WSAEWOULDBLOCK。这个代码表示之前的socket行为已经阻塞,没有发生就被终止了。

//Windows系统
int ioctlsocket(SOCKET s,long cmd, //用于控制socket参数,在这种情况下,输入FIONBIOu_long* argp //是这个参数的取值,任意非零值将开启非阻塞模式,0将阻止开启
);
//POSIX兼容系统
int fcntl(int sock,int cmd, //发给socket的命令...
);

在更新的POSIX系统上,必须首先使用F_GETEL获取当前与socket相关的标志,让它们与常数O_NONBLOCK按位或运算之后,使用F_SETEL命令更新socket上的标志。

当socket出于非阻塞模式,调用任何阻塞函数都是安全的,因为我们知道如果它不能在没有阻塞的情况下完成,它会立刻返回。

Select

socket库提供了同时检查多个socket的方式,只要其中有一个socket准备好了就开始执行:

int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,const timeval* timeout
);

参数:

  1. nfds在POSIX平台,是待检查的编号最大的socket的标识符。在POSIX平台,每一个socket只是一个整数,所以直接将所有socket的最大值传入这个函数。在Windows平台,socket表示为指针,而不是整数,所以这个参数不起作用,可以忽略。
  2. readfds是指向socket集合的指针,称为fd_set,包含要检查可读性的socket。
  3. writefds是指向fd_set的指针,存储待检查可写性的socket。当select函数返回时,保留在writefds中所有socket都保证可写,不会引起调用线程的阻塞。给writefds传入nullptr来跳过任何socket可写性的检查。通常,只有当socket的输出缓冲区有太多数据时,socket才会阻塞写操作。
  4. excptfds是指向fd_set的指针,这个fd_set存储待检查错误的socket。当select函数返回,保留在exceptfds中的所有socket都已经发生了错误。给exceptfds传入nullptr来跳过任何错误的检查。
  5. timeout是指向超时之前可以等待最长时间的指针。如果在readfds中的任意一个socket可读,writefds中的任意一个socket可写,或者exceptfds中的任意一个socket发生错误之前发生超时,清空所有集合,select函数将控制返回给调用线程。给timeout输入nullptr来表名没有超时限制。

select函数返回执行之后保留在readfds、writefds和exceptfds中socket的数量。如果发生超时,这个值是0.

fd_set:

fd_set myReadSet;
FD_ZERO(&myReadSet);	//初始化一个空的fd_set
FD_SET(mySocket, &myReadSet);	//给fd_set添加一个socket
FD_INSET(mySocket, &myReadSet);	//检查在select函数返回值后,一个socket是否在fd_set中

封装

类型安全的SocketAddress类

class SocketAddress {
public:SocketAddress(uint32_t inAddress, uint16_t inPort) {GetAsSockAddrIn()->sin_family = AF_INET;GetAsSockAddrIn()->sin_addr.S_un.S_addr = htonl(inAddress);GetAsSockAddrIn()->sin_port = htons(inPort);}SocketAddress(const sockaddr& inSockAddr) {memcpy(&mSockAddr, &inSockAddr, sizeof(sockaddr));}size_t GetSize() const { return sizeof(sockaddr); }
private:sockaddr mSockAddr;sockaddr_in* GetAsSockAddrIn() {return reinterpret_cast (&mSockAddr);}
};
typedef std::shared_ptr SocketAddressPtr;

使用SocketAddressFactory类的域名解析

class SocketAddressFactory {
public:static SocketAddressPtr CreateIPv4FromString(const std::string& inString) {auto pos = inString.find_last_of(':');std::string host, service;if (pos != std::string::npos) {host = inString.substr(0, pos);service = inString.substr(pos + 1);}else {host = inString;//use default portservice = "0";}addrinfo hint;memset(&hint, 0, sizeof(hint));hint.ai_family = AF_INET;addrinfo* result;int error = getaddrinfo(host.c_str(), service.c_str(), &hint, &result);if (error != 0) {freeaddrinfo(result);return nullptr;}while (!result->ai_addr && result->ai_next)result = result->ai_next;if (!result->ai_addr) {freeaddrinfo(result);return nullptr;}auto toRet = std::make_shared(*result->ai_addr);freeaddrinfo(result);return toRet;}
};

类型安全的UDP Socket

class UDPSocket {
public:~UDPSocket();int Bind(const SocketAddress& inToAddress);int SendTo(const void* inData, int inLen, const SocketAddress& inTo);int ReceiveFrom(void* inBuffer, int inLen, SocketAddress& outFrom);
private:UDPSocket(SOCKET inSocket) : mSocket(inSocket) {}SOCKET mSocket;
};
typedef std::shared_ptr UDPSocketPtr;int UDPSocket::Bind(const SocketAddress& inBindAddress) {int err = bind(mSocket, &inBindAddress.mSockAddr, inBindAddress.GetSize());if (err != 0) {//return error from UDPSocket::Bindreturn -1;}return 0;
}
int UDPSocket::SendTo(const void* inData, int inLen, const SocketAddress& inTo) {int byteSentCount = sendto(mSocket, static_cast(inData), inLen, 0, &inTo.mSockAddr, inTo.GetSize());if (byteSentCount >= 0) return byteSentCount;//return error from UDPSocket::SendToreturn -1;
}
int UDPSocket::ReceiveFrom(void* inBuffer, int inLen, SocketAddress& outFrom) {int fromLength = outFrom.GetSize();int readByteCount = recvfrom(mSocket,static_cast (inBuffer),inLen,0,&outFrom.mSockAddr,&fromLength);if (readByteCount >= 0) return readByteCount;//return error from UDPSocket::ReceiveFromreturn -1;
}
UDPSocket::~UDPSocket() {closesocket(mSocket);
}

类型安全的TCP Socket

class TCPSocket {
public:~TCPSocket();int Connect(const SocketAddress& inAddress);int Bind(const SocketAddress& inToAddress);int Listen(int inBackLog = 32);std::shared_ptr Accept(SocketAddress& inFromAddress);int Send(const void* inData, int inLen);int Receive(void* inBuffer, int inLen);
private:TCPSocket(SOCKET inSocket) : mSocket(inSocket) {}SOCKET mSocket;
};
typedef std::shared_ptr TCPSocketPtr;int TCPSocket::Connect(const SocketAddress& inAddress) {int err = connect(mSocket, &inAddress.mSockAddr, inAddress.GetSize());if (err < 0) return -1;return NO_ERROR;
}
int TCPSocket::Listen(int inBackLog) {int err = listen(mSocket, inBackLog);if (err < 0)return -1;return NO_ERROR;
}
int TCPSocket::Send(const void* inData, int inLen) {int bytesSendCount = send(mSocket,static_cast (inData),inLen, 0);if (bytesSendCount < 0)return -1;return bytesSendCount;
}
int TCPSocket::Receive(void* inBuffer, int inLen) {int bytesReceiveCount = recv(mSocket,static_cast (inBuffer),inLen, 0);if (bytesReceiveCount < 0)return -1;return bytesReceiveCount;
}
TCPSocket::~TCPSocket() {closesocket(mSocket);
}

与类型安全的TCPSocket一起使用的select函数

fd_set* FillSetFromVector(fd_set& outSet,const std::vector* inSockets) {if (inSockets) {FD_ZERO(&outSet);for (const TCPSocketPtr& socket : *inSockets)FD_SET(socket->mSocket, &outSet);return &outSet;}return nullptr;
}void FillVectorFromSet(std::vector* outSockets,const std::vector* inSockets,const fd_set& inSet
) {if (inSockets && outSockets) {outSockets->clear();for (const TCPSocketPtr& socket : *inSockets)if (FD_ISSET(socket->mSocket, &inSet))outSockets->push_back(socket);}
}int Select(const std::vector* inReadSet,std::vector* outReadSet,const std::vector* inWriteSet,std::vector* outWriteSet,const std::vector* inExceptSet,std::vector* outExceptSet
) {fd_set read, write, except;fd_set* readPtr = FillSetFromVector(read, inReadSet);fd_set* writePtr = FillSetFromVector(read, inWriteSet);fd_set* exceptPtr = FillSetFromVector(read, inExceptSet);int toRet = select(0, readPtr, writePtr, exceptPtr, nullptr);if (toRet > 0) {FillVectorFromSet(outReadSet, inReadSet, read);FillVectorFromSet(outWriteSet, inWriteSet, write);FillVectorFromSet(outExceptSet, inExceptSet, except);}return toRet;
}

运行一个TCP服务器循环

void DoTCPLoop() {TCPSocketPtr listenSocket = CreateTCPSocket(INET);SocketAddress receivingAddres(INADDR_ANY, 48000);if (listenSocket->Bind(receivingAddres) != NO_ERROR)return;std::vector readBlockSockets;readBlockSockets.push_back(listenSocket);std::vector readableSockets;while (true) {if (Select(&readBlockSockets, &readableSockets,nullptr, nullptr,nullptr, nullptr)) {for (const TCPSocketPtr& socket : readableSockets) {if (socket == listenSocket) {SocketAddress newCilentAddress;TCPSocketPtr newSocket = listenSocket->Accept(newCilentAddress);readBlockSockets.push_back(newSocket);// 在这了处理新连入 ProcessNewCilent(newSocket, newCilentAddress);}else {char segment[MAX_SEGMENT_SIZE];int dataReceived = socket->Receive(segment, MAX_SEGMENT_SIZE);if (dataReceived > 0) {//在这里处理新数据 ProcessDataFromClient(socket, segment, dataReceived);}}}}}
}

相关内容

热门资讯

长鑫科技下周一上市,发行价为8... 来源:@财联社APP微博 财联社7月23日讯,今日晚间,长鑫科技今日发布公告称,公司股票将于2026...
数字人民币开启从“可用”到“好... 证券时报记者 李颖超 当钱包开立规模持续扩容、试点版图不断铺开之后,数字人民币开始尝试切换发展逻辑。...
收评:超4200只个股上涨!电... 每经编辑:彭水萍 7月23日,A股全天走出缩量修复行情,三大指数小幅收红,盘面黄白线分化明显,中小盘...
商务部:将加快修订出台关于外国... 【大河财立方消息】7月23日,国务院新闻办公室举行新闻发布会,介绍2026年上半年商务工作及运行情况...
「AI新世代」智谱算力出鞘:1... 本报(chinatimes.net.cn)记者石飞月 北京报道 这段时间,智谱的股价经历了一番大起大...
刚刚,封死涨停!16.8亿抢筹... 调整已久的碳酸锂火了! 7月23日,锂矿概念持续走强,*ST威领、川能动力、盛新锂能、永杉锂业、国城...
原创 家... 家庭存款高于多少,才算是国内富裕家庭?社会各界看法不一。有人觉得在国内存款超过1000万,才算是富裕...
事关外贸、消费、新能源等领域 ... 商务部今天发布2026年上半年商务工作及运行情况。 商务部副部长鄢东介绍,上半年商务发展总体情况,...
专访美联储前高级经济学家胡捷 每经记者|周逸斐 每经编辑|廖 丹 法国经济预期增长从0.9%下调为0.6%;德国从0.8%下调为0...
90后接盘清北顶流,仅当3个月... *此图由AI生成 作者| 史大郎&猫哥 来源| 是史大郎&大猫财经Pro 今天讲一个非常生猛的资本游...
罕见反超,GDP十强省份又变了 各省份经济半年报陆续公布,备受关注的十强卡位战正式迎来变局。 今年上半年,安徽以27370亿元GDP...
ETF热门榜:中证短融相关ET... 2026年7月22日,非货币类ETF合计成交5510.77亿元,其中有103只ETF成交额破10亿元...
中国银行 :完成发行300亿元... 中国银行发布公告,公司于2026年7月20日在全国银行间债券市场发行减记型无固定期限资本债券,并于2...
中国真的是五省三市养活全国吗? 近几年,网上流传了一个说法,叫“五省三市养全国”。 简单来说,就是2022年,全国仅广东、江苏、浙江...
摩通:华尔街银行业绩强劲提振欧... 观点网讯:7月22日,华尔街大型银行第二季度业绩表现强劲,提振了市场对欧洲投资银行即将开启的财报季的...
广州拟推动房地产纾困和发展 引... 观点网讯:7月22日,“十五五”时期,广州拟切实推动房地产纾困和发展,引导房地产市场企业转型升级。 ...
AI投入成“无底洞”?谷歌资本... 隔夜美股三大指数集体收低,道琼斯工业指数下跌0.01%报52218.58点,标普500指数跌0.14...
新太空经济从“起势”迈向“成势... 当前,全球正处于新一轮科技革命和产业变革加速演进的关键节点,人工智能、合成生物学、固态电池、高端算力...
金价四连涨,水贝又挤满人 每经记者:赵景致 每经编辑:何小桃,廖丹 记者|赵景致 编辑|何小桃 廖丹杜恒峰校对|金冥羽 金价在...
专访微亿智造董事长张志琦:在“... 本报(chinatimes.net.cn)记者石飞月 上海报道 在刚刚过去的2026世界人工智能大会...