select使用实现多客户端连接

头文件

#ifndef TCPCLIENT_H
#define TCPCLIENT_H

#include <mutex>
#include <string>
#include <condition_variable>
#include <future>
#include <set>
#include <map>

namespace PRONAME {

using namespace PRONAME;

class TcpClient
{
public:
    TcpClient();
    ~TcpClient();
    void run();
    bool write(const void *data, unsigned int len,std::string ip, uint16_t port);
private:
    void read();
    void setNoblock(int fd);
    void setKeepAlive(int fd);
    int  connectToHost(std::string ip,  uint16_t port);
    void disConnectToHost(int fd);
    void disConnectToHost();
    int  fdSet(fd_set* read_fdset);
    int  isFdset(fd_set* read_fdset);
    int  getReadAvailable();
    void removeZombieConnection();
private:
    size_t m_maxConnect{1010};
    std::mutex m_mutex;
    std::condition_variable m_wait;
    std::set<int> m_clientSet;
    std::map<int,std::chrono::steady_clock::time_point> m_clientTimeout;//fd activityTime
    std::chrono::steady_clock::time_point m_activityTime;
};

}

#endif // TCPCLIENT_H

源文件

#include "tcpclient.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/tcp.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <fcntl.h>

namespace PRONAME {


TcpClient::TcpClient()
{

}

TcpClient::~TcpClient()
{

}

int TcpClient::fdSet(fd_set *read_fdset)
{
    do
    {
        if(m_clientSet.empty()){
            break;
        }
        FD_ZERO(read_fdset);
        for(auto client:m_clientSet){
            FD_SET(client, read_fdset);
        }
        auto it = m_clientSet.end();
        it--;
        return *it;
    }while(0);

    return -1;
}

int TcpClient::isFdset(fd_set* read_fdset)
{
    for(auto client:m_clientSet){
        if(FD_ISSET(client,read_fdset)){
            return client;
        }
    }
    return -1;
}

int TcpClient::getReadAvailable()
{
    //fd set
    fd_set read_fdset;
    int fd = fdSet(&read_fdset);
    if(fd < 0 ){
        return 0;
    }
    //select
    struct  timeval timeout;
    timeout.tv_sec  = 0;
    timeout.tv_usec = 1000*50 ;
    int ret = select(fd + 1 , &read_fdset,  nullptr ,  nullptr , &timeout);
    //read available
    if(ret > 0){
        return isFdset(&read_fdset);
    }
    //timeout
    if  (ret == 0 ){
        return 0;
    }
    //read error
    return  -1;
}

void TcpClient::removeZombieConnection()
{
    if(m_maxConnect >= m_clientTimeout.size()){
        return;
    }
    auto it = m_clientTimeout.begin();
    auto time_point = it->second;
    int fd = it->first;
    for(;it!= m_clientTimeout.end();it++)
    {
        if(it->second < time_point){
            fd         = it->first;
            time_point = it->second;
        }
    }
    if(fd > 0){
        disConnectToHost(fd);
    }
}

void TcpClient::run()
{
    while (1)
    {
        this->read();
    }
}

bool TcpClient::write(const void *data, unsigned int len, std::string ip, uint16_t port)
{
    int fd = connectToHost(ip,port);
    if(fd < 0){
        return false;
    }
    bool bRet = (len == send(fd,data,len,MSG_NOSIGNAL));
    if(!bRet){
        perror("tcp send");
        std::cout << "tcp send error" << std::endl;
    }
    m_activityTime = std::chrono::steady_clock::now();
    m_wait.notify_all();
    return  bRet;
}


void TcpClient::read()
{
    std::unique_lock<std::mutex> lck(m_mutex);

    if(m_clientSet.empty()){
        m_wait.wait_for(lck,std::chrono::milliseconds(50));
    }
    if(m_clientSet.empty()){
        return;
    }
    removeZombieConnection();
    int fd = getReadAvailable();
    if(fd < 1){
        //长时间没活动,清空全部连接
        auto now = std::chrono::steady_clock::now();
        auto diff = now - m_activityTime;
        auto X = std::chrono::duration_cast<std::chrono::hours>(diff).count();
        if(X > 1){
            disConnectToHost();
        }
        return;
    }
    char buf[1024]={0};
    int nRet = recv(fd,buf,1024,MSG_NOSIGNAL);
    if(nRet <= 0){
        std::cout << "server disconnect." << std::endl;
        disConnectToHost(fd);
    }else{
        m_activityTime = std::chrono::steady_clock::now();
        m_clientTimeout[fd] = m_activityTime;
        std::cout << "tcp read len:" <<fd<<" "<<nRet<< buf<<std::endl;
    }
}

void TcpClient::setKeepAlive(int fd)
{
    int keep_alive = 1;//打开KeepAlive机制
    int keep_idle = 5;//有5秒钟没有任何数据包传输,则启动保活机制,发送TCP Keep-alive机制。默认为2小时。
    int keep_interval = 1;//值为1秒,代表如果启动保活机制,则每隔1秒发送一个Keep-alive包。默认为75秒
    int keep_count = 3;//值为3,代表如果对端对3次Keep-alive数据包都没有正常响应,则判断对端已经崩溃。默认为9
    int ret = 0;

    if (-1 == (ret = setsockopt(fd, SOL_SOCKET,  SO_KEEPALIVE, &keep_alive, sizeof(keep_alive)))) {
        perror("setsockopt-SO_KEEPALIVE\n");
    }
    if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE,  &keep_idle, sizeof(keep_idle)))) {
        perror("setsockopt-TCP_KEEPIDLE\n");
    }
    if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keep_interval, sizeof(keep_interval)))) {
        perror("setsockopt-TCP_KEEPINTVL\n");
    }
    if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT,   &keep_count, sizeof(keep_count)))) {
        perror("setsockopt-TCP_KEEPCNT\n");
    }
}

