Linux网络编程之UDP

news2024/12/24 20:28:33

文章目录

  • Linux网络编程之UDP
    • 1、端口号
    • 2、端口号和进程ID的区别
    • 3、重新认识网络通讯过程
    • 4、UDP协议的简单认识
    • 5、网络字节序
    • 6、socket编程接口
      • 6.1、socket常见接口
      • 6.2、sockaddr通用地址结构
    • 7、简单的UDP网络程序
      • 7.1、服务器响应程序
      • 7.2、服务器执行命令行
      • 7.3、服务器英语单词字典
      • 7.4、服务器聊天室
    • 8、补充内容

img

Linux网络编程之UDP

1、端口号

端口号是计算机网络中用来标识特定应用程序或服务的数字标识符。在网络通信中,每个数据包都包含一个目标端口号和源端口号,这样可以确保数据包能够正确地路由到目标应用程序或服务

  • 端口号是一个16位的数字,范围从0到65535。

  • 用于在传输层(通常是TCP或UDP协议)标识特定的应用程序或服务。

  • 允许同一台计算机上的多个应用程序同时进行网络通信,每个应用程序使用不同的端口号。

  • 在目标设备上,操作系统通过端口号将数据包传递给正确的应用程序或服务。

  • IP地址 + 端口号 = 套接字,能够标识网络上的某一台主机的某一个进程。

端口号使得计算机网络中的不同应用程序能够通过网络进行可靠的通信和数据交换,同时确保数据能够安全地到达目标应用程序。

一个端口号只能被一个进程占用。


2、端口号和进程ID的区别

  • 用途不同:端口号用于标识网络中的应用程序或服务,而进程ID用于操作系统内部管理和标识正在运行的进程。

  • 作用范围:端口号主要在网络通信中使用,进程ID主要在操作系统内部使用。

  • 分配方式:端口号是在应用程序设计或网络配置时指定的,而进程ID是由操作系统动态分配给每个新创建的进程。

如果端口号和进程ID合并,那么系统的耦合度会增加,不符合低耦合的编程思想

另外,一个进程可以有多个端口号,但是一个端口号不能被多个进程使用。


3、重新认识网络通讯过程

  1. 我们上网,无非就是两种动作:a. 把远端数据拉取到本地 b. 把数据发送到远端。

  2. 大部分的网络通信行为,都是用户触发的,在计算机中,用户就是进程!

  3. 把数据发送到目标主机,不是目的,而是手段。真正的目的,是把数据交给目的主机上的某个服务(进程)。

  4. 网络通信的本质,其实就是进程在帮我们进行网络通信(进程间通信),无论是对于客户端还是服务器。

  5. IP地址 + 端口号 = 套接字,能够标识网络上的某一台主机的某一个进程。

  6. client --> server : client进程 – > server进程

  • client ip + client port = client进程

  • server ip + server port = server进程

  • 在网络中都能唯一找到彼此


4、UDP协议的简单认识

  • 传输层协议

  • 无连接

  • 不可靠传输

  • 面向数据报


5、网络字节序

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

答:网络数据流的地址按大端来定义的。

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出

  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存

  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址

  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节

  • 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据

  • 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可

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

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

助记:h表示host,n表示network,l表示32位长整数,s表示16位短整数。

  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回

  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回

  • IP是32位,端口号是16位,因此IP转网络字节序是使用带l的,端口号是使用带s的


6、socket编程接口

6.1、socket常见接口

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

我们可以看到,上述接口中基本都使用到了sockaddr结构体,下面我们想象解释一下。


6.2、sockaddr通用地址结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket,然而。各种网络协议的地址格式并不相同。sockaddr通用地址结构就是为了统一接口,在不同的操作系统下也可以正常使用(强转)。

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址.

  • IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。

  • socket API可以都用struct sockaddr *类型表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性,可以接收IPv4,IPv6,以及UNIX Domain Socket各种类型的sockaddr结构体指针作为参数。

sockaddr通用地址结构:

/* Structure describing a generic socket address.  */
struct sockaddr
 {
   __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
   char sa_data[14];		/* Address data.  */
 };

sockaddr_in结构:

/* Structure describing an Internet socket address.  */
struct sockaddr_in
 {
   __SOCKADDR_COMMON (sin_); // 地址类型
   in_port_t sin_port;			/* Port number.  */
   struct in_addr sin_addr;		/* Internet address.  */

   /* Pad to size of `struct sockaddr'.  */
   unsigned char sin_zero[sizeof (struct sockaddr) // 8字节填充
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
 };

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

in_addr结构:

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
 {
   in_addr_t s_addr;
 };

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


7、简单的UDP网络程序

7.1、服务器响应程序

主要功能是客户端给服务器发送消息,服务器收到消息进行响应(把收到的消息发送给客户端)。
用到的几个接口:

接收和发送网络数据:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数:
   buf      --  输出型参数,即存接收到的数据
   len      --  期望收到的数据长度
   flag     --  默认为0
   src_addr --  输出型参数,存发送数据的对象
   addrlen  --  输入输出型参数,存src_addr的大小
   
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数:
   buf      --  输入型参数,即要存发送的数据
   len      --  发送数据的长度
   flag     --  默认为0
   src_addr --  输出型参数,存发送数据的对象
   addrlen  --  输入输出型参数,存src_addr的大小

字节序列转换:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 将字符串类型的IP转换为无符号整数类型IP并转换为网络字节序列
in_addr_t inet_addr(const char *cp); 

// 从网络字节序列中将无符号整数类型IP转换为字符串类型的IP
char *inet_ntoa(struct in_addr in); 

接下来就是代码文件了。

  • InetAddr.hpp文件
#pragma once

#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>

class InetAddr
{
    void GetAddress(std::string *ip, uint16_t *port)
    {
        // char *inet_ntoa(struct in_addr in);
        *ip = inet_ntoa(_addr.sin_addr);
        *port = ntohs(_addr.sin_port);
    }

public:
    InetAddr(const struct sockaddr_in &addr) : _addr(addr)
    {
        GetAddress(&_ip, &_port);
    }

    std::string Ip()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }

    ~InetAddr() {}

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};
  • LockGuard.hpp文件
#include <pthread.h>


class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
    {
        pthread_mutex_lock(_mutex); // 构造加锁
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex); // 析构解锁
    }

private:
    pthread_mutex_t *_mutex;
};
  • Log.hpp文件
#pragma once

#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include "LockGuard.hpp"

bool isSave = false; // 默认向显示器打印
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define FILEPATH "./log.txt"

enum level
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

void SaveToFile(const string &message)
{
    ofstream out(FILEPATH, ios_base::app);
    if (!out.is_open())
        return;
    out << message;
    out.close();
}

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";
    default:
        return "Unknow";
    }
}

