Linux之网络套接字

news2025/1/20 23:36:30

Linux之网络套接字

  • 一.IP地址和端口号
  • 二.TCP和UDP协议
    • 2.1网络字节序
  • 三.socket编程的常见API
  • 四.模拟实现UDP服务器和客户端
  • 五.模拟实现TCP服务器和客户端

一.IP地址和端口号

在了解了网络相关的基础知识之后我们知道了数据在计算机中传输的流程并且发现IP地址在其中占据了确定目标主机的功能,但是在计算机中我们网络通信的行为其实本质都是进程间进行通信。那么在一个主机中有那么多个进程我们要如何准确定位到某个进程呢?
那就要利用端口号了,所谓的端口,就好像是门牌号一样,客户端可以通过ip地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号。
所以我们使用IP地址+端口号的方法就可以找到互联网中唯一的一个进程,其中IP地址用来标记互联网中唯一的一台主机,端口号用来标记主机中唯一的一个进程。但是对于端口号有些地方是和IP地址相同的,一个主机既可以只有一个IP地址也可以有多个IP地址所以一个进程既可以有一个端口号也可以有多个端口号。同时多个主机不可以有一个IP地址多个进程也不可以有一个端口号。
在了解到其中的奥秘之后人们为了更好的称呼这两个概念的组合就又创造了一个概念叫套接字(socket),socket的英译其实是插座的意思从它的英译中我们也可以大概理解它的作用。插座是为了连接两端的物品所以将其具体到网络中socket的作用就是充当应用层和传输层之间的一个抽象层,它将TCP/IP传输层中的一些复杂的操作简单化接口化进而提供给应用层进行调用从而实现进程间的网络通信。

如果端口号port是用来确定计算机中的唯一的一个进程的话,那么可能有些人就有疑问了我们在操作系统的学习的时候使用过进程的pid当时说pid的作用也是确定唯一的一个进程。那么这两者之间有什么联系或者是有什么区别吗?
答案是没有什么区别,它两都可以用来确定主机中唯一的一个进程但是pid是在进行进程管理的时候使用的而端口号是在进行网络通信的时候使用的,这么做的原因其实也很简单就是将进程管理和网络通信进行解耦,pid就专职于进程管理port就专职于网络通信两个人谁都不打扰谁。

二.TCP和UDP协议

之前我们一直在谈协议的分层协议的意义等等但是我们一直没有见过真正的协议如今我们就先学习一下传输层中的两个协议:TCP协议和UDP协议
TCP协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

我们先大致了解一下这两个协议的基本特质其中具体的内容我们等之后再谈,在这里我要强调一个概念:可不可靠传输是协议的特性,这两个都是一个中性词没有好坏之分。就像函数的可复用性一样,可靠传输不代表就比不可靠传输好。可靠传输说明传输的过程更复杂更安全,不可靠传输说明传输的过程比较简单适合一些对数据可靠性不高的场景。

2.1网络字节序

在我们最开始学习C语言的时候我们在介绍数据的存储方式时提到过不同的机器存储方式不同其中分为大端机和小端机。大端模式也就是将低位数据存储到高地址中高位数据存储到低地址中,小端模式是将低位数据存储到低地址里高位数据存储到高地址里。大端模式和小端模式没有明显的优劣之差,如果非要说哪个优一点大端机器的可读性比小端机器好一点。

对于内存的中的数据存储方式有大小端之分那么对于网络传输的数据流是否也要有大小端之分呢?我们又该选择哪个呢?
虽然内存地址有大小端之分但是我们要知道计算机对于数据的读取和写入是从低地址到高地址的这也就是导致为什么大端模式比小端模式可读性更高的原因。
在这里插入图片描述
所以在数据从一个主机发送到另外一个主机读取的过程中发送和读取都是从低地址到高地址的所以网络数据流的数据的存储方法也就是数据的地址是按照大端模式的也就是低地址高字节。
要注意这是一个规定不是你先用哪个方式发送就是哪个方式的,所有的机器在进行TCP/IP通信的时候发送数据的时候都需要将其转换为大端模式。而这个转换为大端模式后的数据存储方式叫做网络字节序。
而为了方便用户将各种整型变量网络字节序化系统提供了几个库函数
在这里插入图片描述

三.socket编程的常见API

  1. 创建socket文件描述符(TCP/UDP, 客户端 + 服务器)
    在这里插入图片描述

