基於select函數實現的tcp簡單服務器

源碼地址 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

程序執行效果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章