std::string GetTimeString()
{
    time_t curr_time = time(nullptr);
    struct tm *format_time = localtime(&curr_time);
    if (format_time == nullptr)
        return "None";
    char buff[1024];
    snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900,
             format_time->tm_mon + 1,
             format_time->tm_mday,
             format_time->tm_hour,
             format_time->tm_min,
             format_time->tm_sec);
    return buff;
}

void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...)
{
    std::string levelstr = LevelToString(level);
    std::string timestr = GetTimeString();
    pid_t pid = getpid();

    char buff[1024];
    va_list arg;
    // int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数
    va_start(arg, format);
    vsnprintf(buff, sizeof(buff), format, arg);
    va_end(arg);

    LockGuard lock(&mutex);
    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';
    if (issave == false)
        std::cout << message;
    else
        SaveToFile(message);
}

// 固定文件名和行数
#define LOG(level, format, ...)                                               \
    do                                                                        \
    {                                                                         \
        LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \
    } while (0)

#define EnableScreen()  \
    do                  \
    {                   \
        isSave = false; \
    } while (0)

#define EnableFile()   \
    do                 \
    {                  \
        isSave = true; \
    } while (0)

void Test(int num, ...)
{
    va_list arg;
    va_start(arg, num);
    while (num--)
    {
        int data = va_arg(arg, int);
        std::cout << data << " ";
    }
    std::cout << std::endl;
    va_end(arg);
}
  • Main.cc文件
#include <iostream>
#include <memory>
#include "UdpServer.hpp"

void Usage()
{
    // printf("./udp_server serverip serverport\n");
    printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0
}

int main(int argc, char *argv[])
{
    // if (argc != 3)
    if (argc != 2)
    {
        Usage();
        exit(USAGE_ERROR);
    }
    // std::string ip = argv[1];
    // uint16_t port = std::stoi(argv[2]);
    uint16_t port = std::stoi(argv[1]);
    // std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);
    usvr->InitServer();
    usvr->Start();

    return 0;
}
  • UdpClient.cc文件
#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

enum errorcode
{
    USAGE_ERROR = 1,
};

void Usage()
{
    printf("Usage : ./udp_client serverip serverport\n");
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage();
        exit(USAGE_ERROR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
    }
    // 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
    // a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!
    // b. 什么时候bind呢?首次发送数据的时候
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport); // 主机序列转网络序列 -- 网络序列是大端
    // a.字符串序列点分十进制IP地址 转换为 4字节IP
    // b.主机序列转网络序列
    // in_addr_t inet_addr(const char *cp);
    server.sin_addr.s_addr = inet_addr(serverip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr

    std::string message;
    // 直接通信即可,已经自动绑定
    while (true)
    {
        std::cout << "Please Enter:# ";
        std::getline(std::cin, message);
        // 发送数据
        // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));

        // 获取回应数据
        char buff[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
        ssize_t n = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
        if (n > 0)
        {
            buff[n] = 0;
            std::cout << "Server Echo:# " << buff << std::endl;
        }
    }
}
  • UdpServer.cc文件
#pragma once

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <string.h>
#include <error.h>

using namespace std;

#include "Log.hpp"
#include "InetAddr.hpp"

const int Defaultfd = -1;

enum errorcode
{
    CREATE_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};

class UdpServer
{
public:
    // UdpServer(std::string ip, uint16_t port) : _sockfd(Defaultfd), _ip(ip), _port(port), _isrunning(false)
    UdpServer(uint16_t port) : _sockfd(Defaultfd), _port(port), _isrunning(false)
    {
    }
    void InitServer()
    {
        // 1. 创建udp套接字 -- 必须要做的
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // udp通信
        if (_sockfd < 0)
        {
            LOG(FATAL, "socke create error ! error string %s error %d", strerror(errno), errno);
            exit(CREATE_ERROR);
        }
        // 创建socket成功
        LOG(INFO, "create socket success ! sockfd:", _sockfd);
        // 2.0. 填充sockaddr_in结构
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // 主机序列转网络序列 -- 网络序列是大端
        // a.字符串序列点分十进制IP地址 转换为 4字节IP
        // b.主机序列转网络序列
        // in_addr_t inet_addr(const char *cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr
        local.sin_addr.s_addr = INADDR_ANY; // 0 -- 链接当前服务器的所有ip都接受
        // 2.1. 绑定网络信息
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "socke bind error ! error string %s error %d", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        // 绑定成功
        LOG(INFO, "bind socket success !");
    }
    void Start()
    {
        // 一直运行,直到管理者不想运行了, 服务器都是死循环
        // UDP是面向数据报的协议
        _isrunning = true;
        while (true)
        {
            // 获取数据
            // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 既是输入(必须初始化) ,也是输出(peer的实际长度)
            char buff[1024];
            ssize_t n = recvfrom(_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buff[n] = 0;
                // 回答发送方
                // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
                InetAddr addr(peer);
                LOG(DEBUG, "get message , sender:[%s:%d] , content: %s", addr.Ip().c_str(), addr.Port(), buff);
                sendto(_sockfd, buff, strlen(buff), 0, (struct sockaddr *)&peer, len);
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;
    // std::string _ip; // 暂时先这样写 -- 不需要
    uint16_t _port;
    bool _isrunning;
};
  • Makefile文件
.PHONY:all
all:udp_client udp_server

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

.PHONY:clean
clean:
	rm -f udp_server udp_client

运行结果


7.2、服务器执行命令行

主要是客户端发送命令给服务器,服务器执行命令后返回给客户端。

  • InetAddr.hpp文件
#pragma once

#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>

class InetAddr
{
    void GetAddress(std::string *ip, uint16_t *port)
    {
        // char *inet_ntoa(struct in_addr in);
        *ip = inet_ntoa(_addr.sin_addr);
        *port = ntohs(_addr.sin_port);
    }

public:
    InetAddr(const struct sockaddr_in &addr) : _addr(addr)
    {
        GetAddress(&_ip, &_port);
    }

    std::string Ip()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }

    ~InetAddr() {}

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};
  • LockGuard.hpp文件
#include <pthread.h>


class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
    {
        pthread_mutex_lock(_mutex); // 构造加锁
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex); // 析构解锁
    }

private:
    pthread_mutex_t *_mutex;
};
  • Log.hpp文件
#pragma once

#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include "LockGuard.hpp"

bool isSave = false; // 默认向显示器打印
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define FILEPATH "./log.txt"

enum level
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

void SaveToFile(const string &message)
{
    ofstream out(FILEPATH, ios_base::app);
    if (!out.is_open())
        return;
    out << message;
    out.close();
}

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";
    default:
        return "Unknow";
    }
}

std::string GetTimeString()
{
    time_t curr_time = time(nullptr);
    struct tm *format_time = localtime(&curr_time);
    if (format_time == nullptr)
        return "None";
    char buff[1024];
    snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900,
             format_time->tm_mon + 1,
             format_time->tm_mday,
             format_time->tm_hour,
             format_time->tm_min,
             format_time->tm_sec);
    return buff;
}