对于这三个参数和返回值大家应该有不少的疑惑:为什么返回值是一个文件描述符?socket类型是什么?SOCK_STREAM和SOCK_DGRAM又分别是什么意思?我一个一个的给大家解释一下。
对于返回值是文件描述符的问题我们需要知道网络传输的原理是什么,简单来说两个主机互相传输数据其实就是在服务器和客户端中分别创建一个文档然后服务器会向自己新创建的文档中写入和读取数据,而写入的数据又会通过网络拷贝到客户端新创建的文档中而客户端亦是如此。所以在创建套接字的时候会返回一个文件描述符。
在进行网络编程的时候socket是有很多类型的有些是由于传输的环境不同有些是由于协议的不同,例如unix_socket:域间socket,在同一台主机中进行通信,网络socket:使用ip+port的方式运用于网络通信,原始socket:编写一些网络工具。
SOCK_STREAM和SOCK_DGRAM分别是流格式套接字和数据报格式套接字,这两个概念在我们介绍TCP和UDP协议的提到过这也就是为什么这两个socket类型是和TCP和UDP协议组合在一起的原因。

  1. 绑定端口号 (TCP/UDP, 服务器)
    在这里插入图片描述
    对于第二个参数我需要给大家讲解一下,sockaddr是一个结构体代表的是一个通用的地址类型它其中存储了地址族和套接字中的目标地址信息和端口号。
    在这里插入图片描述
    但是这个结构体是有漏洞的它将目标的地址信息和端口号混在了一起。所以还设计了另外两个结构体sockaddr_in。
    在这里插入图片描述
    而sockaddr和sockaddr_in的就像我们在C++中学习的多态的基类和子类一样,我们在socket编程中使用的一般都是sockaddr_in但是由于某些函数的参数是sockaddr所以我们可以将sockaddr_in强转为sockaddr。

  2. 开始监听socket (TCP, 服务器)
    在这里插入图片描述
    listen函数的作用是将套接字文件描述符从主动转为被动文件描述符,然后用于被动监听客户端的连接,具体的会在讲解协议的时候再谈。如今我们只需要知道怎么用它即可。

  3. 接收请求 (TCP, 服务器)
    在这里插入图片描述
    为什么还要创建一个新文件呢不是已经创建了一个文件了吗为什么不用那个文件描述符呢?
    这个问题其实在使用listen的时候就已经有答案了,大家可以仔细看看listen函数的作用:套接字文件描述符从主动转为被动文件描述符,然后用于被动监听客户端的连接。socket返回的文件描述符已经是被动模式那么肯定需要我们再创建一个新的文件来对客户端进行服务。就好像我们在路上遇到的拉客的人,在我们被他拉进去吃饭之后服务我们的是另外的服务员并不是那个拉我们进来的人。使用套接字文件描述符就是那个拉客的人而这个新的文件描述符才是服务我们的人。

  4. 建立连接 (TCP, 客户端)
    在这里插入图片描述

在大概了解了模拟实现UDP和TCP服务器和客户端时会出现的新函数后我们就可以来分别模拟实现UDP服务器和客户端以及TCP服务器和客户端了。
这两个协议的不同所以模拟实现的过程也不同并且它两服务器和客户端的运行逻辑也不同,到了模拟实现的时候我会具体来说。

四.模拟实现UDP服务器和客户端

我们先来看一下UDP服务器和客户端的运行逻辑
在这里插入图片描述

#Makefile
.PHONY:all
all:udp_server udp_client

udp_server:Main.cc
	g++ -o $@ $^ -std=c++14
udp_client:udp_client.cc
	g++ -o $@ $^ -std=c++14

.PHONY:clean
clean:
	rm -f udp_server udp_client
//log.hpp
#pragma once
#include <iostream>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal
};

// 分为打印到屏幕上,打印到一个文件中,打印到不同的等级的文件中
enum
{
    Screen = 10,
    Onefile,
    Classfile
};

int defaultstyle = Screen;
std::string default_filename = "log.";
std::string logdir = "log";

class Log
{
public:
    Log(int style = defaultstyle, std::string file = default_filename)
        : _style(style), _file(file)
    {
        // mkdir不止是指令,还是一个函数可以用来调用然后创建目录
        mkdir(logdir.c_str(), 0775);
    }

    // 更改模式
    void Enable(int sty)
    {
        std::cout << "Enable success" << std::endl;
        _style = sty;
    }

    std::string LevelToString(int level)
    {
        switch (level)
        {
        case Debug:
            return "Debug";
        case Info:
            return "Info";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        }
        return "null";
    }

