【Linux网络编程】网络编程套接字(TCP服务器)

news2024/10/5 13:38:33

【Linux网络编程】网络编程套接字(TCP服务器)

目录

  • 【Linux网络编程】网络编程套接字(TCP服务器)
    • 地址转换函数
      • 关于inet_ntoa
    • 简单的TCP网络程序
      • TCP sockot API详解
        • socket()
        • bind()
        • listen()
        • accept();
        • connect
    • 完整的TCP服务器代码(线程池版)

作者:爱写代码的刚子

时间:2024.4.4

前言:本篇博客主要介绍TCP及其服务器编码

地址转换函数

只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP地址 但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换

字符串转in_addr的函数

#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
int inet_pton(int family,const char *strptr,void *addrptr);

in_addr转字符串的函数:

char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);

其中inet_ptoninet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。

关于inet_ntoa

inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是否需要调用者手动释放呢?

在这里插入图片描述

inet_ntoa函数将这个返回结果放到了静态存储区,不需要我们进行手动释放,如果多次调用会出现问题吗?

  • 进行一段代码演示:

在这里插入图片描述

在这里插入图片描述

因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果

  • 在APUE中,明确提出inet_ntoa不是线程安全函数
  • 但在centos7上测试没有出现问题,可能内部的实现加了互斥锁
  • 在多线程环境下,推荐使用inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题
  • 可以用以下代码进行多线程的测试
// Created Time:    2024-04-04 10:48:05
// Modified Time:   2024-04-04 11:00:43

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int cnt =100;

void* Func1(void* p) {
    struct sockaddr_in* addr = (struct sockaddr_in*)p;
    while (cnt>0) {
      char* ptr = inet_ntoa(addr->sin_addr);
      printf("addr1: %s,cnt: %d\n", ptr,cnt);
      cnt--;
    }
    return NULL;
}
void* Func2(void* p) {
    struct sockaddr_in* addr = (struct sockaddr_in*)p;
    while (cnt>0) {
      char* ptr = inet_ntoa(addr->sin_addr);
      printf("addr2: %s,cnt: %d\n", ptr,cnt);
      cnt--;
    }
    return NULL;
}
int main() {
  pthread_t tid1 = 0;
  pthread_t tid2 = 0;
  struct sockaddr_in addr1;
  struct sockaddr_in addr2;
  addr1.sin_addr.s_addr = 0;
  addr2.sin_addr.s_addr = 0xffffffff;
  pthread_create(&tid1, NULL, Func1, &addr1);
  pthread_create(&tid2, NULL, Func2, &addr2);
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
  return 0;
}

在这里插入图片描述

所以在我这个环境下他并不是线程安全的

简单的TCP网络程序

TCP sockot API详解

头文件:<sys/socket.h>

socket()

在这里插入图片描述

参数

  • domain(域):这个参数指定了套接字的地址簇,也称为协议簇
    • AF_INET:IPv4 地址族。
    • AF_INET6:IPv6 地址族。
    • AF_UNIX:本地通信(Unix 域套接字)。
    • AF_PACKET:低级网络接口。
  • type(类型)
    • SOCK_STREAM:面向连接的流套接字,提供可靠的、双向的、基于字节的数据传输。
    • SOCK_DGRAM:数据报套接字,提供不可靠的、无连接的、固定长度的数据传输。
    • SOCK_RAW:原始套接字,直接访问网络层。
  • protocol(协议)
    • 0:表示使用默认协议。
    • IPPROTO_TCP:TCP 协议。
    • IPPROTO_UDP:UDP 协议。
    • IPPROTO_ICMP:ICMP 协议。
  • 对于 AF_INET(IPv4 地址族),默认协议通常是 IPPROTO_TCP(TCP 协议)。
  • 对于 AF_INET6(IPv6 地址族),默认协议通常是 IPPROTO_TCP(TCP 协议)。
  • 对于 AF_UNIX(Unix 域套接字),默认协议是不适用的,因为它们在本地通信上工作,不涉及到传输层协议。
  • socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;

  • 应用程序可以像读写文件一样用read/write在网络上收发数据;

  • 如果socket()调用出错则返回-1;

  • 对于IPv4, family参数指定为AF_INET;

  • 对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议

  • protocol参数的介绍从略,指定为0即可。

