基于select函数实现的tcp简单服务器

2023-05-16

select 实现 tcp demo

  • 回忆TCP的连接过程
  • select
    • select 的封装
    • tcp类的封装
    • 程序流程
  • 程序
    • cli.cpp 客户端建立连接
    • SelectSvr.hpp 服务器的头文件
    • Tcpsvr.hpp
    • main.cpp 主函数
    • makefile
    • 程序执行效果

源码地址 https://github.com/duchenlong/linux-text/tree/master/network/IO/SelectTcp

在这里插入图片描述

回忆TCP的连接过程

  • 服务端

在这里插入图片描述

  • 客户端

在这里插入图片描述

select

关于select 的介绍,可以参考上一篇博客 https://blog.csdn.net/duchenlong/article/details/106758718

我们使用select函数的地方,是我们服务端所在的地方。

利用select可以监控可读事件的特性,将客户端所发起的连接产生的新的套接字(也就是一个文件描述符),添加到可读事件的集合中。
在这里插入图片描述
由于select中涉及到的处理有点多,我们可以对这些功能进行封装,构造一个selectSvr的类

select 的封装

在这里插入图片描述

tcp类的封装

在这里插入图片描述

程序流程

在这里插入图片描述

程序

cli.cpp 客户端建立连接

#include "Tcpsvr.hpp"
#include <cstdlib>
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        cout<<"请输入正确的参数 [./client] [ip] [port]"<<endl;
        return 0;
    }

    string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    Tcpsvr tcp;
    if(!tcp.CreateSocket())
    {
        return 0;
    }

    if(!tcp.Connect(ip,port))
    {
        return 0;
    }

    while(1)
    {
        cout<<"请输入想给服务端说的话 : ";
        fflush(stdout);
        string buf;
        cin>>buf;
        tcp.Send(buf);
        buf.clear(); 
        //等待接收数据
        
        if(!tcp.Recv(buf))
        {
            cout<<"我方程序退出"<<endl;
            break;
        }
        cout<<"服务端说 : "<<buf<<endl;
    }
    tcp.Close();

    return 0;
}

SelectSvr.hpp 服务器的头文件

#pragma once 

#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <vector>
#include <cstdio>

#include "Tcpsvr.hpp"

using namespace std;

class SelectSvr
{
    public:
        SelectSvr()
        {
            _maxFd = -1;
            FD_ZERO(&_readfds);
        }

        //添加文件描述符
        void AddFd(int fd)
        {
            FD_SET(fd,&_readfds);

            //更新最大 文件描述符 数值
            if(fd > _maxFd)
                _maxFd = fd;
        }

        //删除文件描述符
        void DeleteFd(int fd)
        {
            FD_CLR(fd,&_readfds);

            //更新最大 文件描述符 数值
            for(int i = _maxFd; i >= 0; i--)
                if(FD_ISSET(i,&_readfds) == 1)//找第一个存在的文件描述符
                {
                    _maxFd = i;
                    break;
                }
        }

        bool SelectWait(vector<Tcpsvr>& vec)
        {
            struct timeval tv;
            tv.tv_sec = 0;
            tv.tv_usec = 3000;//设置超时时间

            fd_set tmp = _readfds;
            int ret = select(_maxFd + 1,&tmp,NULL,NULL,&tv);
            if(ret < 0)
            {
                perror("select");
                return false;
            }
            else if(ret == 0)
            {
                //cout<<"select timeout"<<endl;
                return false;
            }

            //程序正常的流程
            for(int i = 0; i <=  _maxFd; i++)
                if(FD_ISSET(i,&tmp))
                {
                    //返回就绪的 文件描述符i 的类对象
                    Tcpsvr ts;
                    ts.SetFd(i);
                    vec.push_back(ts);
                }

            return true;
        }

    private:
        int _maxFd;
        fd_set _readfds;
};

Tcpsvr.hpp

#pragma once 

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdio>
#include <iostream>
#include <string>

using namespace  std;

class Tcpsvr 
{
    public:
        Tcpsvr()
            :_sockfd(-1)
        {}

