Linux---网络套接字

news2025/4/15 19:37:03

端口号

端口号

端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用

在公网上,IP地址能表示唯一的一台主机,端口号port,用来表示该主机上的唯一的一个进程,IP:port = 标识全网唯一的一个进程。

现在用户要刷抖音,假如抖音的端口号为4321,获取一个抖音短视频,抖音应用通过协议连接到抖音服务器的IP地址,然后建立连接,抖音服务器传送短视频的数据到达用户的设备上。在通信的过程中,一定要知道端口号,不知道的话无法通信。

一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定。

认识TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题.
传输层协议
有连接
可靠传输
面向字节流

认识UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论.
传输层协议
无连接
不可靠传输
面向数据报

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢 ?

大端:低位地址存放高位数据,高位地址存放低位数据
小端:低位地址存放低位数据,高位地址存放高位数据

大端和小端只是对数据类型长度是两个及以上的,如int short,对于单字节没限制,在网络中经常需要考虑大端和小端的是ip和端口。

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

在网络通信中,为了确保数据在不同系统之间能正确解释,网络字节序被定义为大端序。
 


为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回 ;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。


IP地址转换函数

inet_pton 函数是一个用于将点分十进制的IPv4或IPv6地址转换为二进制形式的函数,
即将地址从文本表示形式转换为网络字节序的二进制形式。这个函数的声明如下:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
af 参数表示地址族,可以是 AF_INET 表示IPv4,也可以是 AF_INET6 表示IPv6。
src 参数是包含要转换的IP地址的点分十进制字符串。
dst 参数是指向存储转换后二进制形式的地址的指针。
函数的返回值是整数,如果转换成功,则返回1(表示成功),
如果地址格式无效或发生错误,则返回nullptr(表示无效的地址格式)
或-1(表示发生了错误)。
  1. IP 地址转换函数:
  2. p->表示点分十进制的字符串形式
  3. to->到
  4. n->表示 network 网络

地址转换函数

inet_ntop 函数是用于将网络字节序的地址转换为字符串表示的函数。
它是IPv4和IPv6通用的函数,可以用于将网络地址转换为点分十进制字符串或IPv6的十六进制字符串。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

af:地址族(Address Family),通常是 AF_INET 表示IPv4,AF_INET6 表示IPv6。
src:指向存储网络地址的结构体的指针。
dst:用于存储转换后的字符串的缓冲区指针。
size:缓冲区的大小。
该函数成功时返回指向转换后的字符串的指针,失败时返回 NULL。

还有另一个函数

#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);
这个函数接受一个表示IP地址的字符串(cp参数),
并返回一个in_addr_t类型的二进制格式表示的IP地址。
如果转换失败,函数返回INADDR_NONE。

这个函数仅支持IPV4

socket编程常见接口

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器
int socket(int domain, int type, int protocol)
函数描述:创建socket
参数说明:
domain:协议版本
        AF_INET IPV4
        AF_INET6 IPV6
        AF_UNIX_LOACL 本地套接字使用
type:协议类型
SOCK_STREAM 流式,默认使用的协议是tcp协议
SOCK_DGRAM报式,默认使用的是udp协议
protocal:
一般填0,表示使用对应类型的默认协议

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
函数描述:将socket文件描述符和ip,port绑定
参数说明:
socket: 调用 socket 孟数返回的文件描述符
    addr: 本地服务器的 IP 地址和 PORT,
        struct sockaddr_in serv;
        serv.sin_family = AF_INET:
        serv.sin_port = htons(8888):
        //serv.sin_addr.s_addr = htonl(INADDR_ANY):
        //INADDR_ANY: 表示使用本机任意有效的可用IP
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr)
addrlen: addr 变量的占用的内存大小
返回值:失败返回-1,并设置errno

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog)
函数描述: 将套接字由主动态变为被动态
参数说明:
socket: 调用 socket 函数返回的文件描述符
backlog: 同时请求连接的最大个数(还未建立连接)
返回值:
    成功: 返回0
    失败: 返回-1,并设置errno

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
accept 函数是一个阻寒函数,若没有新的连接请求,则一直阻寒.
从已连接队列中获取一个新的连接,并获得一个新的文件描述符,该文件描
述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)
函数参数:
sockfd:调用 socket 函数返回的文件描述符
addr:传出参数,保存客户端的地址信息
addrlen: n:传入传出参数,addr变量所占内存空间大小
返回值:
成功:返回一个新的文件描述符,用于和客户端通信
失败:返回-1,并设置errno值,

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
函数说明: 连接服务器
函教参款:
sockfd: 调用 socket 函数返回的文件描述符
addr: 服务端的地址信息
addrlen: addr 变量的内存大小
返回值:
成功: 返回0
失败: 返回-1,并设置errno 值

socket变成用到的重要的结构体 struct sockaddr

sockaddr结构

sockaddr_in结构

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址.

in_addr结构
 


in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数 。


UDP通信

在网络通信中,一般不使用read和write这两个接口的。

当有人来发送消息的时候,你想知道是谁发的消息。

可以使用recvfrom函数,这个函数中存在一个src_addr参数,这个结构体需要我们自己定义,然后将结构体对象传入进去,就可以知道是谁发送的信息了。

发送数据可以使用sendto。