bind()

在这里插入图片描述

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后 就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;

  • bind()成功返回0,失败返回-1。

  • bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听 myaddr所描述的地址和端口号;

  • struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结 构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;

我们的程序中对myaddr参数是这样初始化的:

bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
  1. 将整个结构体清零;
  2. 设置地址类型为AF_INET;
  3. 网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
  4. 端口号为SERV_PORT,我们定义为9999
listen()

在这里插入图片描述

  • listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多 的连接请求就忽略, 这里设置不会太大(一般是5), 具体细节同学们课后深入研究;
  • listen()成功返回0,失败返回-1;

在这里插入图片描述

accept();

在这里插入图片描述

  • 三次握手完成后, 服务器调用accept()接受连接;
  • 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr,addrlen是一个输出型参数,accept()返回时传出客户端的地址和端口号;
  • 如果给addr 参数传NULL,表示不关心客户端的地址;
  • addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度 以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
  • accept的返回值也是一个文件描述符
connect

在这里插入图片描述

  • 客户端需要调用connect()连接服务器;
  • connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
  • connect()成功返回0,出错返回-1;

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

注意,云服务器的公有ip无法直接绑定,但可以绑定本地的ip

local.sin_addr.s_addr = INADDR_ANY;//将套接字绑定到所有可用的网络接口,而不是绑定到特定的网络接口。这样就能绑定服务器的公有ip了

添加这个语句就能绑定服务器的公有ip了

  • 向网络发送数据,比如字符串等,使用的接口会自动将主机序列转换为网络序列

  • 获取客户端的ip以及端口号

在这里插入图片描述

  • 接收并发送消息

在这里插入图片描述

在这里插入图片描述

  • 添加重连功能,服务器处理客户端中途读时的错误

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码:

TcpClient.cc

#include <iostream>
//sock四件套
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>



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

// ./tcpclient serverip serverport
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 cnt = 5;
        int isreconnect = false;
        int sockfd = 0;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return 1;
        }
        do
        {
            //tcp客户端要不要绑定,tcp客户端要绑定,只是不用显示地写出来(由操作系统根据需求进行随机选择)
            //UDP在首次发送数据时确定端口号

            //tcp是面向链接的,客户端发起connect的时候进行自动随机bind
            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                isreconnect = true;
                cnt--;
                std::cerr << "connect error..., reconnect: " << cnt << std::endl;
                sleep(2);
            }
            else
            {
                break;
            }
        } while (cnt && isreconnect);

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

         while (true)
         {
            std::string message;
            std::cout << "Please Enter# ";
            std::getline(std::cin, message);

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

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

    return 0;
}

TcpServer.cc

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h> //sockaddr_in类型头文件
#include <netinet/in.h>
#include "Log.hpp"
#include <sys/wait.h>
#include <signal.h>

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 一般不要设置太大

Log lg;

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError
};