void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...)
{
    std::string levelstr = LevelToString(level);
    std::string timestr = GetTimeString();
    pid_t pid = getpid();

    char buff[1024];
    va_list arg;
    // int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数
    va_start(arg, format);
    vsnprintf(buff, sizeof(buff), format, arg);
    va_end(arg);

    LockGuard lock(&mutex);
    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';
    if (issave == false)
        std::cout << message;
    else
        SaveToFile(message);
}

// 固定文件名和行数
#define LOG(level, format, ...)                                               \
    do                                                                        \
    {                                                                         \
        LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \
    } while (0)

#define EnableScreen()  \
    do                  \
    {                   \
        isSave = false; \
    } while (0)

#define EnableFile()   \
    do                 \
    {                  \
        isSave = true; \
    } while (0)

void Test(int num, ...)
{
    va_list arg;
    va_start(arg, num);
    while (num--)
    {
        int data = va_arg(arg, int);
        std::cout << data << " ";
    }
    std::cout << std::endl;
    va_end(arg);
}
  • Main.cc文件
#include <iostream>
#include <memory>
#include <stdio.h>
#include <vector>
#include "UdpServer.hpp"

void Usage()
{
    // printf("./udp_server serverip serverport\n");
    printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0
}

std::string OnMessage(std::string request)
{
    return request + " got you!";
}

bool CheckCommand(std::string command)
{
    vector<string> cmd = {
        "kill",
        "rm",
        "dd",
        "top",
        "reboot",
        "shutdown",
        "mv",
        "cp",
        "halt",
        "unlink",
        "exit",
        "chmod"};
    for (auto &e : cmd)
    {
        if (command.find(e) != std::string::npos)
            return false;
    }
    return true;
}

std::string OnCommand(std::string command)
{
    if (!CheckCommand(command))
    {
        return "bad man!";
    }
    // FILE *popen(const char *command, const char *type);
    FILE *pp = popen(command.c_str(), "r");
    if (!pp)
    {
        return "popen error!";
    }

    std::string response;
    char buff[1024];
    while (true)
    {
        // char *fgets(char *s, int size, FILE *stream);
        char *s = fgets(buff, sizeof(buff), pp);
        if (!s)
            break;
        else
            response += buff;
    }
    pclose(pp);
    return response.empty() ? "not command" : response;
}

int main(int argc, char *argv[])
{
    // if (argc != 3)
    if (argc != 2)
    {
        Usage();
        exit(USAGE_ERROR);
    }
    // std::string ip = argv[1];
    // uint16_t port = std::stoi(argv[2]);
    uint16_t port = std::stoi(argv[1]);
    // std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(OnCommand, port);
    usvr->InitServer();
    usvr->Start();

    return 0;
}
  • Makefile文件
.PHONY:all
all:udp_client udp_server

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

.PHONY:clean
clean:
	rm -f udp_server udp_client
  • UdpClient.cc文件
#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

enum errorcode
{
    USAGE_ERROR = 1,
};

void Usage()
{
    printf("Usage : ./udp_client serverip serverport\n");
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage();
        exit(USAGE_ERROR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
    }
    // 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
    // a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!
    // b. 什么时候bind呢?首次发送数据的时候
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport); // 主机序列转网络序列 -- 网络序列是大端
    // a.字符串序列点分十进制IP地址 转换为 4字节IP
    // b.主机序列转网络序列
    // in_addr_t inet_addr(const char *cp);
    server.sin_addr.s_addr = inet_addr(serverip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr

    std::string message;
    // 直接通信即可,已经自动绑定
    while (true)
    {
        std::cout << "Please Enter:# ";
        std::getline(std::cin, message);
        // 发送数据
        // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));

        // 获取回应数据
        char buff[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
        ssize_t n = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
        if (n > 0)
        {
            buff[n] = 0;
            std::cout << "Server Echo:# " << buff << std::endl;
        }
    }
}
  • UdpServer.hpp文件
#pragma once

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <string.h>
#include <error.h>
#include <functional>

using namespace std;

#include "Log.hpp"
#include "InetAddr.hpp"

using task_t = function<std::string(std::string)>;

const int Defaultfd = -1;

enum errorcode
{
    CREATE_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};

class UdpServer
{
public:
    // UdpServer(std::string ip, uint16_t port) : _sockfd(Defaultfd), _ip(ip), _port(port), _isrunning(false)
    UdpServer(task_t OnMessage, uint16_t port) : _sockfd(Defaultfd), _port(port), _isrunning(false), _OnMessage(OnMessage)
    {
    }
    void InitServer()
    {
        // 1. 创建udp套接字 -- 必须要做的
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // udp通信
        if (_sockfd < 0)
        {
            LOG(FATAL, "socke create error ! error string %s error %d", strerror(errno), errno);
            exit(CREATE_ERROR);
        }
        // 创建socket成功
        LOG(INFO, "create socket success ! sockfd:", _sockfd);
        // 2.0. 填充sockaddr_in结构
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // 主机序列转网络序列 -- 网络序列是大端
        // a.字符串序列点分十进制IP地址 转换为 4字节IP
        // b.主机序列转网络序列
        // in_addr_t inet_addr(const char *cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr
        local.sin_addr.s_addr = INADDR_ANY; // 0 -- 链接当前服务器的所有ip都接受
        // 2.1. 绑定网络信息
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "socke bind error ! error string %s error %d", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        // 绑定成功
        LOG(INFO, "bind socket success !");
    }
    void Start()
    {
        // 一直运行,直到管理者不想运行了, 服务器都是死循环
        // UDP是面向数据报的协议
        
        _isrunning = true;
        while (true)
        {
            // 获取数据
            // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 既是输入(必须初始化) ,也是输出(peer的实际长度)
            char buff[1024];
            ssize_t n = recvfrom(_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buff[n] = 0;
                // 回答发送方
                // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
                InetAddr addr(peer);
                std::string response = _OnMessage(buff); // 处理收到的消息
                LOG(DEBUG, "get message:%s", buff);
                sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;
    // std::string _ip; // 暂时先这样写 -- 不需要
    uint16_t _port;
    bool _isrunning;

    task_t _OnMessage;
};
  • 运行结果:


7.3、服务器英语单词字典

即客户端输入英文单词,服务器返回中文意思。

  • Dict.hpp文件
#pragma once

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <fstream>
#include <unordered_map>

#include "Log.hpp"

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

class Dict
{
private:
    bool Load()
    {
        std::ifstream in(_file_path);
        if (!in.is_open())
        {
            LOG(FATAL, "open %s error", _file_path.c_str());
            return false;
        }
        std::string line;
        while (std::getline(in, line))
        {
            auto pos = line.find(sep);
            if (pos == std::string::npos)
                continue;
            std::string english = line.substr(0, pos);
            std::string chinese = line.substr(pos + sep.size()); // abc: cc -- pos:3
            LOG(DEBUG, "add %s : %s success", english.c_str(), chinese.c_str());
            _dict[english] = chinese;
        }
        LOG(DEBUG, "Load %s success", _file_path.c_str());
        return true;
    }

public:
    Dict(const std::string file_path = dict_path) : _file_path(file_path)
    {
        Load(); // 加载文件到内存
    }
    std::string Translate(std::string &str, bool *ok)
    {
        *ok = false;
        for (auto &e : _dict)
        {
            if (e.first == str)
            {
                *ok = true;
                return e.second;
            }
        }

        return "未找到";
    }
    ~Dict() {}

private:
    const std::string _file_path;
    std::unordered_map<std::string, std::string> _dict;
};
  • dict.txt文件
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
elephant: 大象
fish: 鱼
grape: 葡萄
house: 房子
ice: 冰
juice: 果汁
kite: 风筝
lion: 狮子
monkey: 猴子
night: 夜晚
orange: 橙子
piano: 钢琴
queen: 女王
rabbit: 兔子
sun: 太阳
tree: 树
umbrella: 雨伞
violin: 小提琴
water: 水
xylophone: 木琴
yogurt: 酸奶
zebra: 斑马
book: 书
chair: 椅子
desk: 桌子
ear: 耳朵
flower: 花
glove: 手套
hat: 帽子
island: 岛
jacket: 夹克
key: 钥匙
lamp: 灯
mountain: 山
notebook: 笔记本
ocean: 海洋
pencil: 铅笔
queen: 女王
river: 河流
shoe: 鞋子
telephone: 电话
umbrella: 雨伞
vase: 花瓶
window: 窗户
yard: 院子
zoo: 动物园
ant: 蚂蚁
bird: 鸟
cloud: 云
door: 门
egg: 鸡蛋
frog: 青蛙
guitar: 吉他
horse: 马
ink: 墨水
jelly: 果冻
king: 国王
leaf: 叶子
moon: 月亮
nest: 鸟巢
octopus: 章鱼
pen: 钢笔
quilt: 被子
rain: 雨
star: 星星
turtle: 乌龟
vulture: 秃鹫
whale: 鲸鱼
x-ray: X光
yo-yo: 溜溜球
airplane: 飞机
beach: 海滩
car: 汽车
diamond: 钻石
eagle: 老鹰
forest: 森林
gold: 黄金
hill: 小山
igloo: 冰屋
jungle: 丛林
kangaroo: 袋鼠
lake: 湖泊
mango: 芒果
nest: 鸟巢
owl: 猫头鹰
pizza: 披萨
queen: 女王
road: 道路
ship: 船
train: 火车
volcano: 火山
window: 窗户
  • InetAddr.hpp文件
#pragma once

#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>

class InetAddr
{
    void GetAddress(std::string *ip, uint16_t *port)
    {
        // char *inet_ntoa(struct in_addr in);
        *ip = inet_ntoa(_addr.sin_addr);
        *port = ntohs(_addr.sin_port);
    }

public:
    InetAddr(const struct sockaddr_in &addr) : _addr(addr)
    {
        GetAddress(&_ip, &_port);
    }

    std::string Ip()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }

    ~InetAddr() {}

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};
  • LockGuard.hpp文件
#include <pthread.h>


class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
    {
        pthread_mutex_lock(_mutex); // 构造加锁
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex); // 析构解锁
    }

private:
    pthread_mutex_t *_mutex;
};
  • Log.hpp文件
#pragma once

#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include "LockGuard.hpp"

bool isSave = false; // 默认向显示器打印
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define FILEPATH "./log.txt"

enum level
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

void SaveToFile(const string &message)
{
    ofstream out(FILEPATH, ios_base::app);
    if (!out.is_open())
        return;
    out << message;
    out.close();
}

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";
    default:
        return "Unknow";
    }
}