在这里,简单的写了一个日志系统,后面就不再使用perror,而是直接使用日志系统来提示错误。

#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>
#include <cstring>

#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 logmessage(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\n", leftbuffer, rightbuffer);

    //     // printf("%s", logtxt); // 暂时打印
    //     printLog(level, logtxt);
    // }
    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\n", leftbuffer, rightbuffer);

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

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

// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);

//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }

//     va_end(s); //s = NULL
//     return sum;
// }

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include "log1.hpp"

std::string defaultip = "0.0.0.0";

Log log;

#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port, std::string ip = defaultip)
        :_port(port)
        ,_ip(ip)
    {}

    void Init()
    {
        // 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);
            exit(-1);
        }
        log(Info, "sockfd successful");


        // 2.绑定端口号
        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv)); // 初始化addr
        serv.sin_family = AF_INET;
        serv.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);
        int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));
        if (bd < 0)
        {
            log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);
            exit(-1);
        }
        log(bd, "bind successful");
    }

    void Run()
    {
        IsConnect = true;
        char inbuf[SIZE];
        while (IsConnect)
        {
            struct sockaddr_in client;
            bzero(&client, sizeof(client));
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, (socklen_t *)&len);
            if (n < 0)
            {
                log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);
                continue;
            }
            inbuf[n] = 0;

            std::string info = inbuf;
            std::string echo_string = "server echo# " + info;

            sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);
        }
    }

    ~UdpServer()
    {}
public:
    uint16_t _port;
    std::string _ip;
    bool IsConnect;
    int sockfd;
};
#include "UdpServer.hpp"
#include <memory>

int main()
{
    std::unique_ptr<UdpServer> udp(new UdpServer(8080));
    udp->Init();
    udp->Run();
    return 0;
}

通过 netstat -naup可以查看运行的udp的情况。

只要能用上面的指令查到,就说明我们的服务已经启动了。


当我们把IP改成云服务器的ip,会出现这样的情况,首先端口号是没问题的。

如果你用的是虚拟机,代码是可以运行的。但我今天用的是云服务器,是禁止直接bind公网IP的。当我们服务器有多张网卡的时候,这个IP地址可能就不是唯一的,其他IP地址也可以连接我们这个服务器,所以一般在绑定IP的时候,可以设为0,bind(IP:0), 凡是发给我这台主机的数据,我们都要根据端口号向上交付。

绑定IP地址为0----任意地址bind。


现在将端口号改为80.

会出现这样的错误。

在进行提权之后,就能绑定这个端口号了。

一般情况下,[1,1023]是系统内定的端口号,一般都有固定的应用层协议使用,http:80, https:443, mysql:3306,这种端口号一般就别在使用了。可以考虑8000以上的。


修改一下UdpServer,使用命令行,以./main port的方式执行程序。

#include "UdpServer.hpp"
#include <memory>

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "fail!" << std::endl;
        return 1;
    }
    uint16_t port = std::atoi(argv[1]);
    std::unique_ptr<UdpServer> udp(new UdpServer(port));
    udp->Init();
    udp->Run();
    return 0;
}


现在简单实现一个客户端,来完成通信。

客户端需要bind吗?服务端的bind部分写了一大堆,客户端也需要绑定,服务器要找到客户端,所以也需要bind,只不过不需要用户显示的bind。bind的过程一般会有OS自由随机选择。

一个端口号只能被一个进程bind,对server是如此,对client也是如此。其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以。系统什么时候bind呢?首次发送数据的时候。


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


using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "fail" << endl;
        return 1;
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    struct sockaddr_in serv;
    bzero(&serv, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &serv.sin_addr.s_addr);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "socket fail" << endl;
        return 1;
    }

    string msg;
    char buffer[1024];
    while (true)
    {
        cout << "Please Enter@ ";
        getline(cin, msg);

        sendto(sockfd, msg.c_str(), msg.size(), 0, (const sockaddr*)&serv, sizeof(serv));

        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom (sockfd, buffer, 1023, 0, (struct sockaddr*)&serv, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            cout << buffer << endl;
        }
    }



    close(sockfd);
    return 0;
}

这就是一个简单的客户端,在运行服务端和客户端的时候,就能完成两者之间的通信了。


#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include "log1.hpp"

std::string defaultip = "0.0.0.0";

Log log;

#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port, std::string ip = defaultip)
        :_port(port)
        ,_ip(ip)
    {}

    void Init()
    {
        // 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);
            exit(-1);
        }
        log(Info, "sockfd successful");


        // 2.绑定端口号
        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv)); // 初始化addr
        serv.sin_family = AF_INET;
        serv.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);
        int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));
        if (bd < 0)
        {
            log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);
            exit(-1);
        }
        log(Info, "bind successful");
    }

    void Run()
    {
        IsConnect = true;
        char inbuf[SIZE];
        while (IsConnect)
        {
            struct sockaddr_in client;
            bzero(&client, sizeof(client));
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);
            if (n < 0)
            {
                log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);
                continue;
            }
            inbuf[n] = 0; 

            std::string info = inbuf;
            std::string echo_string = "server echo# " + info;
            std::cout << echo_string << std::endl;
            sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);
        }
    }

    ~UdpServer()
    {
        close(sockfd);
    }