    // 返回本地时间
    std::string TimeChangeLocal()
    {
        time_t currtime = time(nullptr);
        // 我们利用time返回的时间是以时间戳的形式返回的不太好看
        // 所以我们可以利用localtime将其转化为一个类
        struct tm *local = localtime(&currtime);
        char time_buffer[128];
        // 利用snprintf以年-月-日 小时-分钟-秒的形式输入到tim_buffer中
        // 注意:tm类的年是以1900年开始的的
        snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d-%d-%d",
                 local->tm_year + 1900, local->tm_mon, local->tm_mday,
                 local->tm_hour, local->tm_min, local->tm_sec);
        return time_buffer;
    }

    // 打印到具体的某个文件中
    void WriteLogToOneFile(const std::string &logname, const std::string &message)
    {
        umask(0);
        int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
        write(fd, message.c_str(), message.size());
        close(fd);
    }

    // 打印到不同等级的文件中
    void WriteLogToClassFile(const std::string &leverstr, const std::string &message)
    {
        std::string logname = logdir;
        logname += "/";
        logname += _file;
        logname += leverstr;
        WriteLogToOneFile(logname, message);
    }

    void Writelog(std::string &str, std::string &message)
    {
        switch (_style)
        {
        case Screen:
            std::cout << message;
            break;
        case Onefile:
            WriteLogToClassFile("all", message);
            break;
        case Classfile:
            WriteLogToClassFile(str, message);
            break;
        default:
            break;
        }
    }

    // 我们想要日志像printf一样打印所以要使用可变参数
    // 但是我们可以利用va_list参数的变量将可变参数存储在变量中
    void LogMessage(int level, const char *format, ...)
    {
        // 左边信息为:[消息等级][时间][进程编号]
        char leftmessage[1024];
        std::string leverstr = LevelToString(level);
        std::string currtime = TimeChangeLocal();
        std::string idstr = std::to_string(getpid());

        // 右边信息为:想输出的内容
        char rightmessage[1024];
        va_list args;
        va_start(args, format);                                      // 将args指向可变参数部分
        vsnprintf(rightmessage, sizeof(rightmessage), format, args); // 将可变参数写入到rightmessage中
        va_end(args);                                                // args == nullptr
        snprintf(leftmessage, sizeof(leftmessage), "[%s][%s][%s]", leverstr.c_str(), currtime.c_str(), idstr.c_str());

        std::string loginfo = leftmessage;
        loginfo += rightmessage;
        Writelog(leverstr, loginfo);
    }
    ~Log()
    {
    }

private:
    int _style;
    std::string _file;
};

Log lg;
//nocopy.hpp
#pragma once

class NoCopy
{
public:
    NoCopy()
    {
    }
    ~NoCopy()
    {
    }
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator=(const NoCopy &) = delete;
};
//Main.cc
#include "udp_server.hpp"
#include <iostream>
#include <memory>

void Usage(std::string process)
{
    std::cout << "Usage:\n" << process << "local_ip local_port\n" << std::endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return -1;
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);
    std::unique_ptr<UdpServer> up_udp = std::make_unique<UdpServer>(ip,port);
    up_udp->Init();
    up_udp->Start();
    return 0;
}

//udp_client.hpp
#include <iostream>
#include <memory>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

const static int defaultsize = 1024;

void Usage(std::string process)
{
    std::cout << "Usage:\n"
              << process << "server_ip server_port\n"
              << std::endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return -1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1.创建socket
    int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socketfd < 0)
    {
        std::cout << "client socket error" << errno << " " << strerror(errno) << std::endl;
        return -1;
    }
    std::cout << "socket create success socket:" << socketfd << std::endl;

    // 2.绑定网络信息
    // client需要绑定网络信息但是不需要显式绑定,在client发送第一条消息时系统会随机给他绑定一个端口号
    // 为什么client需要绑定网络信息是因为client会有很多如果我们显式绑定很容易重复

    // 收集server的信息等到发送时使用
    struct sockaddr_in servermessage;
    memset(&servermessage, 0, sizeof(servermessage));
    servermessage.sin_family = AF_INET;
    servermessage.sin_port = htons(server_port);
    servermessage.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // 3.发送消息
    // 服务器是个死循环,客户端同理
    while (1)
    {
        std::string inbuffer;
        std::cout << "Please Enter:";
        std::getline(std::cin, inbuffer);
        // 发送消息
        ssize_t n = sendto(socketfd, inbuffer.c_str(), inbuffer.size(),0,(struct sockaddr *)&servermessage, sizeof(servermessage));
        if (n > 0)
        {
            // 接收消息
            char outbuffer[defaultsize];
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            ssize_t m = recvfrom(socketfd, &outbuffer, sizeof(outbuffer) - 1,0,(struct sockaddr*)&temp, &len);
            if (m > 0)
            {
                outbuffer[m] = 0;
                std::cout << "server echo:" << outbuffer << std::endl;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }
    }

    close(socketfd);
    return 0;
}

//udp_server.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "log.hpp"
#include "nocopy.hpp"

const static uint16_t defaultport = 8888;
const static int defaultsocketfd = -1;
const static int defaultsize = 1024;

class UdpServer : public NoCopy
{
public:
    UdpServer(std::string ip, uint16_t port = defaultport, int socketfd = defaultsocketfd)
        : _ip(ip), _port(port), _socketfd(socketfd)
    {
    }
    ~UdpServer()
    {
    }
    void Init()
    {
        // 1.创建socket,创建文件信息
        _socketfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socketfd < 0)
        {
            lg.LogMessage(Fatal, "socket error, %d : %s\n", errno, strerror(errno));
            exit(-1);
        }
        lg.LogMessage(Info, "socket create success, socketfd:%d\n", _socketfd);