std::string GetTimeString()
{
    time_t curr_time = time(nullptr);
    struct tm *format_time = localtime(&curr_time);
    if (format_time == nullptr)
        return "None";
    char buff[1024];
    snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900,
             format_time->tm_mon + 1,
             format_time->tm_mday,
             format_time->tm_hour,
             format_time->tm_min,
             format_time->tm_sec);
    return buff;
}

void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...)
{
    std::string levelstr = LevelToString(level);
    std::string timestr = GetTimeString();
    pid_t pid = getpid();

    char buff[1024];
    va_list arg;
    // int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数
    va_start(arg, format);
    vsnprintf(buff, sizeof(buff), format, arg);
    va_end(arg);

    LockGuard lock(&mutex);
    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';
    if (issave == false)
        std::cout << message;
    else
        SaveToFile(message);
}

// 固定文件名和行数
#define LOG(level, format, ...)                                               \
    do                                                                        \
    {                                                                         \
        LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \
    } while (0)

#define EnableScreen()  \
    do                  \
    {                   \
        isSave = false; \
    } while (0)

#define EnableFile()   \
    do                 \
    {                  \
        isSave = true; \
    } while (0)

void Test(int num, ...)
{
    va_list arg;
    va_start(arg, num);
    while (num--)
    {
        int data = va_arg(arg, int);
        std::cout << data << " ";
    }
    std::cout << std::endl;
    va_end(arg);
}
  • Main.cc文件
#include <iostream>
#include <memory>
#include "UdpServer.hpp"

void Usage()
{
    // printf("./udp_server serverip serverport\n");
    printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0
}

int main(int argc, char *argv[])
{
    // if (argc != 3)
    if (argc != 2)
    {
        Usage();
        exit(USAGE_ERROR);
    }
    // std::string ip = argv[1];
    // uint16_t port = std::stoi(argv[2]);
    uint16_t port = std::stoi(argv[1]);
    // std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);
    usvr->InitServer();
    usvr->Start();

    return 0;
}
  • Makefile文件
.PHONY:all
all:udp_client udp_server

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

.PHONY:clean
clean:
	rm -f udp_server udp_client
  • UdpClient.cc文件
#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

enum errorcode
{
    USAGE_ERROR = 1,
};

