简单的UDP网络程序·续写

news2024/12/24 2:13:24

该文承接文章 简单的UDP网络程序

对于客户端和服务端的基本源码参考上文,该文对服务器润色一下,并且实现几个基本的业务服务逻辑

目录

demo1

第一个功能:字典翻译

初始化字典

测试代码:打印

字符串分割

客户端修改

成品效果

字典热加载

signal

demo2

远端命令行解析

popen

远端操作服务器执行命令

对客户端稍加修改

添加一个函数

效果

小知识

字典&&远端操作服务器执行命令代码

udpServer.cc

udpServer.hpp

udpClient.cc

dict.txt

demo3

聊天室

效果一:没上线

效果二:上线了

聊天室源码

精简

onlineUser.hpp

udpClient.cc

udpClient.hpp

udpServer.cc

udpServer.hpp


demo1

第一个功能:字典翻译

这个用于对发送过来的数据进行简单的翻译,采取文件读取的方式

先创建一个文件,注意在当前目录下建立一个 dict.txt文件放入一些待翻译的字符组,例如下面

apple:苹果
banana:香蕉
hello:你好
world:世界

初始化字典

获取数据 

unordered_map<string, string> dict; // 一个字典
const std::string dictTxt = "./dict.txt";

static void initDict()
{
    ifstream in(dictTxt, std::ios::binary); // 二进制形式打开一个文件
    if (!in.is_open())
    {
        std::cerr << "open file" << dictTxt << "error" << endl;
        exit(OPEN_ERR); //已经添加至错误联合体中
    }
    string line;
    while (getline(in, line))
    {
        cout << line << endl; //-- 测试初始化是否成功
    }

    in.close();
}

小知识:getline的返回值虽然不是bool类型的但是它会被隐式类型转换,所以我们直接放到while里面充当判断是没有问题的

利用getline获得数据

 

测试代码:打印

字符串分割

输出结果

static bool cutString(const string &target, string *s1, string *s2, const string &sep)  //把sep暴露出来可以让外部进行传递
{
    //apple:苹果
    auto pos = target.find(sep);
    if(pos == string::npos) return false;
    *s1 = target.substr(0, pos);
    *s2 = target.substr(pos + sep.size());  //默认到最后一个
    return true;
}

客户端修改

需要拥有把数据接受并且处理的功能

成品效果

字典热加载

实现当捕捉到2号信号时候,重新加载字典里面的数据,用以翻译

signal

demo2

远端命令行解析

实现一个对于简单的命令,通过客户端发送消息给服务端,操作服务端命令

认识接口

popen

远端操作服务器执行命令

对客户端稍加修改

 

添加一个函数

直接写一个回调函数,给下面回调使用,体现了解耦的方便性

 

 

 udpServer.cc 添加函数

// demo2
void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
    // 1.cmd
    // 2.如果必要,可能需要fork, exec*

    if (cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("rmdir") != string::npos)
    {
        cerr << clientip << ":" << clientport << " 正在做一个非法的操作: " << cmd << endl;
        return;
    }

    string response;
    FILE *fp = popen(cmd.c_str(), "r");
    if(fp == nullptr) response = cmd + " exec failed";
    char line[1024];
    while(fgets(line, sizeof(line), fp))
    {
        response += line;
    }
    pclose(fp);

    //开始返回 -- 同上没有变化
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str()); // 字符串转网络(点分十进制 转 数字序列)

    sendto(sockfd, response.c_str(), response.size(), 0, (const sockaddr *)&client, sizeof(client));
}

效果

 

注意:这里是几个简单的Xshell指令,只能分析一些基本的指令,是一个原理性的代码

小知识

 

字典&&远端操作服务器执行命令代码

udpServer.cc

#include "udpServer.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>
#include <stdio.h>

using namespace std;
using namespace Server;

static void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_port\n\n"; // 命令提示符
}

unordered_map<string, string> dict; // 一个字典
const std::string dictTxt = "./dict.txt";

static bool cutString(const string &target, string *s1, string *s2, const string &sep) // 把sep暴露出来可以让外部进行传递
{
    // apple:苹果
    auto pos = target.find(sep);
    if (pos == string::npos)
        return false;
    *s1 = target.substr(0, pos);
    *s2 = target.substr(pos + sep.size()); // 默认到最后一个
    return true;
}