        // 2.绑定网络信息
        // 保存本地的信息
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        // 在保存本地信息时要将ip和端口号port转换为网络序列
        // ip同时要改为四字节的
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());

        // 本地信息的结构体填完了后要将其加载到内核中
        int n = bind(_socketfd, (struct sockaddr *)&local, sizeof(local));
        if (n != 0)
        {
            lg.LogMessage(Fatal, "bind error, %d : %s\n", errno, strerror(errno));
            exit(-1);
        }
    }
    void Start()
    {
        char buffer[defaultsize];
        // 服务器是一个死循环
        while (1)
        {
            // 保存client客户端的信息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 接收消息
            ssize_t n = recvfrom(_socketfd, &buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say :" << buffer << std::endl;
                // 发送消息
                sendto(_socketfd, &buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, len);
            }
        }
    }

private:
    std::string _ip;
    uint16_t _port;
    int _socketfd;
};

这些代码只能完成最简单的UDP的服务器和客户端仅仅只能发消息,大家还可以利用我们之前学习的程序替换来完成发送并且执行指令的功能以及利用多线程来完成一个聊天室的功能。
这里的代码我就不贴出来了大家需要的话可以私聊我。

五.模拟实现TCP服务器和客户端

在这里插入图片描述

在TCP编程中我们就不需要使用sendto和recvform这两个函数了我们可以直接用write和read来完成文件的读写操作。
下面我也是直接贴出代码,这次的代码就是最终版了比较的复杂,所以我就把主要的关于客户端和服务端的代码贴出来了。

#Makefile
.PHONY:all
all:tcp_server tcp_client

tcp_server:Main.cc 
	g++ -o $@ $^ -std=c++14 -lpthread
tcp_client:tcp_client.cc
	g++ -o $@ $^ -std=c++14 

.PHONY:clean
clean:
	rm -f tcp_server tcp_client
//tcp_client.cc
#include <iostream>
#include <memory>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

#include "log.hpp"

#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)
#define Retry_Count 5

const static int defaultsize = 1024;

void Usage(std::string process)
{
    std::cout << "Usage:\n"
              << process << "server_ip server_port\n"
              << std::endl;
}

bool VisitServer(std::string server_ip, uint16_t server_port, int *count)
{
    // 1.创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    ssize_t m = 0;
    ssize_t r = 0;
    std::string inbuffer;
    char service_list[1024];

    if (sockfd < 0)
    {
        std::cout << "client socket error" << errno << " " << strerror(errno) << std::endl;
        return -1;
    }
    // std::cout << "socket create success socket:" << sockfd << std::endl;

    // 2.绑定网络信息
    // client需要绑定网络信息但是不需要显式绑定,在client发送第一条消息时系统会随机给他绑定一个端口号
    // 为什么client需要绑定网络信息是因为client会有很多如果我们显式绑定很容易重复

    // 收集server的信息等到发送时使用
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr); // 从字符串ip到4字符ip 从系统序列变为网络序列

    bool result = true;

    // 3.连接客户端
    int n = connect(sockfd, CONV(&server), sizeof(server));
    if (n < 0)
    {
        result = false;
        goto END;
    }
    *count = 0;
    std::cout << "connect success"
              << std::endl;

    // 3.发送消息和接收信息
    r = read(sockfd, &service_list, sizeof(service_list) - 1);
    if (r > 0)
    {
        service_list[r] = 0;
        std::cout << "service list is " << service_list << std::endl;
    }

    std::cout << "Please select service: ";
    getline(std::cin, inbuffer);
    write(sockfd, inbuffer.c_str(), inbuffer.size());

    std::cout << "Enter: ";
    getline(std::cin, inbuffer);
    if (inbuffer == "quit")
    {
        return true;
    }

    m = write(sockfd, inbuffer.c_str(), inbuffer.size());
    if (m > 0)
    {
        char outbuffer[defaultsize];
        r = read(sockfd, &outbuffer, sizeof(outbuffer) - 1);
        if (r > 0)
        {
            outbuffer[r] = 0;
            std::cout << outbuffer << std::endl;
        }
        else if (r == 0)
        {
            return result;
        }
        else
        {
            lg.LogMessage(Fatal, "client read error, %d : %s\n", errno, strerror(errno));
            result = false;
            goto END;
        }
    }
    else
    {
        lg.LogMessage(Fatal, "client write error, %d : %s\n", errno, strerror(errno));
        result = false;
        goto END;
    }
END:
    close(sockfd);
    return result;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return -1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);
    int cnt = 1;
    while (cnt <= Retry_Count)
    {
        bool result = VisitServer(server_ip, server_port, &cnt);
        if (result)
        {
            break;
        }
        else
        {
            sleep(1);
            std::cout << "client is retring connect, count: " << cnt << std::endl;
            cnt++;
        }
    }
    if (cnt > Retry_Count)
    {
        std::cout << "server is unonline..." << std::endl;
    }

    return 0;
}

//Main.cc
#include <iostream>
#include <memory>
#include <algorithm>

#include "tcp_server.hpp"
#include "Translate.hpp"
#include "Daemon.hpp"