void Usage()
{
    printf("Usage : ./udp_client serverip serverport\n");
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage();
        exit(USAGE_ERROR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
    }
    // 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
    // a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!
    // b. 什么时候bind呢?首次发送数据的时候
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport); // 主机序列转网络序列 -- 网络序列是大端
    // a.字符串序列点分十进制IP地址 转换为 4字节IP
    // b.主机序列转网络序列
    // in_addr_t inet_addr(const char *cp);
    server.sin_addr.s_addr = inet_addr(serverip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr

    std::string message;
    // 直接通信即可,已经自动绑定
    while (true)
    {
        std::cout << "Please Enter:# ";
        std::getline(std::cin, message);
        // 发送数据
        // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));

        // 获取回应数据
        char buff[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
        ssize_t n = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
        if (n > 0)
        {
            buff[n] = 0;
            std::cout << "Server Echo:# " << buff << std::endl;
        }
    }
}
  • UdpServer.hpp文件
#pragma once

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <string.h>
#include <error.h>
#include <functional>

using namespace std;

#include "Log.hpp"
#include "InetAddr.hpp"
#include "Dict.hpp"

const int Defaultfd = -1;

enum errorcode
{
    CREATE_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};

class UdpServer
{
public:
    // UdpServer(std::string ip, uint16_t port) : _sockfd(Defaultfd), _ip(ip), _port(port), _isrunning(false)
    UdpServer(uint16_t port) : _sockfd(Defaultfd), _port(port), _isrunning(false)
    {
    }
    void InitServer()
    {
        // 1. 创建udp套接字 -- 必须要做的
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // udp通信
        if (_sockfd < 0)
        {
            LOG(FATAL, "socke create error ! error string %s error %d", strerror(errno), errno);
            exit(CREATE_ERROR);
        }
        // 创建socket成功
        LOG(INFO, "create socket success ! sockfd: %d", _sockfd);
        // 2.0. 填充sockaddr_in结构
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // 主机序列转网络序列 -- 网络序列是大端
        // a.字符串序列点分十进制IP地址 转换为 4字节IP
        // b.主机序列转网络序列
        // in_addr_t inet_addr(const char *cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr
        local.sin_addr.s_addr = INADDR_ANY; // 0 -- 链接当前服务器的所有ip都接受
        // 2.1. 绑定网络信息
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "socke bind error ! error string %s error %d", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        // 绑定成功
        LOG(INFO, "bind socket success !");
    }

    void Start()
    {
        // 一直运行,直到管理者不想运行了, 服务器都是死循环
        // UDP是面向数据报的协议
        _isrunning = true;
        while (true)
        {
            // 获取数据
            // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 既是输入(必须初始化) ,也是输出(peer的实际长度)
            char buff[1024];
            ssize_t n = recvfrom(_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buff[n] = 0;
                // 回答发送方
                // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
                InetAddr addr(peer);

                Dict dict;
                std::string message = buff;
                auto translate = std::bind(&Dict::Translate, Dict(), placeholders::_1, placeholders::_2);
                bool flag;
                std::string response = translate(message, &flag);
                if(!flag) {
                    // 没找到
                    response = "单词没找到";
                }
                LOG(DEBUG, "get message , sender:[%s:%d] , content: %s", addr.Ip().c_str(), addr.Port(), buff);
                sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;
    // std::string _ip; // 暂时先这样写 -- 不需要
    uint16_t _port;
    bool _isrunning;
};
  • 运行结果:


7.4、服务器聊天室

可以多人聊天的聊天室,一人发消息,在聊天室的其他人都可以收到消息内容。

  • InetAddr.hpp文件
#pragma once

#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>

class InetAddr
{
    void GetAddress(std::string *ip, uint16_t *port)
    {
        // char *inet_ntoa(struct in_addr in);
        *ip = inet_ntoa(_addr.sin_addr);
        *port = ntohs(_addr.sin_port);
    }

public:
    InetAddr(const struct sockaddr_in &addr) : _addr(addr)
    {
        GetAddress(&_ip, &_port);
    }

    std::string Ip()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }
    bool operator==(InetAddr &addr)
    {
        return _ip == addr.Ip() && _port == addr.Port();
    }

    const struct sockaddr_in& GetAddr()
    {
        return _addr;
    }

    ~InetAddr() {}

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

  • LockGuard.hpp文件
# pragma once

#include <pthread.h>


class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
    {
        pthread_mutex_lock(_mutex); // 构造加锁
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex); // 析构解锁
    }

private:
    pthread_mutex_t *_mutex;
};

  • Log.hpp文件
#pragma once

#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include "LockGuard.hpp"

bool isSave = false; // 默认向显示器打印
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define FILEPATH "./log.txt"

enum level
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

void SaveToFile(const string &message)
{
    ofstream out(FILEPATH, ios_base::app);
    if (!out.is_open())
        return;
    out << message;
    out.close();
}

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";
    default:
        return "Unknow";
    }
}

std::string GetTimeString()
{
    time_t curr_time = time(nullptr);
    struct tm *format_time = localtime(&curr_time);
    if (format_time == nullptr)
        return "None";
    char buff[1024];
    snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900,
             format_time->tm_mon + 1,
             format_time->tm_mday,
             format_time->tm_hour,
             format_time->tm_min,
             format_time->tm_sec);
    return buff;
}

void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...)
{
    std::string levelstr = LevelToString(level);
    std::string timestr = GetTimeString();
    pid_t pid = getpid();

    char buff[1024];
    va_list arg;
    // int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数
    va_start(arg, format);
    vsnprintf(buff, sizeof(buff), format, arg);
    va_end(arg);

    LockGuard lock(&mutex);
    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';
    if (issave == false)
        std::cout << message;
    else
        SaveToFile(message);
}

// 固定文件名和行数
#define LOG(level, format, ...)                                               \
    do                                                                        \
    {                                                                         \
        LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \
    } while (0)

#define EnableScreen()  \
    do                  \
    {                   \
        isSave = false; \
    } while (0)

#define EnableFile()   \
    do                 \
    {                  \
        isSave = true; \
    } while (0)

void Test(int num, ...)
{
    va_list arg;
    va_start(arg, num);
    while (num--)
    {
        int data = va_arg(arg, int);
        std::cout << data << " ";
    }
    std::cout << std::endl;
    va_end(arg);
}
  • Main.cc文件
#include <iostream>
#include <memory>

#include "UdpServer.hpp"

void Usage()
{
    // printf("./udp_server serverip serverport\n");
    printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0
}

int main(int argc, char *argv[])
{
    // if (argc != 3)
    if (argc != 2)
    {
        Usage();
        exit(USAGE_ERROR);
    }
    // std::string ip = argv[1];
    // uint16_t port = std::stoi(argv[2]);
    uint16_t port = std::stoi(argv[1]);
    // std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);
    usvr->InitServer();
    usvr->Start();

    return 0;
}
  • Makefile文件
.PHONY:all
all:udp_client udp_server

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

.PHONY:clean
clean:
	rm -f udp_server udp_client
  • Thread.hpp文件
#ifndef __THREAD_HPP__
#define __THREAD_HPP__

#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>

using namespace std;

// 封装Linux线程
namespace ThreadModule
{
    using func_t = function<void(string &)>;
    class Thread
    {
    public:
        // /* ThreadData* */Thread(func_t<T> func, T data, const string& name = "default name") : _func(func), _data(data), _threadname(name), _stop(true) {}
        Thread(func_t func, const string &name = "default name") : _func(func), _threadname(name), _stop(true) {}

        void Execute()
        {
            _func(_threadname);

            // _func(_data);
        }

        //  隐含this
        static void *threadroutine(void *arg)
        {
            Thread *self = static_cast<Thread *>(arg);
            self->Execute(); // static 访问不了成员变量
            return nullptr;
        }
        bool Start()
        {
            int n = pthread_create(&_tid, nullptr, threadroutine, this);
            if (!n)
            {
                _stop = false;
                return true;
            }
            else
            {
                return false;
            }
        }