class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip)
    {
    }
    void InitServer() // 尽量不要将有风险的事情放进构造函数中
    {
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock_ < 0)
        {
            lg(Fatal, "create socket,errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg(Info, "create socket success, sockfd: %d", listensock_);
        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));
        local.sin_addr.s_addr = INADDR_ANY; // 将套接字绑定到所有可用的网络接口,而不是绑定到特定的网络接口。这样就能绑定服务器的ip了

        if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }
        lg(Info, "Bind socket success, sockfd: %d", listensock_);

        if (listen(listensock_, backlog) < 0)
        {
            lg(Fatal, "bind error: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }
        lg(Info, "Listen socket success, sockfd: %d", listensock_);
    }


    void Start()
    {
        // signal(SIGCHLD,SIG_IGN);
        lg(Info, "tcpServer is running...");
        for (;;)
        {
            // 获取新链接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len); // 为什么这里又多了一个fd,为什么不用之前的初始化服务器的sockfd_
            // 这里的listensock_只是将底层的链接获取上来,但是真正提供服务的是accept返回的sockfd
            // 一般listensock_为3,sockfd为4
            if (sockfd < 0)
            {
                lg(Warning, "accept error: %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));

            // 根据新链接来进行通信
            lg(Info, "get a new link...,sockfd: %d,client ip: %s,client port: %d", sockfd, clientip, clientport);

            Service(sockfd, clientip, clientport);
            close(sockfd);


           
        }
    }
    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        while (true)
        {
            // 读消息直接使用read函数即可
            char buffer[4096];

            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                lg(Info, "%s:%d quit,server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                lg(Warning, "read error, sockfd: %d, client ip: %s,client port: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }
    ~TcpServer() {}

private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

但是这种单进程服务器只能处理一个链接,明显无法满足我们的需求,所以我们进行改进:

  • 多线程版的服务器

在这里插入图片描述

父进程的sockfd关闭了不会对子进程产生影响,因为子进程和父进程各自有独立的文件描述符指针(文件描述符fd中也存在引用计数

优雅的让父进程不会阻塞等待(子进程已经退出了),同时让系统领养进程并自动回收

if(fork() > 0) exit(0);

还可以使用信号,让父进程不用等待

在这里插入图片描述

  • 还可以使用多线程(参考):

在这里插入图片描述

注意要将Routine变为static函数,再使用pthread_detach(pthread_self())进行线程分离

线程池:

在这里插入图片描述

完整的TCP服务器代码(线程池版)

TcpClient.cc

#include <iostream>
//sock四件套
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>



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

// ./tcpclient serverip serverport
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 cnt = 5;
        int isreconnect = false;
        int sockfd = 0;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return 1;
        }
        do
        {
            //tcp客户端要不要绑定,tcp客户端要绑定,只是不用显示地写出来(由操作系统根据需求进行随机选择)
            //UDP在首次发送数据时确定端口号

            //tcp是面向链接的,客户端发起connect的时候进行自动随机bind
            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                isreconnect = true;
                cnt--;
                std::cerr << "connect error..., reconnect: " << cnt << std::endl;
                sleep(2);
            }
            else
            {
                break;
            }
        } while (cnt && isreconnect);

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

         while (true)
         {
            std::string message;
            std::cout << "Please Enter# ";
            std::getline(std::cin, message);

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

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

    return 0;
}

TcpServer.cc

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h> //sockaddr_in类型头文件
#include <netinet/in.h>
#include "Log.hpp"
#include <sys/wait.h>
#include <signal.h>
#include "Task.hpp"
#include "ThreadPool.hpp"
const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 一般不要设置太大

Log lg;

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError
};
class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t) : sockfd(fd), clientip(ip), clientport(p), tsvr(t)
    {}

public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip)
    {
    }
    void InitServer() // 尽量不要将有风险的事情放进构造函数中
    {
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock_ < 0)
        {
            lg(Fatal, "create socket,errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg(Info, "create socket success, sockfd: %d", listensock_);
        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));
        local.sin_addr.s_addr = INADDR_ANY; // 将套接字绑定到所有可用的网络接口,而不是绑定到特定的网络接口。这样就能绑定服务器的ip了

        if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }
        lg(Info, "Bind socket success, sockfd: %d", listensock_);

        if (listen(listensock_, backlog) < 0)
        {
            lg(Fatal, "bind error: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }
        lg(Info, "Listen socket success, sockfd: %d", listensock_);
    }

    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->clientip, td->clientport); //???
        delete td;
        return nullptr;
    }

    void Start()
    {
        ThreadPool<Task>::GetInstance()->Start();
        // signal(SIGCHLD,SIG_IGN);
        lg(Info, "tcpServer is running...");
        for (;;)
        {
            // 获取新链接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len); // 为什么这里又多了一个fd,为什么不用之前的初始化服务器的sockfd_
            // 这里的listensock_只是将底层的链接获取上来,但是真正提供服务的是accept返回的sockfd
            // 一般listensock_为3,sockfd为4
            if (sockfd < 0)
            {
                lg(Warning, "accept error: %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));

            // 根据新链接来进行通信
            lg(Info, "get a new link...,sockfd: %d,client ip: %s,client port: %d", sockfd, clientip, clientport);

            // Service(sockfd, clientip, clientport);
            // close(sockfd);


            //多进程版
            // pid_t id = fork();
            // if (id == 0)
            // {
            //     // child
            //     close(listensock_); // 由于子进程不需要父进程的listensock_,所以我们需要将它关闭
            //     if (fork() > 0)
            //         exit(0);
            //     Service(sockfd, clientip, clientport); // 孙子进程, system 领养
            //     close(sockfd);
            //     exit(0);
            // }
            // close(sockfd);
            // // father
            // pid_t rid = waitpid(id, nullptr, 0);
            // (void)rid;


            //多线程版本
            // ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Routine, td);
            
            
            //线程池版本
            Task t(sockfd, clientip, clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }
    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        while (true)
        {
            // 读消息直接使用read函数即可
            char buffer[4096];

            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                lg(Info, "%s:%d quit,server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                lg(Warning, "read error, sockfd: %d, client ip: %s,client port: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }
    ~TcpServer() {}

private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

Task.hpp

#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Init.hpp"

extern Log lg;
Init init;

class Task
{
public:
    Task(int sockfd, const std::string &clientip, const uint16_t &clientport)
        : sockfd_(sockfd), clientip_(clientip), clientport_(clientport)
    {
    }
    Task()
    {
    }
    void run()
    {
        // 测试代码
        char buffer[4096];
        // Tcp是面向字节流的,你怎么保证,你读取上来的数据,是"一个" "完整" 的报文呢?
        ssize_t n = read(sockfd_, buffer, sizeof(buffer)); // BUG?
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client key# " << buffer << std::endl;
            std::string echo_string = init.translation(buffer);

            // sleep(5);
            // // close(sockfd_);
            // lg(Warning, "close sockfd %d done", sockfd_);

            // sleep(2);
            n = write(sockfd_, echo_string.c_str(), echo_string.size()); // 100 fd 不存在
            if(n < 0)
            {
                lg(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));
            }
        }
        else if (n == 0)
        {
            lg(Info, "%s:%d quit, server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);
        }
        else
        {
            lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd_, clientip_.c_str(), clientport_);
        }
        close(sockfd_);
    }
    void operator()()
    {
        run();
    }
    ~Task()
    {
    }

private:
    int sockfd_;
    std::string clientip_;
    uint16_t clientport_;
};

Init.hpp

#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"

const std::string dictname = "./dict.txt";
const std::string sep = ":";

extern Log lg;

//yellow:黄色...
static bool Split(std::string &s, std::string *part1, std::string *part2)
{
    auto pos = s.find(sep);
    if(pos == std::string::npos) return false;
    *part1 = s.substr(0, pos);
    *part2 = s.substr(pos+1);
    return true;
}

class Init
{
public:
    Init()
    {
        std::ifstream in(dictname);
        if(!in.is_open())
        {
            lg(Fatal, "ifstream open %s error", dictname.c_str());
            exit(1);
        }
        std::string line;
        while(std::getline(in, line))
        {
            std::string part1, part2;
            Split(line, &part1, &part2);
            dict.insert({part1, part2});
        }
        in.close();
    }
    std::string translation(const std::string &key)
    {
        auto iter = dict.find(key);
        if(iter == dict.end()) return "Unknow";
        else return iter->second;
    }
private:
    std::unordered_map<std::string, std::string> dict;
};

Log.hpp

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};

Main.cc

#include "TcpServer.hpp"
#include <iostream>
#include <memory>

void Usage(std::string proc)
{
    std::cout<<"\n\rUsage: "<<proc <<" port[1024+]\n" <<std::endl;
}


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

    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));
    tcp_svr->InitServer();
    tcp_svr->Start();
    
    return 0;
}