void Usage(std::string process)
{
    std::cout << "Usage:\n"
              << process << " local_port\n"
              << std::endl;
}

void Interact(int fd, InetAddr addr, std::string inbuffer)
{
    char outbuffer[1024];
    // 先读再写
    while (1)
    {
        ssize_t n = read(fd, &outbuffer, sizeof(outbuffer) - 1);
        if (n > 0)
        {
            outbuffer[n] = 0;
            std::cout << "[" << addr.PrintDebug() << "]# " << outbuffer << std::endl;

            write(fd, inbuffer.c_str(), inbuffer.size());
        }
        else if (n < 0)
        {
            lg.LogMessage(Fatal, "server read error, %d : %s\n", errno, strerror(errno));
            break;
        }
        else if (n == 0)
        {
            lg.LogMessage(Debug, "client quit...\n");
            break;
        }
    }
}

void Ping(int fd, InetAddr addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd:%d\n", addr.PrintDebug().c_str(), "Ping", fd);
    Interact(fd, addr, "pong");
}

translate ts;
void Translate(int fd, InetAddr addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd:%d\n", addr.PrintDebug().c_str(), "Translate", fd);
    char english[128];
    ssize_t m = read(fd, &english, sizeof(english) - 1);
    if (m > 0)
    {
        english[m] = 0;
    }
    std::string chinese = ts.Execute(english);
    write(fd, chinese.c_str(), chinese.size());
    lg.LogMessage(Debug, "translate success, %s->%s\n", english, chinese.c_str());
}

void Transform(int fd, InetAddr addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd:%d\n", addr.PrintDebug().c_str(), "Transform", fd);
    char message[128];
    int n = read(fd, message, sizeof(message) - 1);
    if (n > 0)
        message[n] = 0;
    std::string messagebuf = message;

    std::transform(messagebuf.begin(), messagebuf.end(), messagebuf.begin(), [](unsigned char c)
                   { return std::toupper(c); });
    write(fd, messagebuf.c_str(), messagebuf.size());
    lg.LogMessage(Debug, "transform success, %s->%s\n", message, messagebuf.c_str());
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return -1;
    }
    uint16_t port = std::stoi(argv[1]);

    // 将进程转换为守护进程化
    Daemon(false, false);
    lg.Enable(Classfile);
    lg.LogMessage(Debug, "Daemon success...\n");

    std::unique_ptr<TcpServer> tp_tcp = std::make_unique<TcpServer>(port);

    tp_tcp->EnrollFunc("ping", Ping);
    tp_tcp->EnrollFunc("translate", Translate);
    tp_tcp->EnrollFunc("transform", Transform);

    tp_tcp->Init();
    tp_tcp->Start();
    return 0;
}

//tcp_server.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <functional>
#include <unordered_map>

#include "log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "threadpool.hpp"

#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)
#define ERROR "error"

const static int default_backlog = 1;

using task_t = std::function<void()>;
using callback_t = std::function<void(int, InetAddr &)>;

class TcpServer;

// class ThreadDate
// {
// public:
//     ThreadDate(int fd, TcpServer *server_ptr, struct sockaddr_in addr)
//         : _fd(fd), _server_ptr(server_ptr), _addr(addr)
//     {
//     }
//     ~ThreadDate()
//     {
//         close(_fd);
//     }
//     int GetFd()
//     {
//         return _fd;
//     }
//     TcpServer *GetServer()
//     {
//         return _server_ptr;
//     }
//     InetAddr GetAddr()
//     {
//         return _addr;
//     }

//     int _fd;
//     TcpServer *_server_ptr;
//     InetAddr _addr;
// };

class TcpServer : public NoCopy
{
public:
    TcpServer(uint16_t port)
        : _port(port), _isrunning(false)
    {
    }
    ~TcpServer()
    {
    }
    void Init()
    {
        // 1.创建socket套接字
        _listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenfd < 0)
        {
            lg.LogMessage(Fatal, "socket error, %d : %s\n", errno, strerror(errno));
            exit(-1);
        }
        // 在使用tcp时会出现有多个套接字链接到一个端口上的情况,所以我们需要运行端口复用
        int opt = 1;
        setsockopt(_listenfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        lg.LogMessage(Info, "socket create success, socketfd:%d\n", _listenfd);

        // 2.填充本地网络信息并且bind
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        local.sin_port = htons(_port);

        int n = bind(_listenfd, CONV(&local), sizeof(local));
        if (n != 0)
        {
            lg.LogMessage(Fatal, "bind error, %d : %s\n", errno, strerror(errno));
            exit(-1);
        }
        lg.LogMessage(Info, "bind success, listenfd:%d\n", _listenfd);

        // 3.将socket设置为监听状态
        // 因为tcp协议是依靠链接来进行交互的所以我们需要将客户端的套接字
        // 从主动设置为被动从而监听用户端的连接行为
        int m = listen(_listenfd, default_backlog);
        if (m != 0)
        {
            lg.LogMessage(Fatal, "listen error, %d : %s\n", errno, strerror(errno));
            exit(-1);
        }
        lg.LogMessage(Info, "listen success, listenfd:%d\n", _listenfd);

        threadpool<task_t>::Getinstance()->ThreadStart();
        _funcs.insert(std::make_pair("defaulthandle", std::bind(&TcpServer::DefaultHandle, this, std::placeholders::_1, std::placeholders::_2)));
    }