static void initDict()
{
    ifstream in(dictTxt, std::ios::binary); // 二进制形式打开一个文件
    if (!in.is_open())
    {
        std::cerr << "open file" << dictTxt << "error" << endl;
        exit(OPEN_ERR); // 已经添加至错误联合体中
    }
    string line;
    std::string key, value;
    while (getline(in, line))
    {
        // cout << line << endl; -- 测试初始化是否成功
        if (cutString(line, &key, &value, ":"))
        {
            dict.insert(make_pair(key, value));
        }
    }

    in.close();

    cout << "load dict succss" << endl;
}

void reload(int signal)
{
    (void)signal;
    initDict();
}

static void debugPrint()
{
    for (auto &dt : dict)
    {
        cout << dt.first << " # " << dt.second << endl;
    }
}

// demo1
void handlerMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    // 这里就可以对message进行特定的业务处理,而不关心message怎么来的 --- 这就是server通信和业务逻辑解耦!
    // 幼儿园版的业务逻辑
    string response_message;
    auto iter = dict.find(message);
    if (iter == dict.end())
        response_message = "unknown";
    else
        response_message = iter->second;

    // 开始返回
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str()); // 字符串转网络(点分十进制 转 数字序列)

    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (const sockaddr *)&client, sizeof(client));
}

// demo2
void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
    // 1.cmd
    // 2.如果必要,可能需要fork, exec*

    if (cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("rmdir") != string::npos)
    {
        cerr << clientip << ":" << clientport << " 正在做一个非法的操作: " << cmd << endl;
        return;
    }

    string response;
    FILE *fp = popen(cmd.c_str(), "r");
    if(fp == nullptr) response = cmd + " exec failed";
    char line[1024];
    while(fgets(line, sizeof(line), fp))
    {
        response += line;
    }
    pclose(fp);

    //开始返回 -- 同上没有变化
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str()); // 字符串转网络(点分十进制 转 数字序列)

    sendto(sockfd, response.c_str(), response.size(), 0, (const sockaddr *)&client, sizeof(client));
}

int main(int argc, char *argv[])
{
    if (argc != 2) // 这里我们只想要传递两个参数,所以当argc不是3的时候就直接报错退出就行了,注意文件名运行的那个指令也会算进去所以argc +1
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]); // 使用atoi强转,因为argv里面放置的都是字符串,类型需要转换
    // string ip = argv[1];

    //signal(2, reload); // 信号捕捉 CTRL + C (2号信号)
    //initDict();        // 字典初始化
    //debugPrint();   字典打印测试

    //std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));
    std::unique_ptr<udpServer> usvr(new udpServer(execCommand, port));

    usvr->initServer();
    usvr->start();

    return 0;
}

udpServer.hpp

#pragma once
 
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
 
