Linux网络编程——tcp套接字

文章目录

    • 主要代码
    • 关于构造
    • listen监听
    • accept
    • telnet测试
    • 读取信息
    • 掉线重连
    • 翻译服务器演示

本章Gitee仓库:tcp套接字

主要代码

客户端:

#pragma once

#include"Log.hpp"

#include
#include

#include
#include
#include
#include

#include
#include
#include
#include

#include"threadPool.hpp"
#include"Task.hpp"

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5;  //不要设置太大
Log log;


enum{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LITSEN_ERR
};
class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *t)
    :t_sockfd_(fd), t_clientip_(ip), t_clientport_(port), t_tsvr_(t)
    {}
public:
    int t_sockfd_;
    std::string t_clientip_;
    uint16_t t_clientport_;
    TcpServer *t_tsvr_; //需要this指针
};

class TcpServer
{

public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip)
    :listensockfd_(defaultfd)
    ,port_(port)
    ,ip_(ip)
    {}

    //初始化服务器
    void Init()
    {
        //创建套接字
        listensockfd_ = socket(AF_INET, SOCK_STREAM, 0); //sock_stream提供字节流服务--tcp
        if(listensockfd_ < 0)
        {
            log(Fatal, "create socket, errno: %d, errstring: %s",errno, strerror(errno));
            exit(SOCKET_ERR);
        }

        log(Info, "create socket success, sockfd: %d",listensockfd_);

        int opt = 1;
        setsockopt(listensockfd_, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));  //防止偶发性服务器无法进行立即重启

        //本地套接字信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        //填充网络信息
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));

        //bind
        int bd = bind(listensockfd_, (struct sockaddr*)&local, sizeof(local));
        if(bd < 0)
        {
            log(Fatal, "bind error, errno: %d, errstring: %s",errno, strerror(errno));
            exit(BIND_ERR);
        }
        log(Info, "bind success");


        //tcp面向连接, 通信之前要建立连接
        //监听
        if(listen(listensockfd_, backlog) < 0)
        {
            log(Fatal, "listen error, errno: %d, errstring: %s",errno, strerror(errno));
            exit(LITSEN_ERR);
        }
        log(Info, "listen success");

    }

    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        //子线程无需关闭文件描述符
        ThreadData *td = static_cast<ThreadData*>(args);

        td->t_tsvr_->Service(td->t_sockfd_, td->t_clientip_, td->t_clientport_);

        delete td;
    }

    void Start()
    {
        signal(SIGPIPE, SIG_IGN);
        threadPool<Task>::GetInstance()->Start();
        //signal(SIGCHLD, SIG_IGN);  //直接忽略进程等待 V2
        log(Info, "server is running...");
        while(true)
        {
            //获取新链接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensockfd_, (struct sockaddr*)&client, &len);
            if(sockfd < 0)
            {
                log(Warning, "accpet error, errno: %d, errstring: %s",errno, strerror(errno));
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            log(Info, "get a new link..., sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip, clientport);
            //根据新链接进行通信
            
            //V1--单进程
            //Service(sockfd, clientip, clientport);
            //close(sockfd);
            
            //V2--多进程
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     //child
            //     close(listensockfd_);  //子进程可以看到父进程的文件描述符表,关闭不需要的 
            //     if(fork() > 0)  exit(0);//父进程创建的子进程直接退出
            //     Service(sockfd, clientip, clientport);  //孙子进程执行, 由于孙子的父进程退出, 由系统领养
            //     close(sockfd);
            //     exit(0);
            // }
            // //father
            // close(sockfd);  //存在引用计数,不会这个关闭这个文件
            // pid_t tid = waitpid(id, nullptr, 0);
            // (void)tid;
            
            //V3--多线程(创建进程成本较高,换线程)
            // ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Routine, td);
            //pthread_join(tid, nullptr);   //已进行线程分离,无需等待(并发执行)
            
            //V4--线程池
            Task t(sockfd, clientip, clientport);
            threadPool<Task>::GetInstance()->Push(t);

            //sleep(1); 
        }
    }
    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        char buffer[4096];
        while(true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if(n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_str = "tcpserver echo# ";
                echo_str += buffer;

                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if(n == 0)
            {
                log(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                log(Warning, "read error, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
            memset(buffer, 0, sizeof(buffer));
        }
    }

    ~TcpServer(){}
private:
    int listensockfd_;
    uint16_t port_;
    std::string ip_;
};

客户端:

#include
#include
#include

#include
#include
#include
#include

void Usage(const std::string &proc)
{
    std::cout << "\n\tUsage: " << proc << "serverip serverport" << std::endl;
}

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 填充信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));



    //连接模块
    while (true)
    {
        int sockfd = 0;
        int cnt = 5;
        bool isreconnect = false;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return 1;
        }
        do
        {
            // tcp客户端也无需显示bind
            // 向目标发起连接(connect的时候进行自动随机bind)
            int cn = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (cn < 0)
            {
                isreconnect = true;
                cnt--;  //重连5秒
                std::cerr << "connet error, reconnect: " << cnt << std::endl;
                close(sockfd);
                sleep(1);   //每隔一秒重连一次
            }
            else
            {
                break;
            }
        }while(cnt && isreconnect);

        if(cnt == 0)
        {
            std::cerr << "server offline..." << std::endl;
            break;
        }

        //服务模块
        //while (true)
        {
            std::string message;
            std::cout << "Please Enter# ";
            std::getline(std::cin, message);
            if (message.empty())
                continue;

            int wn = write(sockfd, message.c_str(), message.size());
            if (wn < 0)
            {
                std::cerr << "write error" << std::endl;
                //break;
            }

            char inbuffer[4096];
            int rn = read(sockfd, inbuffer, sizeof(inbuffer));
            if (rn > 0)
            {
                inbuffer[rn] = 0;
                std::cout << inbuffer << std::endl;
            }
            else
            {
                //break;
            }
        }
        close(sockfd);
    }

    return 0;
}

