前言
这个也我在公司的使用自己写库clib中在win上连接服务器时中只能连接63个的客户端,这个我一开始还以为是自己的配置文件中配置问题呢 就没有注意这个问题。 直到最近自己服务器上业务写完了, 需要压力测试时, 这个问题我可查有几天, 一开始我以为是自己编码中写死了呢, 为什么这样说呢! 在我的知识体系中select最大连接数是1024, 我找很长时间 最后在发现在win 上 FD_SETSIZE的宏定义是64。 这才定位到问题。
使用原生的socket写 服务器或者写客户端都会使用监听文件描述符 读写的状态, 都遇到文件描述符到1024或者64的连接限制, 但是linux内核2.6以后有了epoll就可以完美突破这个限制, 当是在win上和其它平台没有epoll支持, 就要使用select 和poll等等 函数,它们都是连接数 的限制的。
还有一次我面试一家游戏公司, 面试官问到我知道linux上最大连接数, 我当时以为是 一个命令呢, ulimit修改linux连接数呢,现在我了解一点 连接数关于编程方面的限制。与大家分享一下
正文
一, 简单介绍 fd_set使用
1, fd_set 结构中定义结构体的大小
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */// FD_SETSIZE: 最大连接数据
} fd_set;
2, 添加文件描述符到 fd_set中的数组中fd_array
#define FD_SET(fd, set) do { \
u_int __i; \
for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \ // 查找fd_array数组中文件描述符的位置
if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \
break; \
} \
} \
if (__i == ((fd_set FAR *)(set))->fd_count) { \
if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \ // 插入文件描述符到数组中 并且增加 管理的文件多少 fd_count
((fd_set FAR *)(set))->fd_array[__i] = (fd); \
((fd_set FAR *)(set))->fd_count++; \
} \
} \
} while(0, 0)
3, 在数组中删除文件描述符
#define FD_CLR(fd, set) do { \
u_int __i; \
for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \
if (((fd_set FAR *)(set))->fd_array[__i] == fd) { \
while (__i < ((fd_set FAR *)(set))->fd_count-1) { \
((fd_set FAR *)(set))->fd_array[__i] = \
((fd_set FAR *)(set))->fd_array[__i+1]; \
__i++; \
} \
((fd_set FAR *)(set))->fd_count--; \
break; \
} \
} \
} while(0, 0)
extern int PASCAL FAR __WSAFDIsSet(SOCKET fd, fd_set FAR *);
#define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))
如果你还是不了解可以看我以前写文章地址:https://chensongpoixs.github.io/2017/10/31/Linux网络编程之IO复用(select函数的使用)/
二, 分析问题怎么突破FD_SETSIZE的限制
1, FD_SETSIZE的win宏定义是64
我们只要自己定义 fd_set 的结构 数组的大小就可以了
这里我们就要使用适配器(fd_set_adapter)怎么一个东西 , 我相信写android的界面的知道怎么一个东西 , 我也前几天写android东西,才想起以前写android一些东西, 发现android一些设计模式太多了, 适合我们学习的设计模式的,挺搞笑的 我连java的基本List数据结构都不会使用, 我以为跟C++的数据结构一样的使用方式呢, 足足花了我半天的时间,我才知道要 new一下才可以使用, 要给这个对象申请内存空间。 毕竟android系统是google开源出来了, 里面有很多东西要学习的
这个适配器我也看boost中源码有适配器对win做了适配器,boost库也是C++中著名网络库,
我是借鉴boost1.6是动态增加数组
namespace chen {
class cfd_set_adapter
{
private:
enum { default_fd_set_size = 1024 };
struct _fd_set {
uint32 m_count; //typedef unsigned int u_int;
socket_type m_fd_array[1];//动态申请数组大小
};
public:
explicit cfd_set_adapter();
~cfd_set_adapter();
public:
bool init();
void destroy();
public:
bool set(socket_type descriptor);
// FD_ISSET
bool is_set(socket_type descriptor) const;
// []
operator fd_set*();
void reset();
socket_type max_descriptor() const
{
return m_max_descriptor_fd;
}
private:
//扩容
void reserve(uint32 size);
private:
cfd_set_adapter(const cfd_set_adapter&);
cfd_set_adapter& operator=(const cfd_set_adapter&);
private:
_fd_set* m_fd_set;
uint32 m_capacity;// 数组的大小
uint32 m_max_descriptor_fd;
};
} // namespace chen
2, 分析boost 库中怎么使用select 适配器的fd_set_adapter
使用在主要在run方法中
主要思想是 文件描述符队列 op_queue
fd_sets_是分别放read,write和except三个数组
然后把队列中文件描述符放到fd_set_三个文件描述符数组中 在放到select函数中
void select_reactor::run(bool block, op_queue<operation>& ops)
{
boost::asio::detail::mutex::scoped_lock lock(mutex_);
#if defined(BOOST_ASIO_HAS_IOCP)
// Check if the thread is supposed to stop.
if (stop_thread_)
return;
#endif // defined(BOOST_ASIO_HAS_IOCP)
// Set up the descriptor sets.
for (int i = 0; i < max_select_ops; ++i)
fd_sets_[i].reset();
fd_sets_[read_op].set(interrupter_.read_descriptor());
socket_type max_fd = 0;
bool have_work_to_do = !timer_queues_.all_empty();
for (int i = 0; i < max_select_ops; ++i)
{
have_work_to_do = have_work_to_do || !op_queue_[i].empty();
fd_sets_[i].set(op_queue_[i], ops);
if (fd_sets_[i].max_descriptor() > max_fd)
max_fd = fd_sets_[i].max_descriptor();
}
#if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
// Connection operations on Windows use both except and write fd_sets.
have_work_to_do = have_work_to_do || !op_queue_[connect_op].empty();
fd_sets_[write_op].set(op_queue_[connect_op], ops);
if (fd_sets_[write_op].max_descriptor() > max_fd)
max_fd = fd_sets_[write_op].max_descriptor();
fd_sets_[except_op].set(op_queue_[connect_op], ops);
if (fd_sets_[except_op].max_descriptor() > max_fd)
max_fd = fd_sets_[except_op].max_descriptor();
#endif // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
// We can return immediately if there's no work to do and the reactor is
// not supposed to block.
if (!block && !have_work_to_do)
return;
// Determine how long to block while waiting for events.
timeval tv_buf = { 0, 0 };
timeval* tv = block ? get_timeout(tv_buf) : &tv_buf;
lock.unlock();
// Block on the select call until descriptors become ready.
boost::system::error_code ec;
int retval = socket_ops::select(static_cast<int>(max_fd + 1),
fd_sets_[read_op], fd_sets_[write_op], fd_sets_[except_op], tv, ec);
// Reset the interrupter.
if (retval > 0 && fd_sets_[read_op].is_set(interrupter_.read_descriptor()))
{
interrupter_.reset();
--retval;
}
lock.lock();
// Dispatch all ready operations.
if (retval > 0)
{
#if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
// Connection operations on Windows use both except and write fd_sets.
fd_sets_[except_op].perform(op_queue_[connect_op], ops);
fd_sets_[write_op].perform(op_queue_[connect_op], ops);
#endif // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
// Exception operations must be processed first to ensure that any
// out-of-band data is read before normal data.
for (int i = max_select_ops - 1; i >= 0; --i)
fd_sets_[i].perform(op_queue_[i], ops);
}
timer_queues_.get_ready_timers(ops);
}
三, 测试效果图
因为在win限制是64 我使用200 机器人连接gateway gateway的session回话id 从1000 到800, 机器人每30秒发送一个数据包。
下面的测试流程图
源码路径
https://github.com/chensongpoixs/clib