        //创建套接字
        bool CreateSocket()
        {
            int ret = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
            if(ret < 0)
            {
                perror("socket");
                return false;
            }
            _sockfd = ret;

            //解决地址复用问题
            int  i = 0;
            setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));
            return true;
        }

        //绑定地址信息
        bool Bind(const string& ip,uint16_t port)
        {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());

            int ret = bind(_sockfd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in));
            if(ret < 0)
            {
                perror("bind");
                return false;
            }
            return true;
        }

        //建立监听
        bool Listen(int backlog = 5)
        {
            int ret = listen(_sockfd,backlog);
            if(ret < 0)
            {
                perror("listen");
                return false;
            }
            return true;
        }

        //获取连接
        bool Accept(struct sockaddr_in* addr,Tcpsvr& ts)
        {
            //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

            socklen_t addrLen = sizeof(struct sockaddr_in);
            int serFd = accept(_sockfd,(struct sockaddr*)addr,&addrLen);
            if(serFd < 0)
            {
                perror("accept");
                return false;
            }

            ts._sockfd = serFd;
            return true;
        }

        //发起连接
        bool Connect(string& ip,uint16_t port)
        {
            //int connect(int sockfd, const struct sockaddr *addr,
            //                   socklen_t addrlen);
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());

            int ret = connect(_sockfd,(struct sockaddr*)&addr,sizeof(addr));
            if(ret < 0)
            {
                perror("connect");
                return false;
            }
            return true;
        }

        //发送数据
        bool Send(string& data)
        {
            // ssize_t send(int sockfd, const void *buf, size_t len, int flags);
            ssize_t ret  = send(_sockfd,data.c_str(),data.size(),0);
            if(ret == 0)
            {
                perror("send");
                return false;
            }
            return true;
        }

        //接收数据
        bool Recv(string& data)
        {
            // ssize_t recv(int sockfd, void *buf, size_t len, int flags);
            char buf[1024] = {'\0'};
            ssize_t ret = recv(_sockfd,buf,sizeof(buf) - 1,0);
            if(ret < 0)
            {
                perror("recv");
                return false;
            }
            else if (ret == 0)
            {
                cout<<"对方关闭了连接"<<endl;
                return false;
            }
            

            //ret 就是所接收数据的大小
            data.assign(buf,ret);
            return true;
        }

        //关闭套接字
        void Close()
        {
            close(_sockfd);
            _sockfd = -1;
        }

        //设置套接字
        inline void SetFd(int fd)
        {
            _sockfd = fd;
        }

        //获得套接字
        inline int GetFd()
        {
            return _sockfd;
        }
    private:
        int _sockfd;
};

main.cpp 主函数

#include "SelectSvr.hpp"

#define CHECK_RET(p) if(p != true) {return -1;}

int main()
{
    Tcpsvr listen_ts;//监听的对象
    CHECK_RET(listen_ts.CreateSocket());
    CHECK_RET(listen_ts.Bind("0.0.0.0",19998));
    CHECK_RET(listen_ts.Listen());
    
    //建立服务端
    SelectSvr ss;
    ss.AddFd(listen_ts.GetFd());

    while(1)
    {
        //监控
        vector<Tcpsvr> vec;
        if(!ss.SelectWait(vec))
            continue;
        
        for(size_t i = 0; i < vec.size(); i++)
        {
            //接收新连接
            if(listen_ts.GetFd() == vec[i].GetFd())
            {
                struct sockaddr_in addr;
                Tcpsvr peerts;
                listen_ts.Accept(&addr,peerts);

                cout<<"有一个新连接 : ip = [" << inet_ntoa(addr.sin_addr)<<"] ";
                cout<<"port = ["<<ntohs(addr.sin_port)<<" ]"<<endl;

                //新创建的条件字 添加到select事件集合中
                ss.AddFd(peerts.GetFd());
            }
            else //接收数据
            {
                string data;
                bool ret = vec[i].Recv(data);

                //如果对端关闭连接,或者发送了错误
                //进行终止
                if(!ret)
                {
                    ss.DeleteFd(vec[i].GetFd());
                    vec[i].Close();
                    continue;
                }
                cout<<"客户端["<<vec[i].GetFd()<<  "]发送的数据是 : "<<data<<endl;

                //给客户端回复数据
                fflush(stdout);
                cout<<"回复客户端数据 : ";
                cin>>data;
                vec[i].Send(data);
            }
        }
    }
    return 0;
}

makefile

all:main cli

cli:cli.cpp 
	g++ $^ -o $@ -g

main:main.cpp
	g++ $^ -o $@ -g

程序执行效果

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于select函数实现的tcp简单服务器 的相关文章

随机推荐