namespace Server
{
    using namespace std;
    static const string defaultIp = "0.0.0.0"; // 直接使用这个缺省值,代表监听机器上的所有ip端口
    static const int gnum = 1024;
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        OPEN_ERR
    }; // 1.命令行输入错误 2.创建套接字错误 3.绑定端口号错误
 
    typedef function<void(int, string, uint16_t, string)> func_t;
 
    class udpServer
    {
    public:
        udpServer(const func_t &cd, const uint16_t &port, const string &ip = defaultIp)
            : _callback(cd), _port(port), _ip(ip), _sockfd(-1)
        {
        }
        void initServer() // 初始化
        {
            // 1.创建套接字
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR); // 创建套接字失败直接终止进程
            }
            cout << "udpServer success: "
                 << " : " << _sockfd << endl;
 
            // 2.绑定套接字(port, ip)
            // 未来服务器要明确的port, 不能随意改变 -- 变了别人就找不到了
            struct sockaddr_in local;     // 这里是定义了一个变量,在栈上,而且是用户层,还没有bind之前都是没有产生联系
            bzero(&local, sizeof(local)); // 先填 0 再修正
            // 注意这下面几个名字是拼接出来的,就是那个##拼接而来的
            local.sin_family = AF_INET;                     // 这里设置与套接字的AF_INET设置意义是不一样的,socket是创建一个网络通信的套接字,在这里是填充一个sockaddr_in的结构体用来网络通信
            local.sin_port = htons(_port);                  // 你如果给别人发信息,你的port和ip要不要发送给对方? 答案是要的
            local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. string->unit32_t  2. htonl(); -> inet_addr
            // local.sin_addr.s_addr = htonl(INADDR_ANY);  //可以主机转网络,不够也可以不处理,直接赋值也行
            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n == -1)
            {
                cerr << "bin error: " << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
            // UDP Server 的预备工作完成
        }
        void start() // 启动
        {
            // 服务器的本质其实就是一个死循环
            char buffer[gnum]; // 定义一个数组来充当缓冲区
            for (;;)
            {
                // 读取数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer); // 必须设置成这个结构体的大小,当作为输入时,告诉recvfrom的长度的多少
                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                // 关系两件事情
                // 1.数据是什么? 2. 谁发的?
                if (s > 0)
                {
                    buffer[s] = 0;
                    // 因为是从网络上读取的,所以一定要转,可以使用接口
                    // inet_ntoa 将一个网络字节序的IP地址(也就是结构体in_addr类型变量)转化为点分十进制的IP地址(字符串)
                    string clientip = inet_ntoa(peer.sin_addr); // 1.网络序列 2.整数 -> 点分十进制的ip
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;
 
                    cout << clientip << "[" << clientport << "]# " << message << endl;
 
                    //我们只把数据读上来就完了吗? 我们要对数据进行处理 -- 所以我们用回调函数的方式来解决
                    _callback(_sockfd, clientip, clientport, message);
                }
            }
        }
        ~udpServer() // 析构
        {
        }
 
    private:
        uint16_t _port;
        // 实际上,一款网络服务器,不建议指明一个IP,因为一个服务器可以能有多个ip,万一用户使用其他的ip地址来访问该端口号(这里是8080,就收不到了),这也是我们为什么使用0.0.0.0的IP缺省值
        string _ip;
        int _sockfd;
        func_t _callback; // 回调函数,用以处理数据
    };
}

 

udpClient.cc

#include "udpClient.hpp"
#include <memory>
 
using namespace Client;
 
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n"; //命令提示符
}
 
// ./udpClient server_ip server_port    第一个是运行方式 要知道要发送的服务端的ip地址 和 端口号
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
 
 
    unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));
 
    ucli->initClient();
    ucli->run();
 
    return 0;
}

udpClient.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace Client
{
    using namespace std;
    class udpClient
    {
    public:
        udpClient(const string &serverip, const uint16_t &serverport)
            : _serverip(serverip), _serverprot(serverport), _sockfd(-1), _quit(false)
        {
        }
        void initClient()
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(1); // 创建套接字失败直接终止进程
            }
            cout << "socket success: "
                 << " : " << _sockfd << endl; // 成功打印出来

            // 2. client要不要bind(必须要!)因为bind就是和系统、网络产生联系. client要不要显示的bind -> 需不需要程序员bind? 名字不重要,重要的是唯一性的,和服务端是不一样的
            // 就像宿舍号是几不重要,有就行了。一个端口号只能被一个客户端绑定,就像是服务端是明星,客户端是民众,民众名字不重要
            // 服务端是具体的一家公司,比如抖音是字节的,就像一个手机有很多app比如抖音,快手这样的客户端,不能让它们固定bind端口号,万一其他公司也用了用一个端口就冲突其他不来了
            // 写服务器的是一家公司,写client是无数家公司 -- 有OS自动形成端口进行bind!不需要自己操作,包括ip地址也不需要,OS自己会处理,当然也可以自己写
            // 那么OS在什么时候,如何bind
        }
        void run()
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverprot);

            string messages;
            char cmdline[1024];
            while (!_quit)
            {
                cout << "[xxx@我的远程机器 project42]# ";   // ls -a -l
                //cin >> messages; 读取不到空格
                fgets(cmdline, sizeof(cmdline), stdin);
                messages = cmdline;

                sendto(_sockfd, messages.c_str(), messages.size(), 0, (struct sockaddr *)&server, sizeof(server));

                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t temp_len = sizeof(temp);
                size_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &temp_len);
                if(n >= 0) buffer[n] = 0; //把这里先当作字符串进行处理,这里是C式风格的
                cout << "\n" << buffer << endl;
            }
        }
        ~udpClient()
        {
        }

    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverprot;
        bool _quit;
    };

}

dict.txt