    void Service(int sockfd, InetAddr addr)
    {
        char outbuffer[1024];
        // 先读再写
        while (1)
        {
            ssize_t n = read(sockfd, &outbuffer, sizeof(outbuffer) - 1);
            if (n > 0)
            {
                outbuffer[n] = 0;
                std::cout << "[" << addr.PrintDebug() << "]# " << outbuffer << std::endl;

                std::string inbuffer = "pong";
                write(sockfd, inbuffer.c_str(), inbuffer.size());
            }
            else if (n < 0)
            {
                lg.LogMessage(Fatal, "server read error, %d : %s\n", errno, strerror(errno));
                break;
            }
            else if (n == 0)
            {
                lg.LogMessage(Debug, "client quit...\n");
                break;
            }
        }
    }

    // 因为这个函数是成员函数所以会带有一个this指针从而导致参数不对
    // 所以我们需要将其转换为静态成员函数
    // static void *HandleFunction(void *args)
    // {
    //     // 线程分离
    //     pthread_detach(pthread_self());
    //     ThreadDate *td = static_cast<ThreadDate *>(args);
    //     td->GetServer()->Service(td->GetFd(),td->GetAddr());
    //     delete td;
    //     return nullptr;
    // }

    void Routinue(int fd, InetAddr addr)
    {
        _funcs["defaulthandle"](fd, addr);
        std::string type = Read(fd);
        lg.LogMessage(Debug, "%s select %s\n", addr.PrintDebug().c_str(), type.c_str());
        if (type == "ping")
        {
            _funcs[type](fd, addr);
        }
        else if (type == "translate")
        {
            _funcs[type](fd, addr);
        }
        else if (type == "transform")
        {
            _funcs[type](fd, addr);
        }
        else
        {
        }
        close(fd);
    }

    void EnrollFunc(std::string name, callback_t func)
    {
        _funcs[name] = func;
    }

    std::string Read(int fd)
    {
        char type[128];
        ssize_t n = read(fd, &type, sizeof(type) - 1);
        if (n > 0)
        {
            type[n] = 0;
            lg.LogMessage(Debug,"server read success, message:%s\n",type);
        }
        else if (n < 0)
        {
            lg.LogMessage(Fatal, "server read error, %d : %s\n", errno, strerror(errno));
            return ERROR;
        }
        else if (n == 0)
        {
            lg.LogMessage(Debug, "client quit...\n");
            return ERROR;
        }
        return type;
    }

    void DefaultHandle(int fd, InetAddr addr)
    {
        std::string lists = "|";
        for (auto &ch : _funcs)
        {
            lists += ch.first;
            lists += "|";
        }
        write(fd, lists.c_str(), lists.size());
    }

    void Start()
    {
        // 服务器是一个死循环
        _isrunning = true;
        signal(SIGCHLD, SIG_IGN);
        while (_isrunning)
        {
            sleep(1);
            // 4.获取连接
            // 当客户端进入监听状态后我们还需要让客户端使用accept函数来获取连接也就是获取一个全新的文件描述符
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listenfd, CONV(&peer), &len);
            if (sockfd < 0)
            {
                lg.LogMessage(Fatal, "accept error, %d : %s\n", errno, strerror(errno));
                continue;
            }
            lg.LogMessage(Debug, "accept success, get a new sockfd:%d\n", sockfd);

            // 5.提供服务
            // 版本1
            // 问题:只能有一个客户端连接服务器
            // Service(sockfd);
            // close(sockfd);

            // 版本2
            // 利用多进程会继承文件描述符表来解决版本1的问题
            //     pid_t id = fork();
            //     if (id == 0)
            //     {
            //         // 子进程
            //         // 子进程需要关闭不需要的文件描述符即_listenfd
            //         close(_listenfd);
            //         pid_t iid = fork();
            //         if (iid > 0)
            //         {
            //             //子进程
            //             exit(0);
            //         }
            //         else
            //         {
            //             //孙子进程
            //             //在子进程退出后孙子进程就变成了孤儿进程
            //             //然后就被系统领养
            //             Service(sockfd);
            //             close(sockfd);
            //             exit(0);
            //         }
            //     }
            //     else if (id > 0)
            //     {
            //         // 父进程
            //         // 子进程需要关闭不需要的文件描述符即sockfd
            //         close(sockfd);
            //         pid_t rid = waitpid(id, nullptr, 0);
            //         if (rid == id)
            //         {
            //             lg.LogMessage(Debug, "wait success...\n");
            //         }
            //     }
            //     else
            //     {
            //         lg.LogMessage(Fatal, "fork error, %d : %s\n", errno, strerror(errno));
            //         close(sockfd);
            //         continue;
            //     }

            // 版本3
            // 利用多进程中子进程退出时会发出信号后修改信号的处理方法
            // 从而让父进程不用等待子进程,系统会自动回收子进程的资源
            // pid_t id = fork();
            // if (id == 0)
            // {
            //     // 子进程
            //     // 子进程需要关闭不需要的文件描述符即_listenfd
            //     close(_listenfd);
            //     Service(sockfd);
            //     close(sockfd);
            //     exit(0);
            // }
            // else if (id > 0)
            // {
            //     // 父进程
            //     // 子进程需要关闭不需要的文件描述符即sockfd
            //     close(sockfd);
            // }
            // else
            // {
            //     lg.LogMessage(Fatal, "fork error, %d : %s\n", errno, strerror(errno));
            //     close(sockfd);
            //     continue;
            // }

            // 版本4
            // 利用多线程会共享一个文件描述符表的特性来让新线程去执行服务
            // 并且将新线程进行分离从而达到自动回收资源的目的
            // ThreadDate *td = new ThreadDate(sockfd, this, peer);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, HandleFunction, td);

            // 版本5
            // 利用线程池提前创建线程并向其分配任务
            // 问题:不能给客户端提供长服务
            task_t ts = std::bind(&TcpServer::Routinue, this, sockfd, InetAddr(peer));
            threadpool<task_t>::Getinstance()->Push(ts);
        }
    }