public:
    uint16_t _port;
    std::string _ip;
    bool IsConnect;
    int sockfd;
};
#include "UdpServer.hpp"
#include <memory>

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "fail!" << std::endl;
        return 1;
    }
    uint16_t port = std::atoi(argv[1]);
    std::unique_ptr<UdpServer> udp(new UdpServer(port));
    udp->Init();
    udp->Run();
    return 0;
}

服务端和客户端可以直接

以这样的方式运行,

./UdpClient ip port。

通过这个选项可以将远程服务器中的文件发送到本地。

通过rz可以将本地文件发送到远程服务器。现在我使用两个不同的服务器,来运行客户端。上传之后是没有x权限的,使用chmod +x 文件名 可以获得x权限。

然后运行程序,是可以通信的。

所以网络通信最终就是这样子的,只不过可以在进行适当的结耦。


当然udp不仅仅可以只进行通信,如果客户端发送的是指令,我们接收到这个指令的时候可以去处理这个指令。

通过popen函数。

用于创建一个由调用进程执行命令并建立到该命令的标准输入或标准输出的管道。
FILE *popen(const char *command, const char *mode);
  • command 参数是一个字符串,表示要执行的命令。这个字符串会被传递给 shell 进行解释。
  • mode 参数是一个字符串,表示使用的管道的模式。 "r"(只读模式)和 "w"(只写模式)。

popen 返回一个指向 FILE 结构的指针,该结构描述了与新进程的连接。可以使用返回的文件指针进行读取或写入,就像处理常规文件一样。

std::string Command(const std::string &cmd)
{
    FILE* fp = popen(cmd.c_str(), "r");
    if (fp == nullptr)
    {
        perror("popen");
        return "error";
    }

    std::string ret;
    char buf[2048];
    while (true)
    {
        char *tail = fgets(buf, sizeof(buf), fp);
        if (tail == nullptr)
        {
            break;
        }
        ret += buf;
    }

    return ret;
}

当然, 在处理客户端的指令之前,要进行检查,避免出现客户端执行rm -rf等之类的指令。


上述通信的源代码

// UdpServer.hpp
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <functional>
#include "log1.hpp"

// 在Run中带一个参数,这个参数是客户端需要做的事情。可以用function进行封装。然后通过传入的参数来改变客户端需要做的事情。
using func_t = std::function<std::string(const std::string&)>;

std::string defaultip = "0.0.0.0";

Log log;

#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port, std::string ip = defaultip)
        :_port(port)
        ,_ip(ip)
    {}

    void Init()
    {
        // 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);
            exit(-1);
        }
        log(Info, "sockfd successful");


        // 2.绑定端口号
        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv)); // 初始化addr
        serv.sin_family = AF_INET;
        serv.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);
        int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));
        if (bd < 0)
        {
            log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);
            exit(-1);
        }
        log(Info, "bind successful");
    }

    void Run(func_t func)
    {
        IsConnect = true;
        char inbuf[SIZE];
        while (IsConnect)
        {
            struct sockaddr_in client;
            bzero(&client, sizeof(client));
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);
            if (n < 0)
            {
                log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);
                continue;
            }
            inbuf[n] = 0; 
            std::string info = inbuf;

            std::string echo_string = func(info);

            sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);
        }
    }

    ~UdpServer()
    {
        close(sockfd);
    }
public:
    uint16_t _port;
    std::string _ip;
    bool IsConnect;
    int sockfd;
};
// Main.cc
#include "UdpServer.hpp"
#include <cstdio>
#include <memory>

std::string Handler(const std::string &msg)
{
    std::string res = "Server get a message : ";
    res += msg;
    std::cout << res << std::endl;
    return res;
}

std::string Command(const std::string &cmd)
{
    FILE* fp = popen(cmd.c_str(), "r");
    if (fp == nullptr)
    {
        perror("popen");
        return "error";
    }

    std::string ret;
    char buf[2048];
    while (true)
    {
        char *tail = fgets(buf, sizeof(buf), fp);
        if (tail == nullptr)
        {
            break;
        }
        ret += buf;
    }

    return ret;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "fail!" << std::endl;
        return 1;
    }
    uint16_t port = std::atoi(argv[1]);
    std::unique_ptr<UdpServer> udp(new UdpServer(port));
    udp->Init();
    udp->Run(Command);
    return 0;
}
// UdpClient.cc
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>


using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "fail" << endl;
        return 1;
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    struct sockaddr_in serv;
    bzero(&serv, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &serv.sin_addr.s_addr);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "socket fail" << endl;
        return 1;
    }

    string msg;
    char buffer[1024];
    while (true)
    {
        cout << "Please Enter@ ";
        getline(cin, msg);

        sendto(sockfd, msg.c_str(), msg.size(), 0, (const sockaddr*)&serv, sizeof(serv));

        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom (sockfd, buffer, 1023, 0, (struct sockaddr*)&serv, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            cout << buffer << endl;
        }
    }



    close(sockfd);
    return 0;
}
.PHONY:all
all:UdpServer UdpClient
UdpServer:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread

UdpClient:UdpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread
	
.PHONY:clean
clean:
	rm UdpClient UdpServer

使用Udp做一个聊天

简单的对UdpServer.hpp中的代码进行了一个更改。

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <functional>
#include <unordered_map>
#include <netinet/in.h>
#include "log1.hpp"