关于构造

关于构造和初始化,可以直接在构造的时候,将服务器初始化,那为什么还要写到init初始化函数里面呢?

构造尽量简单一点,不要做一些“有风险”的操作。

listen监听

tcp是面向连接的,通信之前要建立连接,服务器处于等待连接到来的状态:

#include 
#include 
int listen(int sockfd, int backlog);

Linux网络编程——tcp套接字_第1张图片

accept

获取新链接:

#include 
#include 
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

后两个参数输出型参数,获取对方的信息

这里返回也是一个套接字,这和最开始创建的listensockfd_有什么区别呢?

这就好比去吃饭,门口有一个拉客的,问“帅哥,来不来我们这里吃饭啊”,如果你去了,那就是“欢迎光临,几位呀”,你回答“我和我女朋友”,然后这个人大喊“两位客人,来一个服务员”,这时候就来了一个服务员招呼你坐下,然后给你菜单,点完菜之后给你上菜;在这个期间站在门口的拉客的人,一直在招揽客人,有人来就喊服务员招招待,人家不来就继续拉。

listensockdf_就是这个拉客的,真正给我们提供服务的是accept返回的

telnet测试

telnet可以对指定服务的远程连接

image-20240205192444936

127.0.0.1本地环回,再加上端口号就可以测试了

ctrl + ],然后回车

读取信息

tcp是面向字节流的,管道是面向字节流的、读取普通文件也是面向字节流的,所以可以采用read进行读取。

掉线重连

当读端关闭之后,系统会杀掉写端,采用signal(SIGPIPE, SIG_IGN)忽略这个信号

翻译服务器演示

Linux网络编程——tcp套接字_第2张图片

你可能感兴趣的:(Linux网络编程,原创,网络,linux,tcp/ip)