private:
    uint16_t _port;
    // int _fd;
    int _listenfd;
    bool _isrunning;

    std::unordered_map<std::string, callback_t> _funcs;
};

如果大家需要全部的代码可以去我的gitee中自行拷贝或者私信我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2279560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

迈向 “全能管家” 之路:机器人距离终极蜕变还需几步?

【图片来源于网络&#xff0c;侵删】 这是2024年初Figure公司展示的人形机器人Figure 01&#xff0c;他可以通过观看人类的示范视频&#xff0c;在10小时内经过训练学会煮咖啡&#xff0c;并且这个过程是完全自主没有人为干涉的&#xff01; 【图片来源于网络&#xff0c;侵删】…

几何数据结构之四叉树与八叉树

几何数据结构之四叉树与八叉树 四叉树的定义四叉树深度的计算公式推导假设&#xff1a;计算过程&#xff1a;1. 划分空间&#xff1a;2. 节点容纳的最小距离&#xff1a;3. 解出深度&#xff1a;4. 考虑常数项&#xff1a; 总结&#xff1a; 八叉树 四叉树的定义 四叉树&#…

(一)相机标定——四大坐标系的介绍、对应转换、畸变原理以及OpenCV完整代码实战(C++版)

一、四大坐标系介绍 1&#xff0c;世界坐标系 从这个世界&#xff08;world&#xff09;的视角来看物体 世界坐标系是3D空间坐标&#xff0c;每个点的位置用 ( X w , Y w , Z w ) (X_w,Y_w,Z_w) (Xw​,Yw​,Zw​)表示 2&#xff0c;相机坐标系 相机本身具有一个坐标系&…

嵌入式知识点总结 C/C++ 专题提升(一)-关键字

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.C语言宏中"#“和"##"的用法 1.1.(#)字符串化操作符 1.2.(##)符号连接操作符 2.关键字volatile有什么含意?并举出三个不同的例子? 2.1.并行设备的硬件寄存…

重塑商业智能:大数据改变商业的十种方式

在过去几年间&#xff0c;大数据一直在改变许多公司的运营方式。大数据指的是大量且多样的数据集&#xff0c;当这些数据被妥善收集和分析时&#xff0c;人们能够从中获取有价值的洞察信息。随着大数据逐渐应用于中小型企业&#xff0c;它有望彻底变革企业运营模式。以下将介绍…

基于Spring Boot的车间调度管理系统

基于 Spring Boot 的车间调度管理系统 一、系统概述 基于 Spring Boot 的车间调度管理系统是一个为制造企业车间生产活动提供智能化调度和管理解决方案的软件系统。它利用 Spring Boot 框架的便捷性和高效性&#xff0c;整合车间内的人员、设备、物料、任务等资源&#xff0c…

Ubuntu 24.04 LTS 安装 tailscale 并访问 SMB共享文件夹

Ubuntu 24.04 LTS 安装 tailscale 安装 Tailscale 官方仓库 首先&#xff0c;确保系统包列表是最新的&#xff1a; sudo apt update接下来&#xff0c;安装 Tailscale 所需的仓库和密钥&#xff1a; curl -fsSL https://tailscale.com/install.sh | sh这会自动下载并安装 …

Ubuntu 22.04 TLS 忘记root密码,重启修改的解决办法

1.想办法进入这个界面&#xff0c;我这里是BIOS引导的是按Esc按一下就行&#xff0c;UEFI的貌似是按Shift不得而知&#xff0c;没操作过。下移到Advanced options for Ubuntu&#xff0c;按enter 2.根据使用的内核版本&#xff0c;选择带「recovery mode」字样的内核版本&#…