// 在Run中带一个参数,这个参数是客户端需要做的事情。可以用function进行封装。然后通过传入的参数来改变客户端需要做的事情。
using func_t = std::function<void(const std::string&, std::string&, uint16_t&)>;

std::string defaultip = "0.0.0.0";

Log log;

#define SIZE 1024

class UdpServer
{
public:
UdpServer(uint16_t port, std::string ip = defaultip)
:_port(port)
,_ip(ip)
{}

void Init()
{
    // 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);
        exit(-1);
    }
    log(Info, "sockfd successful");


    // 2.绑定端口号
    struct sockaddr_in serv;
    bzero(&serv, sizeof(serv)); // 初始化addr
    serv.sin_family = AF_INET;
    serv.sin_port = htons(_port);
    inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);
    int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));
    if (bd < 0)
    {
        log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);
        exit(-1);
    }
    log(Info, "bind successful");
}

void CheckOnlineUser(const struct sockaddr_in &client,std::string& ip, uint16_t& port)
{
    auto iter = online_user.find(ip);
    if (iter == online_user.end())
    {
        online_user.insert({ip, client});
        std::cout << "[" + ip + ":" + std::to_string(port) + "]#" << "add new client" << std::endl;
    }
}

std::string func(std::string info, std::string ip, uint16_t port)
{
    std::string echo_string = "[" + ip + ":" + std::to_string(port) + "]#" + info;
    return echo_string;
}

void BroadCast(std::string &info, std::string &ip, uint16_t &port)
{
    for (auto &it : online_user)
    {
        sendto(sockfd, info.c_str(), info.size(), 0, (const struct sockaddr*)&it.second, sizeof(it.second));
    }
}

void Run()
{
    IsConnect = true;
    char inbuf[SIZE];
    while (IsConnect)
        {
            struct sockaddr_in client;
            bzero(&client, sizeof(client));
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);
            if (n < 0)
            {
                log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);
                continue;
            }
            inbuf[n] = 0; 
            std::string client_ip = inet_ntoa(client.sin_addr);// 获取用户的ip
            uint16_t client_port = ntohs(client.sin_port);// 获取用户的端口号
            CheckOnlineUser(client,client_ip, client_port);// 检查在线的用户,若有新上线的,会进行提醒

            std::string info = inbuf;
            // std::string echo_string = func(info);
            std::string echo_string = func(info, client_ip, client_port);// 将信息拼接为字符串

            BroadCast(echo_string,client_ip, client_port);// 将这条信息转发给所有人
            
            // sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);
        }
    }

    ~UdpServer()
    {
        close(sockfd);
    }
public:
    uint16_t _port;
    std::string _ip;
    bool IsConnect;
    int sockfd;
    std::unordered_map<std::string, struct sockaddr_in> online_user;// 用来存储所有客户端的IP和sockaddr
};


inet_ntoa 是一个用于将 IPv4 地址从二进制表示转换为点分十进制字符串表示的函数。它通常在网络编程中使用,特别是在处理套接字编程时。

因为客户端是单线程的原因,每次循环收到一条信息后,在getline中会被阻塞,这就导致服务端转发消息的时候,不能及时的将所有的信息转发出来。


下面将客户端改为多线程。

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <mutex>

using namespace std;

pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;

struct ThreadData
{
    struct sockaddr_in serv;
    int sockfd;
};

void *recv_msg(void *args)
{
    // OpenTerminal();
    ThreadData *td = static_cast<ThreadData *>(args);
    char buffer[1024];
    while (true)
    {
        memset(buffer, 0, sizeof(buffer));
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            cerr << buffer << endl;
        }
    }
}

void *send_msg(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    string message;
    socklen_t len = sizeof(td->serv);

    sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->serv), len);

    while (true)
    {
        cout << "Please Enter@ ";
        getline(cin, message);
        sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->serv), len);
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "fail" << endl;
        return 1;
    }

    struct ThreadData td;

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    bzero(&td.serv, sizeof(td.serv));
    td.serv.sin_family = AF_INET;
    td.serv.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &td.serv.sin_addr.s_addr);

    td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (td.sockfd < 0)
    {
        cout << "socket fail" << endl;
        return 1;
    }

    pthread_t client_send, client_recv;
    pthread_create(&client_send, nullptr, send_msg, &td);
    pthread_create(&client_recv, nullptr, recv_msg, &td);


    pthread_join(client_send, nullptr);
    pthread_join(client_recv, nullptr);


    close(td.sockfd);
    return 0;
}

这样就可以实时的将所有用户发送的信息转发到屏幕上了,类似于群聊。

TcpServer

TCP协议跟 UDP协议有点不同。

在街上,会碰到一些饭店门口站的有专门拉客的人,如果有客人要去他们饭店吃饭,他会把你领到饭店里面,然后进去之后让其他人接待你,然后拉客的人继续去外面拉客。而TCP中的listen就是拉客的人。它负责监听每一个要连接服务端的人。而accept就是服务员,你要向客户端发送什么信息都是通过accept返回的文件描述符来进行通信的。

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log1.hpp"

Log lg;

const std::string defaultip = "0.0.0.0";
const int backlog = 5;

class TcpServer
{
public:
    TcpServer(uint16_t port, const std::string ip = defaultip, int lsfd = -1)
        :_port(port)
        ,_ip(ip)
        ,_listensockfd(lsfd)
    {}