        void Detach()
        {
            if (!_stop)
            {
                pthread_detach(_tid);
            }
        }

        void Join()
        {
            if (!_stop)
            {
                pthread_join(_tid, nullptr);
            }
        }

        string name()
        {
            return _threadname;
        }

        void Stop()
        {
            _stop = true;
        }
        // ~Thread() {}

    private:
        pthread_t _tid;
        string _threadname;
        func_t _func;
        bool _stop;
    };

} // namespace ThreadModule

#endif

  • Threadpool.hpp文件
#pragma once

#include <vector>
#include <queue>
#include <queue>
#include "Thread.hpp"
#include <pthread.h>
#include "LockGuard.hpp"

using namespace ThreadModule;

const int NUM = 3;

template <typename T>
class Threadpool
{
    void LockQueue(pthread_mutex_t &mutex)
    {
        pthread_mutex_lock(&mutex);
    }

    void UnLockQueue(pthread_mutex_t &mutex)
    {
        pthread_mutex_unlock(&mutex);
    }

    void SleepThread(pthread_cond_t &cond, pthread_mutex_t &mutex)
    {
        pthread_cond_wait(&cond, &mutex);
    }
    void WakeUpThread(pthread_cond_t &cond)
    {
        pthread_cond_signal(&cond);
    }
    void WakeUpAll(pthread_cond_t &cond)
    {
        pthread_cond_broadcast(&_cond);
    }
    Threadpool(const int threadnum = NUM) : _threadnum(threadnum), _waitnum(0), _isrunning(false)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        LOG(INFO, "Threadpool Constructor successful ! ");
    }

    void TaskHandler(string &name)
    {
        // sleep(1);
        // cout  << name << " : hh " << endl;
        // sleep(1);
        LOG(DEBUG, "%s is running", name.c_str());
        while (true)
        {
            LockQueue(_mutex);
            while (_task_queue.empty() && _isrunning)
            {
                // 等待
                ++_waitnum;
                SleepThread(_cond, _mutex);
                --_waitnum;
            }
            // 此时一定大于一个线程没有休眠
            if (_task_queue.empty() && !_isrunning)
            {
                // 此时任务队列已经没有内容,且此时线程池已经停止
                UnLockQueue(_mutex);
                cout << name << " quit ... " << endl;
                break;
            }
            LOG(DEBUG, "%s get task sucessful !", name.c_str());
            //  其他情况就得处理任务
            T t = _task_queue.front();
            _task_queue.pop();
            UnLockQueue(_mutex);

            // 处理任务
            t();
            // cout << name << " : " << t.stringResult() << endl;
            // LOG(DEBUG, "%s handler task sucessful ! Result is %s", name.c_str(), t.stringResult().c_str());
            sleep(1);
        }
    }
    void InitThreadPool()
    {
        for (int i = 0; i < _threadnum; ++i)
        {
            string name = "Thread - " + to_string(i + 1);
            _threads.emplace_back(bind(&Threadpool::TaskHandler, this, placeholders::_1), name);
        }
        _isrunning = true;
        LOG(INFO, "Init Threadpool successful !");
    }

public:
    static Threadpool<T> *GetInstance(int threadnum = NUM)
    {

        if (_instance == nullptr)
        {
            LockGuard lockguard(&_lock);
            if (_instance == nullptr)
            {
                // pthread_mutex_lock(&_lock);
                // 第一次创建线程池
                _instance = new Threadpool<T>(threadnum);
                _instance->InitThreadPool();
                _instance->Start();
                LOG(DEBUG, "第一次创建线程池");
                // pthread_mutex_unlock(&_lock);
                return _instance;
            }
        }

        LOG(DEBUG, "获取线程池");
        return _instance;
    }

    bool Enqueue(const T &in)
    {
        bool ret = false;
        LockQueue(_mutex);
        if (_isrunning)
        {
            _task_queue.push(in);
            if (_waitnum > 0)
                WakeUpThread(_cond);
            LOG(DEBUG, "enqueue sucessful...");
            ret = true;
        }

        UnLockQueue(_mutex);
        return ret;
    }

    void Stop()
    {
        LockQueue(_mutex);
        _isrunning = false;
        if (_waitnum > 0)
            WakeUpAll(_cond);
        UnLockQueue(_mutex);
    }

    void Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
            LOG(INFO, "%s is start sucessful...", thread.name().c_str());
        }
    }

    void Wait()
    {
        for (auto &thread : _threads)
        {
            thread.Join();
            LOG(INFO, "%s is quit...", thread.name().c_str());
        }
    }
    ~Threadpool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        LOG(INFO, "delete mutex sucessful !");
    }

private:
    vector<Thread> _threads;
    queue<T> _task_queue;
    int _threadnum;
    int _waitnum;

    pthread_mutex_t _mutex; // 互斥访问任务队列
    pthread_cond_t _cond;

    bool _isrunning;

    // 懒汉模式
    static Threadpool<T> *_instance;
    static pthread_mutex_t _lock;
};

template <typename T>
Threadpool<T> *Threadpool<T>::_instance = nullptr;
template <typename T>
pthread_mutex_t Threadpool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
  • UdpClient.cc文件
#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include "Thread.hpp"

using namespace ThreadModule;

enum errorcode
{
    USAGE_ERROR = 1,
};

void Usage()
{
    printf("Usage : ./udp_client serverip serverport\n");
}

class ThreadData
{
public:
    ThreadData(int sock, struct sockaddr_in &server) : _sockfd(sock), _server(server) {}
    ~ThreadData() {}

public:
    int _sockfd;
    struct sockaddr_in _server;
};