故障诊断 | BWO白鲸算法优化KELM故障诊断(Matlab)

目录 效果一览文章概述BWO白鲸算法优化KELM故障诊断一、引言1.1、研究背景及意义1.2、故障诊断技术的现状1.3、研究目的与内容二、KELM基本理论2.1、KELM模型简介2.2、核函数的选择2.3、KELM在故障诊断中的应用三、BWO白鲸优化算法3.1、BWO算法基本原理3.2、BWO算法的特点3.3、…

TCP状态转移图详解

状态 描述 LISTEN represents waiting for a connection request from any remote TCP and port. SYN-SENT represents waiting for a matching connection request after having sent a connection request. SYN-RECEIVED represents waiting for a confirming connect…

LabVIEW 水电站厂内经济运行系统

基于 LabVIEW 的水电站经济运行系统&#xff0c;主要针对农村小水电站运行管理的不足进行改进&#xff0c;通过精确控制发电与用水量&#xff0c;最小化耗水量并优化负荷分配&#xff0c;提升水电站的运营效率和经济效益。 ​ LabVIEW 在系统中的功能特点 强大的图形化编程环…

蓝桥杯训练—矩形面积交

文章目录 一、题目二、示例三、解析四、代码 一、题目 平面上有两个矩形&#xff0c;它们的边平行于直角坐标系的X轴或Y轴&#xff0c;对于每个矩形&#xff0c;我们给出它的一对相对顶点的坐标&#xff0c;请你编程写出两个矩形的交的面积 输入格式&#xff1a; 输入包含两行…

Flask简介与安装以及实现一个糕点店的简单流程

目录 1. Flask简介 1.1 Flask的核心特点 1.2 Flask的基本结构 1.3 Flask的常见用法 1.3.1 创建Flask应用 1.3.2 路由和视图函数 1.3.3 动态URL参数 1.3.4 使用模板 1.4 Flask的优点 1.5 总结 2. Flask 环境创建 2.1 创建虚拟环境 2.2 激活虚拟环境 1.3 安装Flask…

基于机器学习的电信用户流失预测与数据分析可视化

完整源码项目包获取→点击文章末尾名片&#xff01; 背景描述 根据IBM商业社区分享团队描述&#xff0c;该数据集为某电信公司在加利福尼亚为7000余位用户&#xff08;个人/家庭&#xff09;提供电话和互联网服务的相关记录。描述用户基本情况&#xff0c;包括每位用户已注册的…

InVideo AI技术浅析(五):生成对抗网络

一、特效生成 1. 工作原理 特效生成是计算机视觉中的高级应用,旨在通过算法生成高质量的视觉特效,如风格迁移、图像到图像的翻译等。InVideo AI 使用生成对抗网络(GAN)来实现这一功能。GAN 通过生成器和判别器两个网络的对抗训练,生成逼真的视觉特效。 2. 关键技术模型…

Linux操作系统的灵魂,深度解析MMU内存管理

在计算机的奇妙世界里&#xff0c;我们每天使用的操作系统看似流畅自如地运行着各类程序&#xff0c;背后实则有着一位默默耕耘的 “幕后英雄”—— 内存管理单元&#xff08;MMU&#xff09;。它虽不常被大众所熟知&#xff0c;却掌控着计算机内存的关键命脉&#xff0c;是保障…

线性代数概述

矩阵与线性代数的关系 矩阵是线性代数的研究对象之一&#xff1a; 矩阵&#xff08;Matrix&#xff09;是一个按照长方阵列排列的复数或实数集合&#xff0c;是线性代数中的核心概念之一。矩阵的定义和性质构成了线性代数中矩阵理论的基础&#xff0c;而矩阵运算则简洁地表示和…

Reactor 模式在 Edis、Nginx 和 Netty 中的应用与高性能网络模式解析

文章目录 参考文章Reactor 模式在 Edis、Nginx 和 Netty 中的应用与高性能网络模式解析一、Reactor 模式二、Redis 中的 Reactor 模式三、Nginx 中的 Reactor 模式四、Netty 中的 Reactor 模式五、Reactor 模式的优势六、总结 参考文章 redis&#xff0c;nginx&#xff0c;net…

企业级NoSQL数据库Redis

1.浏览器缓存过期机制 1.1 最后修改时间 last-modified 浏览器缓存机制是优化网页加载速度和减少服务器负载的重要手段。以下是关于浏览器缓存过期机制、Last-Modified 和 ETag 的详细讲解&#xff1a; 一、Last-Modified 头部 定义&#xff1a;Last-Modified 表示服务器上资源…

【自动驾驶BEV感知之Transformer】

欢迎大家关注我的B站&#xff1a; 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 本文为深蓝学院《BEV感知理论与实践》 的学习笔记 以图书馆看书举例 query&#xff1a;查询&#xff0c;感兴趣的东西 Key&#xff1a;索引&…