    void Init()
    {
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听socket的文件描述符
        if (_listensockfd < 0)
        {
            lg(Fatal, "socket fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "socket successful");

        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv));
        serv.sin_family = AF_INET;
        serv.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);
        if (bind(_listensockfd, (const struct sockaddr*)&serv, sizeof(serv)) < 0) // 注册
        {
            lg(Fatal, "bind fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "bind successful");

        if (listen(_listensockfd, backlog) < 0) // 监听
        {
            lg(Fatal, "listen fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "listen successful");   
    }

    void Service()
    {
        char buf[4096];
        while (true)
        {
            ssize_t n = read(_sockfd, buf, 4096);
            if (n > 0)
            {
                buf[n] = 0;
                std::cout << buf << std::endl;
            }

            std::string res = "Server get a message : ";
            res += buf;
            write(_sockfd, res.c_str(), res.size());
        }
    }

    void Start()
    {
        for (;;)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            _sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len); // 创建文件描述符,因为tcp是有连接,面向字节流的,可以通过accept返回值使用read和write来完成通信
            if (_sockfd < 0)
            {
                lg(Fatal, "accept fail, error code: %d , error string: %s", errno, strerror(errno));
                exit(-1);
            }
            lg(Info, "accept successful");

            Service();
        }
        
    }

    ~TcpServer()
    {
        close(_sockfd);
        close(_listensockfd);
    }

private:
    uint16_t _port;
    std::string _ip;
    int _listensockfd;
    int _sockfd;
};
#include "TcpServer.hpp"
#include <memory>

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "port" << std::endl;
        exit(-1);
    }
    uint16_t port = std::atoi(argv[1]);


    std::unique_ptr<TcpServer> tcp(new TcpServer(port));
    tcp->Init();
    tcp->Start();
    return 0;
}
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "client fail" << std::endl;
        exit(-1);
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 1;
    }
    struct sockaddr_in serv;
    bzero(&serv, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(port);
    inet_pton(AF_INET, ip.c_str(), &serv.sin_addr.s_addr);
    // tcp客户端要不要bind? 要不要显示的bind?
    // 客户端发起connect的时候,进行自动随机bind
    socklen_t len = sizeof(serv);
    int n = connect(sockfd, (const struct sockaddr*)&serv, len);
    if (n < 0)
    {
        std::cerr << "connect fail" << std::endl;
        exit(-1);
    }
    // 连接成功就可以发送信息了。

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

        write(sockfd, msg.c_str(), msg.size());

        char inbuf[4096];
        int n = read(sockfd, inbuf, sizeof(inbuf));
        if (n > 0)
        {
            inbuf[n] = 0;
            std::cout << inbuf << std::endl;
        }
    }

    close(sockfd);
    return 0;
}

这样就能完成通信了。

下面可以将代码进行一些细节上的处理。


tcp在通信的时候,如果客户端推出了,服务端读取的时候就会读取到0.

可以做一下修改。

当前我们代码开两个客户端的时候,一端发送信息,后连的客户端也发送信息,会阻塞。这是因为目前我们的通信代码是单进程版的通信。一个客户连上去了,另一个客户需要等待。


改为多进程版,这里使用了孙子进程,是一个很妙的想法。

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "log1.hpp"

Log lg;

const std::string defaultip = "0.0.0.0";
const int backlog = 5;

class TcpServer
{
public:
    TcpServer(uint16_t port, const std::string ip = defaultip, int lsfd = -1)
        :_port(port)
        ,_ip(ip)
        ,_listensockfd(lsfd)
    {}

    void Init()
    {
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听socket的文件描述符
        if (_listensockfd < 0)
        {
            lg(Fatal, "socket fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "socket successful");

        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv));
        serv.sin_family = AF_INET;
        serv.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);
        if (bind(_listensockfd, (const struct sockaddr*)&serv, sizeof(serv)) < 0) // 注册
        {
            lg(Fatal, "bind fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "bind successful");

        if (listen(_listensockfd, backlog) < 0) // 监听
        {
            lg(Fatal, "listen fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "listen successful");   
    }

    void Service()
    {
        char buf[4096];
        while (true)
        {
            ssize_t n = read(_sockfd, buf, 4096);
            if (n > 0)
            {
                buf[n] = 0;
                std::cout << buf << std::endl;
            }
            else if (n == 0) // 通信过程中,客户端突然关闭
            {
                lg(Info, "server close sockfd: %d", _sockfd);
                break;
            }
            else 
            {
                lg(Warning, "read error, sockfd: %d", _sockfd);
                break;
            }

            std::string res = "Server get a message : ";
            res += buf;
            write(_sockfd, res.c_str(), res.size());
        }
    }

    void Start()
    {
        for (;;)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            _sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len); // 创建文件描述符,因为tcp是有连接,面向字节流的,可以通过accept返回值使用read和write来完成通信
            if (_sockfd < 0)
            {
                lg(Fatal, "accept fail, error code: %d , error string: %s", errno, strerror(errno));
                exit(-1);
            }
            lg(Info, "accept successful");

            // 单进程
            // Service(); 
            // close(_sockfd);

            // 多进程
            pid_t id = fork();
            if (id == 0)
            {
                // child
                close(_listensockfd);
                if (fork() > 0) exit(0); // 让孙子进程来处理,这个时候waitpid直接返回,因为父进程挂了,孙子进程会被系统领养。
                Service(); // 由子进程做服务
                close(_sockfd);
                exit(0);
            }
            // father
            close(_sockfd); // 由子进程完成一些任务,子进程会拷贝父进程的文件描述符表,而父进程后续也不对文件进行操作,所以可直接将sockfd关闭
            pid_t rid = waitpid(id, nullptr, 0);
            (void)rid;
        }
    }

    ~TcpServer()
    {
        close(_listensockfd);
    }

private:
    uint16_t _port;
    std::string _ip;
    int _listensockfd;
    int _sockfd;
};

来了一个新连接,就创建一个进程,这样进程会变得越来越多。但是创建进程的成本是非常高的。


多线程版本

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "log1.hpp"

Log lg;

const std::string defaultip = "0.0.0.0";
const int backlog = 5;


class TcpServer;

class ThreadData
{
public:
    ThreadData(TcpServer *t):ts(t)
    {}
    ~ThreadData()
    {}
public:
    TcpServer *ts;
};

class TcpServer
{
public:
    TcpServer(uint16_t port, const std::string ip = defaultip, int lsfd = -1)
        :_port(port)
        ,_ip(ip)
        ,_listensockfd(lsfd)
    {}