apple:苹果
banana:香蕉
hello:你好
world:世界
success:除了成功,别无选择
nihao:你好

demo3

聊天室

 

效果一:没上线

 

没有上线在大厅是看不到的

 

 

效果二:上线了

上线就可以看到了

 

 

聊天室源码

精简

onlineUser.hpp

 

#pragma once

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

using namespace std;

class User
{
public:
    User(const string &ip, const uint16_t &port) : _ip(ip), _port(port)
    {
    }
    ~User()
    {
    }
    string ip() { return _ip; }
    uint16_t port() { return _port; }

private:
    string _ip;
    uint16_t _port;
};

class OnlineUser
{
public:
    OnlineUser()
    {
    }
    ~OnlineUser()
    {
    }
    void addUser(const string &ip, const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        users.insert(make_pair(id, User(ip, port)));
    }
    void delUser(const string &ip, const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        users.erase(id);
    }
    bool isOnline(const string &ip, const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        return users.find(id) == users.end() ? false : true;
    }
    void broadcastMessage(int sockfd, const string &ip, const uint16_t &port, const string &messages)
    {
        for (auto &user : users)
        {
            struct sockaddr_in client;
            bzero(&client, sizeof(client));
            client.sin_family = AF_INET;
            client.sin_port = htons(user.second.port());
            client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());

            string s = ip + "-" + to_string(port) +  "# ";   //把用户名用ip加端口号的形式显示出来,谁发的拿谁的
            s += messages;
            sendto(sockfd, s.c_str(), s.size(), 0, (const sockaddr *)&client, sizeof(client));
        }
    }

private:
    unordered_map<string, User> users;
};

udpClient.cc

#include "udpClient.hpp"
#include <memory>
 
using namespace Client;
 
static void Usage(string proc)
{
    cerr << "\nUsage:\n\t" << proc << " server_ip server_port\n\n"; //命令提示符 demo3 为了测试
}
 
// ./udpClient server_ip server_port    第一个是运行方式 要知道要发送的服务端的ip地址 和 端口号
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
 
 
    unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));
 
    ucli->initClient();
    ucli->run();
 
    return 0;
}

 

udpClient.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>

namespace Client
{
    using namespace std;
    class udpClient
    {
    public:
        udpClient(const string &serverip, const uint16_t &serverport)
            : _serverip(serverip), _serverprot(serverport), _sockfd(-1), _quit(false)
        {
        }
        void initClient()
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(1); // 创建套接字失败直接终止进程
            }
            cout << "socket success: "
                 << " : " << _sockfd << endl; // 成功打印出来
        }

        // 类内线程必须使用静态方法
        static void *readMessage(void *args)
        {
            // 新线程只负责读消息
            int sockfd = *(static_cast<int *>(args));
            pthread_detach(pthread_self()); // 分离

            while (true)    //死循环一直读
            {
                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t temp_len = sizeof(temp);
                size_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &temp_len);
                if (n >= 0)
                    buffer[n] = 0;      // 把这里先当作字符串进行处理,这里是C式风格的
                cout << buffer << endl; // demo3
            }

            return nullptr;
        }
        void run()
        {
            // 主线程只负责发消息
            pthread_create(&_reader, nullptr, readMessage, (void *)&_sockfd);

            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverprot);

            string messages;
            char cmdline[1024];
            while (!_quit)
            {

                //cerr << "# "; // demo3 因为使用了管道的重定向,1号被占用,所以使用2号cerr打印提示符
                fprintf(stderr, "Enter# ");
                fflush(stderr);   //刷新
                fgets(cmdline, sizeof(cmdline), stdin);
                cmdline[strlen(cmdline)-1] = 0; //把反斜杠n处理掉
                messages = cmdline;
                sendto(_sockfd, messages.c_str(), messages.size(), 0, (struct sockaddr *)&server, sizeof(server));

            }
        }
        ~udpClient()
        {
        }

    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverprot;
        bool _quit;

        pthread_t _reader;
        pthread_t _writer;
    };

}

udpServer.cc

#include "udpServer.hpp"
#include "onlineUser.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>
#include <stdio.h>

using namespace std;
using namespace Server;

static void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_port\n\n"; // 命令提示符
}