void RecverRoute(ThreadData &td, std::string &threadname)
{
    while (true)
    {
        // 获取回应数据
        char buff[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
        ssize_t n = recvfrom(td._sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
        if (n > 0)
        {
            buff[n] = 0;
            std::cerr << threadname << " | " << buff << std::endl; // 方便重定位,分离发送窗口和接收窗口
        }
    }
}

void SenderRoute(ThreadData &td, std::string &threadname)
{
    pthread_detach(pthread_self());
    while (true)
    {
        std::string message;
        std::cout << threadname << " | Please Enter:# ";
        std::getline(std::cin, message);
        if (message == "QUIT")
        {
            std::cout <<threadname << " : 不玩了!" << std::endl;
            break;
        }
        // 发送数据
        // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
        int n = sendto(td._sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&td._server, sizeof(td._server));
        if (n <= 0)
        {
            std::cout << "sendto error" << std::endl;
            break;
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage();
        exit(USAGE_ERROR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
    }
    // 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
    // a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!
    // b. 什么时候bind呢?首次发送数据的时候
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport); // 主机序列转网络序列 -- 网络序列是大端
    // a.字符串序列点分十进制IP地址 转换为 4字节IP
    // b.主机序列转网络序列
    // in_addr_t inet_addr(const char *cp);
    server.sin_addr.s_addr = inet_addr(serverip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr
    // 直接通信即可,已经自动绑定

    // 创建两个线程,一个收消息,一个发消息
    ThreadData td(sockfd, server);
    auto boundRecv = std::bind(RecverRoute, td, std::placeholders::_1);
    auto boundSend = std::bind(SenderRoute, td, std::placeholders::_1);
    Thread recver(boundRecv, "recver");
    recver.Start();
    Thread sender(boundSend, "sender");
    sender.Start();

    // // udp是全双工的
    // // 下面代码只能半双工,不能不能同时收发
    // while (true)
    // {
    //     std::cout << "Please Enter:# ";
    //     std::getline(std::cin, message);
    //     // 发送数据
    //     // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
    //     sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));

    //     // 获取回应数据
    //     char buff[1024];
    //     struct sockaddr_in peer;
    //     socklen_t len = sizeof(peer);
    //     // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
    //     ssize_t n = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
    //     if (n > 0)
    //     {
    //         buff[n] = 0;
    //         std::cout << "Server Echo:# " << buff << std::endl;
    //     }
    // }

    sender.Join();
    recver.Join();
    return 0;
}
  • UdpServer.hpp文件
#pragma once

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <string.h>
#include <error.h>
#include <pthread.h>
#include <functional>

using namespace std;

#include "Log.hpp"
#include "InetAddr.hpp"
#include "Threadpool.hpp"

const int Defaultfd = -1;

enum errorcode
{
    CREATE_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};

using task_t = function<void()>;

class UdpServer
{
public:
    // UdpServer(std::string ip, uint16_t port) : _sockfd(Defaultfd), _ip(ip), _port(port), _isrunning(false)
    UdpServer(uint16_t port) : _sockfd(Defaultfd), _port(port), _isrunning(false)
    {
        pthread_mutex_init(&_mutex, nullptr);
    }
    void InitServer()
    {
        // 1. 创建udp套接字 -- 必须要做的
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // udp通信
        if (_sockfd < 0)
        {
            LOG(FATAL, "socke create error ! error string %s error %d", strerror(errno), errno);
            exit(CREATE_ERROR);
        }
        // 创建socket成功
        LOG(INFO, "create socket success ! sockfd:", _sockfd);
        // 2.0. 填充sockaddr_in结构
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // 主机序列转网络序列 -- 网络序列是大端
        // a.字符串序列点分十进制IP地址 转换为 4字节IP
        // b.主机序列转网络序列
        // in_addr_t inet_addr(const char *cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr
        local.sin_addr.s_addr = INADDR_ANY; // 0 -- 链接当前服务器的所有ip都接受
        // 2.1. 绑定网络信息
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "socke bind error ! error string %s error %d", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        // 绑定成功
        LOG(INFO, "bind socket success !");

        // Threadpool<task_t>::GetInstance()->Start(); // 启动线程池 // 单例模式GetInstance里面已经启动线程池了,直接放入任务即可
    }

    void AddOnlineUser(InetAddr user)
    {
        LockGuard lockguard(&_mutex);
        for (auto &e : _online_user)
        {
            if (e == user)
                return;
        }
        _online_user.push_back(user);
    }

    void DelOnlineUser(InetAddr user)
    {
        LockGuard lockguard(&_mutex);
        for (auto iter = _online_user.begin(); iter != _online_user.end(); ++iter)
        {
            if (*iter == user)
                _online_user.erase(iter);
        }
    }

    void Route(std::string message)
    {
        LockGuard lockguard(&_mutex);
        for (auto &user : _online_user)
        {
            sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&user.GetAddr(), sizeof(user.GetAddr()));
        }
    }

    void Start()
    {
        // 一直运行,直到管理者不想运行了, 服务器都是死循环
        // UDP是面向数据报的协议
        _isrunning = true;
        while (true)
        {
            // 获取数据
            // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 既是输入(必须初始化) ,也是输出(peer的实际长度)
            char buff[1024];
            ssize_t n = recvfrom(_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buff[n] = 0;
                // 回答发送方
                // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr
                InetAddr addr(peer);
                AddOnlineUser(addr); // 添加用户

                if (!strcmp(buff, "QUIT"))
                {
                    DelOnlineUser(addr); // 删除用户
                    continue;
                }

                // 转发
                std::string message = "[";
                message += addr.Ip();
                message += ":";
                message += std::to_string(addr.Port());
                message += "] ";
                message += buff;
                task_t task = std::bind(&UdpServer::Route, this, message); // 这里绑定后,task的参数就是void(),task就是绑定后的Route
                Threadpool<task_t>::GetInstance()->Enqueue(task);          // 启动线程池

                LOG(DEBUG, "get message , sender:[%s:%d] , content: %s", addr.Ip().c_str(), addr.Port(), buff);
                // sendto(_sockfd, buff, strlen(buff), 0, (struct sockaddr *)&peer, len);
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
        pthread_mutex_destroy(&_mutex);
    }

private:
    int _sockfd;
    // std::string _ip; // 暂时先这样写 -- 不需要
    uint16_t _port;
    bool _isrunning;

    pthread_mutex_t _mutex;
    std::vector<InetAddr> _online_user;
};
  • 运行结果:

    注意,下面用到的clienta和clientb都是管道文件,用来对文件的标准输出和标准错误分离。


8、补充内容

地址转换函数:将点分十进制的IP字符串转换为in_addr类型的IP地址,或者反过来。

#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);
int inet_aton(const char *cp, struct in_addr *inp);

const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
char *inet_ntoa(struct in_addr in);
// The inet_ntoa() function  converts  the  Internet  host address  in,  given  in  networkbyte  order,  to  a  string in IPv4 dotted-decimal  notation. The  string  is  returned in a statically  allocated  buffer,which  subsequent  calls  will overwrite.

对应inet_ntoa,man手册上说会返回一个静态分配的缓冲区,后续调用会覆盖前面的内容,不需要我们手动释放。那么如果我们多次调用这个函数会出现什么情况?

在我们使用多线程调用该函数时,可能会出现数据错误的情况(需要使用互斥锁的保护)!

并且,在 APUE 中, 明确提出 inet_ntoa 不是线程安全的函数。

在多线程环境下,推荐使用 inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题


OKOK,Linux网络编程之UDP就到这里,如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

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

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

相关文章

R语言画散点图-饼图-折线图-柱状图-箱线图-直方图-曲线图-热力图-雷达图

R语言画散点图-饼图-折线图-柱状图-箱线图-直方图-曲线图-热力图-雷达图 散点图示例解析效果 饼图示例解析效果 折线图示例解析效果 柱状图示例解析效果 箱线图示例解析效果 直方图示例解析效果 曲线图使用 curve() 函数示例效果 使用 plot() 函数示例效果 使用 ggplot2 包绘制…

iMazing 3 换手机后苹果游戏数据还有吗 换iPhone怎么转移游戏数据

当你想要更换手机&#xff0c;无论是选择升级到最新款iPhone&#xff0c;或者换到“经典”旧款iPhone&#xff0c;单机游戏数据的转移总是让人发愁。本文将详细介绍换手机后苹果游戏数据还有吗&#xff0c;以及换iPhone怎么转移游戏数据&#xff0c;确保你能无缝继续你的游戏体…

jenkins+gitlab+harbor+maven自动化容器部署

一、gitlab安装配置 1.1、安装 由于比较懒啊&#xff01;这里就直接使用docker安装了啊&#xff01; 没事先更新一个yum源&#xff1a;yum update -y 整一个gitlab镜像&#xff1a;docker pull gitlab/gitlab-ce 运行一个gitlab容器&#xff1a;docker run -d -p 8443:443 -p…

安装 Maven

安装 Maven 的步骤&#xff1a; 1. 访问 Maven 官方网站: https://maven.apache.org/download.cgi 2. 下载 Maven 的二进制文件 3. 解压下载的文件到希望安装的目录 4. 将 Maven 的 bin 目录添加到您的系统环境变量 PATH 中&#xff08;配置环境变量&#xff09; 这个步骤可…

Ubuntu24.04 NFS 服务配置

1、NFS 介绍 NFS 是 Network FileSystem 的缩写&#xff0c;顾名思义就是网络文件存储系统&#xff0c;它允许网络中的计算机之间通过 TCP/IP 网络共享资源。通过 NFS&#xff0c;我们本地 NFS 的客户端应用可以透明地读写位于服务端 NFS 服务器上的文件&#xff0c;就像访问本…

el-menu弹出菜单样式不生效

1. 使用 ruoyi 项目时出现的问题。 <template><el-menu:default-active"activeMenu":collapse"false":unique-opened"true"class"container":collapse-transition"true"mode"horizontal"><sideba…

CH01_WPF概述

第1章&#xff1a;WPF概述 本章目标 了解Windows图形演化了解WPF高级API了解分辨率无关性概念了解WPF体系结构了解WPF 4.5 WPF概述 ​ 欢迎使用 Windows Presentation Foundation (WPF) 桌面指南&#xff0c;这是一个与分辨率无关的 UI 框架&#xff0c;使用基于矢量的呈现引…

核函数支持向量机(Kernel SVM)

核函数支持向量机&#xff08;Kernel SVM&#xff09;是一种非常强大的分类器&#xff0c;能够在非线性数据集上实现良好的分类效果。以下是关于核函数支持向量机的详细数学模型理论知识推导、实施步骤与参数解读&#xff0c;以及两个多维数据实例&#xff08;一个未优化模型&a…

【iOS】—— isMemberOfClass isKindOfClass以及源码

【iOS】—— isMemberOfClass & isKindOfClass以及源码 isa指针示例源码解析&#xff1a;isKindOfClass&#xff1a;源码解析&#xff08;实例方法和类方法&#xff09;isMemberOfClass&#xff1a;源码解析&#xff08;实例方法和类方法&#xff09;源码分析总结&#xff…

MF173:将多个工作表转换成PDF文件

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

【C++】16. set 和 map

在之前的博客中&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身。 我们这篇博客的内容是关联式容器&#xff…

负重3Kg垂起固定翼无人机技术详解

一、基本参数 负重3Kg垂起固定翼无人机是一款具备强大负载能力和长航时特性的无人机。其基本参数如下&#xff1a; - 最大负载&#xff1a;3Kg - 最大续航时间&#xff1a;203分钟&#xff08;示例数据&#xff0c;实际续航时间可能根据任务负载、环境条件等因素有所不同&…

敲详细的springframework-amqp-rabbit源码解析

看源码时将RabbitMQ的springframework-amqp-rabbit和spring-rabbit的一套区分开&#xff0c;springboot是基于RabbitMQ的Java客户端建立了简便易用的框架。 springboot的框架下相对更多地使用消费者Consumer和监听器Listener的概念&#xff0c;这两个概念不注意区分容易混淆。…

【electron6】浏览器实时播放PCM数据

pcm介绍&#xff1a;PCM&#xff08;Puls Code Modulation&#xff09;全称脉码调制录音&#xff0c;PCM录音就是将声音的模拟信号表示成0,1标识的数字信号&#xff0c;未经任何编码和压缩处理&#xff0c;所以可以认为PCM是未经压缩的音频原始格式。PCM格式文件中不包含头部信…

FPGA文档阅读

FPGA的文档没有相应的基础还真不容易看懂&#xff0c;下面是B站上对FPGA文档的解读(本文非对文档解读&#xff0c;只是为个人记录第三期&#xff1a;CycloneIV E最小系统板设计&#xff08;一&#xff09;从Datasheet上获取FPGA的基本参数_哔哩哔哩_bilibili 电源部份 核心电…

elasticsearch, kibana, 6.8.18 版本下的创建索引,指定timestamp,java CRUD,maven版本等

ELK 这一套的版本更迭很快&#xff0c; 而且es常有不兼容的东西出现&#xff0c; 经常是搜一篇文章&#xff0c;看似能用&#xff0c;拿到我这边就不能用了。 很是烦恼。 我这边的ELK版本目前是 6.8.18&#xff0c;这次的操作记录一下。 &#xff08;涉密内容略有删改&#xf…

SQL语句——DDL数据定义语言

1.sql语言不区分大小写 2._&#xff08;下划线&#xff09;进行名字的分割&#xff0c;不适用驼峰命名 3.; 语句sql结尾处加一个;来表示结束 4.一般关键词建议用大写 5.所有名称不能用中文 1.创建数据库 CREATE DATABASE [IF NOT EXISTS] 库名 -- 库 #创建库 #create databa…

计算机网络序章

计算机网络学习什么&#xff1f; 下列举例由用户使用计算机角度去理解 首先&#xff0c;计算机网络是通过路由等方式去获取我们希望的数据用户可以在APP中去进行方便的操作去获取数据。每个应用都有自己的端口去确定每次来的数据是否是自己需要的数据应该应该传到哪里&#x…

COD论文笔记 Deep Gradient Learning for Efficient Camouflaged 2022

动机 这篇论文的动机在于解决伪装目标检测(COD)中的一个关键问题&#xff1a;在复杂背景下&#xff0c;伪装目标与背景的边界模糊&#xff0c;使得检测变得极其困难。现有的方法&#xff0c;如基于边界或不确定性的模型&#xff0c;通常仅响应于伪装目标的稀疏边缘&#xff0c…

oceanbase架构、功能模块、数据存储、特性、sql流转层等概念详解

一、架构图 OceanBase 数据库采用无共享&#xff08;Shared-Nothing&#xff09;分布式集群架构&#xff0c;各个节点之间完全对等&#xff0c;每个节点都有自己的 SQL 引擎、存储引擎、事务引擎&#xff0c;运行在普通 PC 服务器组成的集群之上&#xff0c;具备高可扩展性、高…