    void Init()
    {
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听socket的文件描述符
        if (_listensockfd < 0)
        {
            lg(Fatal, "socket fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "socket successful");

        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv));
        serv.sin_family = AF_INET;
        serv.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);
        if (bind(_listensockfd, (const struct sockaddr*)&serv, sizeof(serv)) < 0) // 注册
        {
            lg(Fatal, "bind fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "bind successful");

        if (listen(_listensockfd, backlog) < 0) // 监听
        {
            lg(Fatal, "listen fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "listen successful");   
    }

    void Service()
    {
        char buf[4096];
        while (true)
        {
            ssize_t n = read(_sockfd, buf, 4096);
            if (n > 0)
            {
                buf[n] = 0;
                std::cout << buf << std::endl;
            }
            else if (n == 0) // 通信过程中,客户端突然关闭
            {
                lg(Info, "server close sockfd: %d", _sockfd);
                break;
            }
            else 
            {
                lg(Warning, "read error, sockfd: %d", _sockfd);
                break;
            }

            std::string res = "Server get a message : ";
            res += buf;
            write(_sockfd, res.c_str(), res.size());
        }
    }

    static void *Routinue(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->ts->Service();
    }

    void Start()
    {
        for (;;)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            _sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len); // 创建文件描述符,因为tcp是有连接,面向字节流的,可以通过accept返回值使用read和write来完成通信
            if (_sockfd < 0)
            {
                lg(Fatal, "accept fail, error code: %d , error string: %s", errno, strerror(errno));
                exit(-1);
            }
            lg(Info, "accept successful");

            // 1.单进程
            // Service(); 
            // close(_sockfd);

            // 2.多进程
            // pid_t id = fork();
            // if (id == 0)
            // {
            //     // child
            //     close(_listensockfd);
            //     if (fork() > 0) exit(0); // 让孙子进程来处理,这个时候waitpid直接返回,因为父进程挂了,孙子进程会被系统领养。
            //     Service(); // 由子进程做服务
            //     close(_sockfd);
            //     exit(0);
            // }
            // // father
            // close(_sockfd); // 由子进程完成一些任务,子进程会拷贝父进程的文件描述符表,而父进程后续也不对文件进行操作,所以可直接将sockfd关闭
            // pid_t rid = waitpid(id, nullptr, 0);
            // (void)rid;


            // 3.多线程
            ThreadData* td = new ThreadData(this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routinue, td);
        }
    }

    ~TcpServer()
    {
        close(_listensockfd);
    }

private:
    uint16_t _port;
    std::string _ip;
    int _listensockfd;
    int _sockfd;
};

每个客户端到来的时候都需要创建线程,那么,可不可以在线程没有到的时候,提前准备好线程,为可能会连接的客户做准备?


#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "ThreadPool.hpp"
#include "log1.hpp"
#include "Task.hpp"

Log lg;

const std::string defaultip = "0.0.0.0";
const int backlog = 5;


class TcpServer;

class ThreadData
{
public:
    ThreadData(TcpServer *t):ts(t)
    {}
    ~ThreadData()
    {}
public:
    TcpServer *ts;
};

class TcpServer
{
public:
    TcpServer(uint16_t port, const std::string ip = defaultip, int lsfd = -1)
        :_port(port)
        ,_ip(ip)
        ,_listensockfd(lsfd)
    {}