dict.txt

apple:苹果...
banana:香蕉...
red:红色...
yellow:黄色...
the: 这
be: 是
to: 朝向/给/对
and: 和
I: 我
in: 在...里
that: 那个
have: 有
will: 将
for: 为了
but: 但是
as: 像...一样
what: 什么
so: 因此
he: 他
her: 她
his: 他的
they: 他们
we: 我们
their: 他们的
his: 它的
with: 和...一起
she: 她
he: 他(宾格)
it: 它

ThreadPool.hpp

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 10;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
        }
    }
    void Start()
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == tp_) // ???
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

我们更推荐多线程版的服务器(创建进程的成本比较高)

注意线程池版本的服务器不适合长服务,因为线程的个数是确定的

服务器的结构:

while(1)
{
  cliaddr_len = sizeof(cliaddr);
  connfd = accept(listenfd,(struct sockaddr *)&cliaddr_len);
  n = read(connfd,buf,MAXLINE);
  ...
  close(connfd);
}

【附】:双工在物理层面上是指通信线路上可以同时发送和接收数据,然而在数据链路层的传输采用载波监听/碰撞检测的技术。指的就是在有线传输上只能存在一种电信号,所以也就不会存在两种电信号同时存在的场景了,即有线传输物理层面上没有双工一说,要么发送数据,要么接收数据。 (ps:无线传输中的码分复用可以同时存在)我们现在认为的双工是指逻辑连接层面上,而 UDP 是不需要进行逻辑连接的,只是单向发送,但双方随时都可以发送,如果你认为这是双工那也合理。而TCP的全双工也是逻辑层面上,通信两端随时都可以发送和接收数据,不需要像 HTTP/1.1 那样采用请求应答模式。

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

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