OnlineUser onlineuser; // 先定义一个全局的对象
// demo3
void routeMessage(int sockfd, string clientip, uint16_t clientport, string messages)
{
    if (messages == "online")onlineuser.addUser(clientip, clientport);
    if (messages == "offline")onlineuser.delUser(clientip, clientport);
    if (onlineuser.isOnline(clientip, clientport))
    {
        // 消息的路由
        onlineuser.broadcastMessage(sockfd, clientip, clientport, messages);
    }
    else
    {
        // 还没有上线,单独给该用户发送
        struct sockaddr_in client;
        bzero(&client, sizeof(client));

        client.sin_family = AF_INET;
        client.sin_port = htons(clientport);
        client.sin_addr.s_addr = inet_addr(clientip.c_str()); // 字符串转网络(点分十进制 转 数字序列)

        string response = "你还没有上线,请先上线,运行: online";
        sendto(sockfd, response.c_str(), response.size(), 0, (const sockaddr *)&client, sizeof(client));
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2) // 这里我们只想要传递两个参数,所以当argc不是3的时候就直接报错退出就行了,注意文件名运行的那个指令也会算进去所以argc +1
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]); // 使用atoi强转,因为argv里面放置的都是字符串,类型需要转换

    std::unique_ptr<udpServer> usvr(new udpServer(routeMessage, port)); // 在线聊天室

    usvr->initServer();
    usvr->start();

    return 0;
}

udpServer.hpp

#pragma once
 
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
 
namespace Server
{
    using namespace std;
    static const string defaultIp = "0.0.0.0"; // 直接使用这个缺省值,代表监听机器上的所有ip端口
    static const int gnum = 1024;
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        OPEN_ERR
    }; // 1.命令行输入错误 2.创建套接字错误 3.绑定端口号错误
 
    typedef function<void(int, string, uint16_t, string)> func_t;
 
    class udpServer
    {
    public:
        udpServer(const func_t &cd, const uint16_t &port, const string &ip = defaultIp)
            : _callback(cd), _port(port), _ip(ip), _sockfd(-1)
        {
        }
        void initServer() // 初始化
        {
            // 1.创建套接字
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR); // 创建套接字失败直接终止进程
            }
            cout << "udpServer success: "
                 << " : " << _sockfd << endl;
 
            // 2.绑定套接字(port, ip)
            // 未来服务器要明确的port, 不能随意改变 -- 变了别人就找不到了
            struct sockaddr_in local;     // 这里是定义了一个变量,在栈上,而且是用户层,还没有bind之前都是没有产生联系
            bzero(&local, sizeof(local)); // 先填 0 再修正
            // 注意这下面几个名字是拼接出来的,就是那个##拼接而来的
            local.sin_family = AF_INET;                     // 这里设置与套接字的AF_INET设置意义是不一样的,socket是创建一个网络通信的套接字,在这里是填充一个sockaddr_in的结构体用来网络通信
            local.sin_port = htons(_port);                  // 你如果给别人发信息,你的port和ip要不要发送给对方? 答案是要的
            local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. string->unit32_t  2. htonl(); -> inet_addr
            // local.sin_addr.s_addr = htonl(INADDR_ANY);  //可以主机转网络,不够也可以不处理,直接赋值也行
            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n == -1)
            {
                cerr << "bin error: " << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
            // UDP Server 的预备工作完成
        }
        void start() // 启动
        {
            // 服务器的本质其实就是一个死循环
            char buffer[gnum]; // 定义一个数组来充当缓冲区
            for (;;)
            {
                // 读取数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer); // 必须设置成这个结构体的大小,当作为输入时,告诉recvfrom的长度的多少
                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                // 关系两件事情
                // 1.数据是什么? 2. 谁发的?
                if (s > 0)
                {
                    buffer[s] = 0;
                    // 因为是从网络上读取的,所以一定要转,可以使用接口
                    // inet_ntoa 将一个网络字节序的IP地址(也就是结构体in_addr类型变量)转化为点分十进制的IP地址(字符串)
                    string clientip = inet_ntoa(peer.sin_addr); // 1.网络序列 2.整数 -> 点分十进制的ip
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;
 
                    cout << clientip << "[" << clientport << "]# " << message << endl;
 
                    //我们只把数据读上来就完了吗? 我们要对数据进行处理 -- 所以我们用回调函数的方式来解决
                    _callback(_sockfd, clientip, clientport, message);
                }
            }
        }
        ~udpServer() // 析构
        {
        }
 
    private:
        uint16_t _port;
        // 实际上,一款网络服务器,不建议指明一个IP,因为一个服务器可以能有多个ip,万一用户使用其他的ip地址来访问该端口号(这里是8080,就收不到了),这也是我们为什么使用0.0.0.0的IP缺省值
        string _ip;
        int _sockfd;
        func_t _callback; // 回调函数,用以处理数据
    };
}