    void Init()
    {
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听socket的文件描述符
        if (_listensockfd < 0)
        {
            lg(Fatal, "socket fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "socket successful");

        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv));
        serv.sin_family = AF_INET;
        serv.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);
        if (bind(_listensockfd, (const struct sockaddr*)&serv, sizeof(serv)) < 0) // 注册
        {
            lg(Fatal, "bind fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "bind successful");

        if (listen(_listensockfd, backlog) < 0) // 监听
        {
            lg(Fatal, "listen fail, error code: %d , error string: %s", errno, strerror(errno));
            exit(-1);
        }
        lg(Info, "listen successful");   
    }

    void Service()
    {
        char buf[4096];
        while (true)
        {
            ssize_t n = read(_sockfd, buf, 4096);
            if (n > 0)
            {
                buf[n] = 0;
                std::cout << buf << std::endl;
            }
            else if (n == 0) // 通信过程中,客户端突然关闭
            {
                lg(Info, "server close sockfd: %d", _sockfd);
                break;
            }
            else 
            {
                lg(Warning, "read error, sockfd: %d", _sockfd);
                break;
            }

            std::string res = "Server get a message : ";
            res += buf;
            write(_sockfd, res.c_str(), res.size());
        }
    }

    static void *Routinue(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->ts->Service();
    }

    void Start()
    {
        ThreadPool<Task>::GetInstance()->Start();
        for (;;)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            _sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len); // 创建文件描述符,因为tcp是有连接,面向字节流的,可以通过accept返回值使用read和write来完成通信
            if (_sockfd < 0)
            {
                lg(Fatal, "accept fail, error code: %d , error string: %s", errno, strerror(errno));
                exit(-1);
            }
            lg(Info, "accept successful");

            // 1.单进程
            // Service(); 
            // close(_sockfd);

            // 2.多进程
            // pid_t id = fork();
            // if (id == 0)
            // {
            //     // child
            //     close(_listensockfd);
            //     if (fork() > 0) exit(0); // 让孙子进程来处理,这个时候waitpid直接返回,因为父进程挂了,孙子进程会被系统领养。
            //     Service(); // 由子进程做服务
            //     close(_sockfd);
            //     exit(0);
            // }
            // // father
            // close(_sockfd); // 由子进程完成一些任务,子进程会拷贝父进程的文件描述符表,而父进程后续也不对文件进行操作,所以可直接将sockfd关闭
            // pid_t rid = waitpid(id, nullptr, 0);
            // (void)rid;


            // 3.多线程
            // ThreadData* td = new ThreadData(this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Routinue, td);

            // 4. 线程池版
            Task task(_sockfd);
            ThreadPool<Task>::GetInstance()->Push(task);
        }
    }

    ~TcpServer()
    {
        close(_listensockfd);
    }

private:
    uint16_t _port;
    std::string _ip;
    int _listensockfd;
    int _sockfd;
};

#pragma once

#include <iostream>
#include <string>
#include "log1.hpp"

extern Log lg;

std::string opers="+-*/%";

enum{
    DivZero=1,
    ModZero,
    Unknown
};

class Task
{
public:
    Task(int fd):_sockfd(fd)
    {}
    Task()
    {}
    void run()
    {
        char buf[4096];
        ssize_t n = read(_sockfd, buf, 4096);
        if (n > 0)
        {
            buf[n] = 0;
            std::cout << buf << std::endl;
        }
        else if (n == 0) // 通信过程中,客户端突然关闭
        {
            lg(Info, "server close sockfd: %d", _sockfd);
        }
        else 
        {
            lg(Warning, "read error, sockfd: %d", _sockfd);
        }

        std::string res = "Server get a message : ";
        res += buf;
        write(_sockfd, res.c_str(), res.size());
        close(_sockfd);
    }
    void operator ()()
    {
        run();
    }
    std::string GetTask()
    {
        
    }
    ~Task()
    {
    }

private:
    int _sockfd;
};
#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 = 5;

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;
#include "TcpServer.hpp"
#include <memory>

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "port" << std::endl;
        exit(-1);
    }
    uint16_t port = std::atoi(argv[1]);


    std::unique_ptr<TcpServer> tcp(new TcpServer(port));
    tcp->Init();
    tcp->Start();
    return 0;
}
.PHONY:all
all:TcpServer TcpClient
TcpServer:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread

TcpClient:TcpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread
	
.PHONY:clean
clean:
	rm TcpClient TcpServer

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

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

相关文章

大模型激活函数知识

FFN块 计算公式 在Transformer模型中&#xff0c;FFN&#xff08;Feed-Forward Network&#xff09;块通常指的是在编码器&#xff08;Encoder&#xff09;和解码器&#xff08;Decoder&#xff09;中的一个全连接前馈网络子结构。FFN块位于自注意力层&#xff08;Self-Attent…

C语言第二十三弹---指针(七)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 指针 1、sizeof和strlen的对比 1.1、sizeof 1.2、strlen 1.3、sizeof 和 strlen的对比 2、数组和指针笔试题解析 2.1、⼀维数组 2.2、二维数组 总结 1、si…

Java 基于微信小程序的私家车位共享系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

WebSocket原理详解

目录 1.引言 1.1.使用HTTP不断轮询 1.2.长轮询 2.websocket 2.1.概述 2.2.websocket建立过程 2.3.抓包分析 2.4.websocket的消息格式 3.使用场景 4.总结 1.引言 平时我们打开网页&#xff0c;比如购物网站某宝。都是点一下列表商品&#xff0c;跳转一下网页就到了商品…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Marquee组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Marquee组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Marquee组件 跑马灯组件&#xff0c;用于滚动展示一段单行文本&#xff0c;仅当…

【Effective Objective - C 2.0】——读书笔记(三)

文章目录 十五、用前缀避免命名空间冲突十六、提供全能初始化方法十七、实现description方法十八、尽量使用不可变对象十九、使用清晰而协调的命名方式二十、为私有方法名加前缀二十一、理解Objective-C错误模型二十二、理解NSCopying协议 十五、用前缀避免命名空间冲突 OC语言…