相关文章

云谈网页聊天室的web自动化测试 性能测试 (包含测试代码和测试的详细过程)

概要 项目名称&#xff1a;云谈网页聊天室测试日期&#xff1a;2024-03-05测试人员&#xff1a;汪汪miao~测试类型&#xff1a;功能测试、集成测试&#xff0c;接口测试、性能测试测试框架&工具&#xff1a;Selenium、Junit、LoadRunner 项目背景 云谈网页聊天室是一款实…

【算法】两数之和(暴力求解+哈希表)

本题来源---《两数之和》。 题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里…

OPPO云VPC网络实践

1 OPPO 云网络现状 随着OPPO业务的快速发展&#xff0c;OPPO云规模增长迅速。大规模虚拟实例的弹性伸缩、低延时需求对网络提出了诸多挑战。原有基于VLAN搭建的私有网络无法解决这些问题&#xff0c;给网络运维和业务的快速上线带来了挑战。 梳理存在的主要问题如下&#xf…

redis 集群模式(redis cluster)介绍

目录 一 redis cluster 相关定义 1&#xff0c; redis cluster 是什么 2&#xff0c;redis 集群的组成 3&#xff0c;集群的作用 4&#xff0c;集群架构图 二 Redis集群的数据分片 1&#xff0c;哈希槽是什么 2&#xff0c;哈希槽如何排布 3&#xff0c;Redis集…

(源码+部署+讲解)基于Springboot+vue校园设施报修系统的设计与实现

前言 &#x1f497;博主介绍&#xff1a;✌专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅&#x1f447;&#x1f3fb; 2024年Java精品实战案例《100套》 &#x1f345;文末获取源码联系&#x1f345; &#x1f31f…

freeRTOS-day4

1.总结二进制信号量和计数型信号量的区别&#xff0c;以及他们的使用场景。 二进制信号量只有两个状态&#xff1a;0和1。它通常用于线程同步&#xff0c;表示某个线程执行完毕&#xff0c;另一个线程才能开始执行。这种特性使得二进制信号量特别适用于互斥访问共享资源的场景…

ensp 通过cloud连接交换,通过本机直连telnet交换机

#连接图 #cloud配置 绑定本机一个虚拟网卡&#xff0c;勾选双向通信&#xff0c;这样就可以通过真机直接telent到交换机 #交换机配置 #需要将管理口ip配置为绑定的虚拟网卡同网段的IP&#xff0c;便于直接链接 system-view sysname s5700 undo info-center en telnet server…

HTTP 常见的状态码以及其适用场景

是什么 HTTP状态码&#xff08;英语&#xff1a;HTTP Status Code&#xff09;&#xff0c;用以表示网页服务器超文本传输协议响应状态的3位数字代码 它由 RFC 2616规范定义的&#xff0c;并得到 RFC 2518、RFC 2817、RFC 2295、RFC 2774与 RFC 4918等规范扩展 简单来讲&#…

什么是网络行为监控审计

网络行为监控审计&#xff0c;顾名思义&#xff0c;是指对网络使用者的行为进行实时监控&#xff0c;并对这些行为进行详细记录和审计的过程。 它涉及到对网络流量、用户操作、数据访问等多个方面的监控&#xff0c;旨在发现潜在的安全威胁和违规行为&#xff0c;从而保障网络…

视听杂志知网收录期刊投稿发表论文