下篇预告:基于windows环境在Visual Studio 2022下与Linux云服务器进行通信交流

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

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

相关文章

idea如何一个项目启动两个tomcat服务(不同端口)

目录 第一步&#xff1a;创建一个普通的springboot项目 第二步&#xff1a;编写一个controller代码&#xff0c;用来接收请求 第三步&#xff1a;复制服务&#xff0c;端口设置为8081 第四步&#xff1a;依次访问8080、8081两个服务进行测试 第一步&#xff1a;创建一个普通…

基于JavaWeb的保护动物管理系统设计与实现

摘要&#xff1a;随着全球一些稀有物种、野生动物日益稀少&#xff0c;保护动物已经成为全球多个国家开始重视并投入大量物力着手解决的重要课题。动物是大自然的产物&#xff0c;自然界是由许多复杂的生态系统构成的。有一种植物消失了&#xff0c;以这种植物为食的昆虫就会消…

easyExcel导入失败提示用户第几行有误并回滚数据

思路&#xff1a; 在controller定义一个map&#xff0c;将map传入excel监听器&#xff0c;在监听器中处理excel的数据&#xff0c;读取到某一行出现错误就将错误提示信息存入map并抛出一个异常给service。在service方法上开启事务&#xff0c;并将异常出实现数据回滚&#xff0…

vite使用vue3 v-for渲染图片时的写法

写法一 <template><divclass"demo-img"v-for"item in demoOption":key"item.code":style"background-image: url(${getImageUrl(../assets/imge/${item.img}.jpeg)})"></div> </template><script lang&qu…

2023网安最流行的10大工具(附安装包)

从事网络安全工作&#xff0c;手上自然离不开一些重要的网络安全工具。今天&#xff0c;分享10大网络安全工具。 一、Kali Linux Kali 是一个基于 Debian 的 Linux 发行版。它的目标就是为了简单&#xff1a;在一个实用的工具包里尽可能多的包含渗透和审计工具。Kali 实现了这…

Linux 之Python 定制篇-APT 软件管理和远程登录

Linux 之Python 定制篇-APT 软件管理和远程登录 apt 介绍 apt 是Advanced Packaging Tool 的简称&#xff0c;是一款安装包管理工具。在Ubuntu 下&#xff0c;我们可以使用apt 命令进行软件包的安装、删除、清理等&#xff0c;类似于Windows 中的软件管理工具。 unbuntu 软件…

【C++】深拷贝浅拷贝

前言&#xff1a;在 C 中&#xff0c;当一个对象通过赋值或拷贝即将成为另一个对象的副本时&#xff0c;会发生“浅拷贝”或“深拷贝”。 什么是浅拷贝&#xff1f; 浅拷贝是指两个对象共享同一个内存地址&#xff0c;即将源对象的数据成员的地址赋给了目标对象&#xff0c;当…

vue diff算法与虚拟dom知识整理(15) 终结篇,收尾新前到旧前全部不匹配情况

我们现在就只需要处理最后一种情况了 我们在 updateChildren.js 在while中 的if最后加个 else 当他们都没哟匹配到的情况 我们现在在updateChildren.js最上面 定义一个空对象 叫 keyMap 参考代码如下 let keyMap null;然后 在我们刚写的else中编写代码如下 //判断 如果…

Stable-Diffusion|入门怎么下载与使用civitai网站的模型(二)

C站&#xff1a;https://civitai.com/ 文章目录 1 样图2 实现2.1 下载主模型并放到正确文件夹中2.2 找到prompt2.3 生成2.4 Lora使用 3 一些有趣的项目3.1 胶片风格Lora3.2 [最近很火] 现实感很强的Majicmix-realistic 先贴几张笔者自己实验的图&#xff0c;模型来自&#xff1…

Libevent学习