void TcpClient::setNoblock(int fd)
{
    int curr_flags = fcntl(fd, F_GETFL);
    if (curr_flags < 0) {
        perror("fcntl-F_GETFL\n");
    }
    else if (fcntl(fd, F_SETFL, curr_flags|O_NONBLOCK) != 0) {
        perror("fcntl-F_SETFL O_NONBLOCK\n");
    }
}

int TcpClient::connectToHost(std::string ip, uint16_t port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0){
        return sock;
    }
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(ip.c_str());
    serv_addr.sin_port = htons(port);
    if(0 == connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))){
        setKeepAlive(sock);
        setNoblock(sock);
        m_mutex.lock();
        m_clientSet.insert(sock);
        m_clientTimeout[sock]=std::chrono::steady_clock::now();
        m_mutex.unlock();
    }else{
        perror("connectToHost error\n");
        ::close(sock);
        sock = -1;
    }
    return sock;
}

void TcpClient::disConnectToHost(int fd)
{
    auto it = m_clientSet.find(fd);
    if(m_clientSet.end() != it){
        m_clientSet.erase(it);
    }
    auto it1 = m_clientTimeout.find(fd);
    if(m_clientTimeout.end() != it1){
        m_clientTimeout.erase(it1);
    }
    ::close(fd);

}

void TcpClient::disConnectToHost()
{
    for(auto fd:m_clientSet){
        ::close(fd);
    }
    m_clientSet.clear();
}


}

测试

#include <iostream>
#include "tcpclient.h"
#include <thread>

using namespace std;

int main()
{
    cout << "Hello World!" << endl;

    PRONAME::TcpClient tcp;
    std::thread([](PRONAME::TcpClient* tcp){tcp->run();},&tcp).detach();

    for(int i = 0;i< 5;i++){
        std::cout<<"write: "<< tcp.write("hello",5,"192.168.2.101",5123);
    }
    std::cout<<"write: \n"<< tcp.write("hello",5,"192.168.2.101",5123);

    getchar();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值