《视听》是由国家新闻出版总署批准的正规的&#xff0c;兼有新闻传播、新媒体理论探索的当代学术性省级综合期刊。坚持正确的理论导向&#xff0c;全面展示广播影视事业发展中出现的新情况、新事物&#xff0c;探索现代传媒基础理论&#xff0c;研究网络传播、通讯传播等新媒体…

职场中持续加班的原因、影响及应对策略

在当今竞争激烈的职场环境中&#xff0c;加班已经成为许多行业的常态。本文将探讨持续加班的原因、对员工和企业的负面影响&#xff0c;以及应对持续加班的策略。 一、持续加班的原因 1. 工作任务繁重&#xff1a;在许多企业中&#xff0c;工作任务量过大&#xff0c;员工为了完…

Sora的阅读技术报告

sora的技术报告 走进sorasora的特性sora的介绍sora的实际操作sora的发展安全措施研究技术 走进sora 大家好&#xff0c;我是清风之上。随着人工智能的发展&#xff0c;慢慢的他已经出现在我们生活中的各个角落&#xff0c;其中有API推出的sora&#xff0c;让我们震惊不已&…

Linux多进程通信(4)——消息队列从入门到实战!

Linux多进程通信总结——进程间通信看这一篇足够啦&#xff01; 1.基本介绍 1&#xff09;消息队列的本质其实是一个内核提供的链表&#xff0c;内核基于这个链表&#xff0c;实现了一个数据结构&#xff0c;向消息队列中写数据&#xff0c;实际上是向这个数据结构中插入一个…

页面转word的那些事

背景 有些时候需要将页面内容或者是页面的数据通过word进行下载&#xff0c;以方便客户进行二次编辑&#xff0c;而不是直接导出图片或者是pdf。 想在页面端点击下载成word&#xff0c;那必然需要服务端来进行读写文件&#xff0c;无论是你后端编辑好的内容流&#xff0c;还是…

MySQL如何创建存储过程

工作中有时候需要自己去创建存储过程&#xff0c;然后调用存储去获得一些数据等&#xff0c;接下来就给大家介绍下MySQL如何创建存储过程。 语法&#xff1a; CREATE PROCEDURE 存储程名([[IN|OUT|INOUT] 参数名 数据类型[,[IN|OUT|INOUT] 参数名 数据类型…]]) [特性 …] 过…

git生成ssh key并推送到远端仓库

ssh-keygen -t rsa -C "anarckkgmail.com"在用户文件夹中找到id_rsa.pub&#xff0c;把内容复制到gitea的配置里&#xff0c;然后直接用git推送就可以了

HTML基础知识详解(下)(如果想知道html的全部基础知识点,那么只看这一篇就足够了!)

前言&#xff1a;在上一篇文章中&#xff0c;我们已经学习完了超链接标签、列表标签和表格标签&#xff0c;但是我们还有一些标签没有学习&#xff0c;在这篇文章中&#xff0c;我们将学习剩余的标签。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页…

【进阶六】Python实现SDVRPTW常见求解算法——遗传算法(GA)

基于python语言&#xff0c;采用经典遗传算法&#xff08;GA&#xff09;对 带硬时间窗的需求拆分车辆路径规划问题&#xff08;SDVRP&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整2.1 需求拆分2.2 需求拆分后的服务时长取值问题 3. 求解结果4. 代码片段参…

前端零基础学习web3开发

目录 1 钱包 2 发起交易 3 出块 4 块高 5 矿工 6 Gas费 这一节&#xff0c;我们不说让人神往的比特币&#xff0c;不说自己会不会利用这个虚拟的货币来发财&#xff0c;也不说那些模模糊糊的知识&#xff0c;什么去中心化啦&#xff0c;什么奇妙的加密啦&#xff0c;我们…

深入浅出 -- 系统架构之负载均衡Nginx缓存机制

一、Nginx缓存机制 对于性能优化而言&#xff0c;缓存是一种能够大幅度提升性能的方案&#xff0c;因此几乎可以在各处都能看见缓存&#xff0c;如客户端缓存、代理缓存、服务器缓存等等&#xff0c;Nginx的缓存则属于代理缓存的一种。对于整个系统而言&#xff0c;加入缓存带来…