一、Libevent概述 1、简介 Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库&#xff0c;主要有以下几个亮点&#xff1a;事件驱动&#xff08; event-driven&#xff09;&#xff0c;高性能;轻量级&#xff0c;专注于网络&#xff0c;不如 ACE 那么臃肿庞大&…

C++必背基础知识点总结

重点&#xff1a;不借助任何外部资料就能掌握&#xff0c;考试面试大概率涉及。 掌握&#xff1a;不借助任何外部资料就能掌握&#xff0c;考试面试有可能涉及。 熟悉&#xff1a;可以适当参考资料掌握&#xff0c;考试面试有可能涉及。 了解&#xff1a;可是参考资料掌握&…

iptables的四表五链

文章目录 1. Iptables的链2.Iptables的表3.数据包过滤的匹配流程3.1 规则表之间的顺序3.2 规则链之间的顺序3.3 规则链内部各条防火墙规则之间的顺序3.4如何应用 每个 规则表&#xff0c;其实就相当于一个内核空间的容器&#xff0c; 按照规则集的不同用途进行划分为默认的四…

w11+wsl+3060安装cuda等深度学习环境

把一切都重新又梳理学习了一遍&#xff0c;然后发现很多事情其实没必要弄 0. 显卡驱动、nvidia-smi、cuda、nvcc、cuDNN、pytorch、cudatoolkit与它们之间的关系 笔者本人鸟枪换炮了&#xff0c;还是wsl环境香&#xff0c;但是按照官方教程跑完后&#xff0c;遇到了bugRuntim…

RTMPose关键点检测实战——笔记3

文章目录 摘要安装MMPose安装虚拟环境安装pytorch安装MMCV安装其他的安装包下载 MMPose下载预训练模型权重文件和视频素材 安装MMDetection安装Pytorch安装MMCV安装其它工具包下载 MMDetection安装MMDetection下载预训练模型权重文件和视频素材 MMPose预训练模型预测命令行的方…

关于render: h => h(App)的解释

当我们第一次安装完脚手架&#xff0c;打开 的时候&#xff0c;我相信&#xff0c;一定有小伙伴和我一样&#xff0c;看到main.js里面的render: h > h(App),感觉懵懵的。 因为&#xff0c;在刚开始接触vue的时候&#xff0c;我们这里是这样写的&#xff1a; 而使用了脚手…

iOS性能优化-异步绘制与异步底层View处理

前言&#xff1a; 基于UIKit的性能优化似乎已经到了瓶颈&#xff0c;无论是使用frame代理snpakit&#xff0c;缓存高度&#xff0c;减少布局层次&#xff0c;diff刷新&#xff0c;压缩图片&#xff0c;选择合适队列&#xff0c;选择高性能锁&#xff0c;也不能满足当前庞大而又…

SpringBoot 整合 MongoDB 实现数据的增删改查功能

1、介绍说明 在 MongoDB 中有三个比较重要的名词&#xff1a;数据库、集合、文档 数据库&#xff08;Database&#xff09;&#xff1a;和关系型数据库一样&#xff0c;每个数据库中有自己的用户权限&#xff0c;不同的项目组可以使用不同的数据库 集合&#xff08;Collectio…

汽车新能源 - 单体电压值为什么通常是5V以内

常见蓄电池单体电压的值&#xff08;25℃&#xff09;&#xff0c;如下表&#xff1a; 蓄电池类型单体电压&#xff08;V&#xff09;铅酸 蓄电池2.08镍金属氢 蓄电池&#xff08;NIMH&#xff09;1.32锂离子 蓄电池2.5~4.2&#xff08;典型3.6&#xff09; 单体电压为什么不…

安卓学习笔记(一)

从今天开始我们开始学习安卓的知识&#xff1a; 1.0 Android基础入门教程 1.Android背景与当前的状况 Android系统是由Andy Rubin创建的&#xff0c;后来被Google收购了&#xff1b;最早的版本是:Android 1.1版本 而现在最新的版本是今年5.28&#xff0c;Google I/O大会上推…

Logstash部署与使用

ElasticSearch 1、ElasticSearch学习随笔之基础介绍 2、ElasticSearch学习随笔之简单操作 3、ElasticSearch学习随笔之java api 操作 4、ElasticSearch学习随笔之SpringBoot Starter 操作 5、ElasticSearch学习随笔之嵌套操作 6、ElasticSearch学习随笔之分词算法 7、ElasticS…