WSL外部SSH连接有效方法

前言 wsl作为windows下使用linux平台有效的手段之一&#xff0c;本文可以让win作为工作站&#xff0c;外部系统用来连接win下的wsl系统。 自动启动服务脚本 https://zhuanlan.zhihu.com/p/47733615 开机自启端口转发 wslname "Ubuntu-20.04" 要转发端口的Linux…

不懂编程?节点包来凑——Dynamo常用节点包推荐(上)

由于篇幅有限&#xff0c;本次文章我们分上、下两篇&#xff0c;来分享给大家。 Dynamo作为一款辅助三维设计工具&#xff0c;他可以通过图形化的编程&#xff0c;帮我们解决很多在设计或者建模过程中遇到的小问题&#xff1b;同时他作为一款可视化编程软件&#xff0c;学起来…

Spring Boot 笔记 006 创建接口_注册

1.1 由于返回数据都是以下这种格式&#xff0c;那么久再编写一个result实体类 报错了&#xff0c;原因是没有构造方法 可以使用lombok的注解自动生成&#xff0c;添加无参的构造器和全参的构造器 package com.geji.pojo;import lombok.AllArgsConstructor; import lombok.NoArg…

【C语言】【力扣】7.整数反转和9.回文数

一、整数反转 1.1 个人思考过程 初解&#xff1a;出现ERROR&#xff0c;数据溢出的情况下应该返回0。&#xff08;错误&#xff09; int reverse(int x){int y0;while(x!0){yy*10x%10;x/10; }return y; } 再解&#xff1a;加上数据溢出判断条件。&#xff08;正确&#…

JS逆向进阶篇【去哪儿旅行登录】【上篇】

目标url: aHR0cHM6Ly91c2VyLnF1bmFyLmNvbS9wYXNzcG9ydC9sb2dpbi5qc3A 实现难点&#xff1a; 逆向滑块请求发送短信登录 目录 每篇前言&#xff1a;0、前置技术栈&#xff08;1&#xff09;JS实现页面滑动&#xff08;2&#xff09;JS实现记录滑动轨迹&#xff08;3&#xff…

二、ClickHouse简介

ClickHouse简介 前言一、行式存储二、DBMS功能三、多样化引擎四、高吞吐写入能力五、数据分区与线程级并行六、场景七、特定版本 前言 ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库&#xff08;DBMS&#xff09;&#xff0c;使用 C 语言编写&#xff0c;主要…

2024年教师资格证认定报名完整流程

&#x1f49a;网上报名流程概览 一、进入教资认定网报入口&#xff1b; 二、进行实名核验&#xff1b; 三、申请网报时间查询&#xff1b; 四、个人信息维护&#xff1b; 五、认定申请报名&#xff1b; &#x1f49a;认定所需材料 1、 身份证&#xff1b; 2 、户口本/居住证…

【Java程序设计】【C00249】基于Springboot的私人健身与教练预约管理系统(有论文)

基于Springboot的私人健身与教练预约管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的私人健身与教练预约管理系统 本系统分为系统功能模块、管理员功能模块、教练功能模块以及用户功能模块。 系统功能模…

计算机网络——08应用层原理

应用层原理 创建一个新的网络 编程 在不同的端系统上运行通过网络基础设施提供的服务&#xff0c;应用进程批次通信如Web Web服务器软件与浏览器软件通信 网络核心中没有应用层软件 网络核心没有应用层功能网络应用只能在端系统上存在 快速网络应用开发和部署 网络应用…

ChatGPT4 教你如何完成SQL的实践应用

对数据库的各项应用与操作都离不开SQL来对数据进行增删改查。 例如 &#xff1a; 有一张某公司职员信息表如下&#xff1a; 需求1&#xff1a;在公司职员信息表中&#xff0c;请统计各部门&#xff0c;各岗位下的员工人数。 如果这个SQL语句不会写或者不知道怎么操作可以交给…

【C基础刷题】第九讲

本系列博客为个人刷题思路分享&#xff0c;有需要借鉴即可。 1.目录大纲&#xff1a; 2.题目链接&#xff1a; 统计成绩 00&#xff1a;00&#xff1a;00⸺00&#xff1a;09&#xff1a;00题号&#xff1a;BC33 链接&#xff1a;https://www.nowcoder.com/practice/ cad8d94…

【VTKExamples::PolyData】第二十七期 KochanekSpline

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享VTK样例KochanekSpline & KochanekSplineDemo,并解析接口vtkParametricSpline & vtkParametricFunctionSource,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,…

过河卒(洛谷)

题目 原题 题目描述 棋盘上 A A A 点有一个过河卒&#xff0c;需要走到目标 B B B 点。卒行走的规则&#xff1a;可以向下、或者向右。同时在棋盘上 C C C 点有一个对方的马&#xff0c;该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。…

响应式编程三流处理

响应式编程三流处理 组合响应式流concatmergezipcombineLatest flatMap、concatMap、flatMapSequebtial操作符flatMapconcatMapflatMapSequential 元素采样sample 和sampleTimeout 流的批处理bufferwindow操作符group by将响应式流转化为阻塞结构在序列处理时查看元素物化和非物…