前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Linux】Socket编程—UDP

【Linux】Socket编程—UDP

作者头像
大耳朵土土垚
发布2025-02-10 14:22:58
发布2025-02-10 14:22:58
5100
代码可运行
举报
文章被收录于专栏:c/c++c/c++
运行总次数:0
代码可运行

1.Echo server

简单的回显服务器和客户端代码。

  • 服务器代码:
代码语言:javascript
代码运行次数:0
复制
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>

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

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

using namespace LogModule;
using namespace InetAddrModule;

const static int gsockfd = -1;
const static uint16_t gdefaultport = 8080;

class UdpServer
{
public:
    UdpServer(uint16_t port = gdefaultport)
        : _sockfd(gsockfd),
          _addr(port),
          _isrunning(false)
    {
    }
    void InitServer()
    {
        // 1. 创建socket
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;

        // 2. bind : 设置进入内核中
        int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
            Die(BIND_ERR);
        }

        LOG(LogLevel::INFO) << "bind success";
    }
    bool Start()
    {
        _isrunning = true;
      
        while (true)
        {
            char inbuffer[1024]; // string
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 必须设定
            ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer)-1, 0, CONV(&peer),&len);
            if(n > 0)
            {
                inbuffer[n] = 0;
                InetAddr client(peer);//获取cilent相关信息
             
                std::string cilentmessage = client.Ip()+":"+std::to_string(client.Port())+"# "+ inbuffer;
                LOG(LogLevel::DEBUG)<<cilentmessage;
                

                //将获取到的信息写回client
                std::string echo_string = "echo# ";
                echo_string += inbuffer;
                ::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,client.NetAddr(),client.NetAddrLen());

            }
        }
         _isrunning = false;
    }

    ~UdpServer()
    {
        if (_sockfd > gsockfd)
            ::close(_sockfd);
    }

private:
    InetAddr _addr; // 服务器地址包括ip和port
    int _sockfd;
    bool _isrunning; // 服务器运行状态
};

#endif

要使用网络服务器需要使用socket创建套接字,然后将IP和端口号bind进入内核,最后就可以调用recv/sendto接口进行网络发送和接收信息了

服务器使用代码:

代码语言:javascript
代码运行次数:0
复制
#include "UdpServer.hpp"

// ./server_udp localport
int main(int argc, char *argv[])
{
    ENABLE_CONSOLE_LOG_STRATEGY();

    std::unique_ptr<UdpServer> svr_uptr;
    if (argc == 2)
    {
        uint16_t port = std::stoi(argv[1]);
        svr_uptr = std::make_unique<UdpServer>(port);
    }
    else
        svr_uptr = std::make_unique<UdpServer>();


    svr_uptr->InitServer();
    svr_uptr->Start();

    return 0;
}

服务器需要输入端口号,不输入也行,服务器默认端口号为8080

  • 客户端代码:
代码语言:javascript
代码运行次数:0
复制
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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

using namespace LogModule;
using namespace InetAddrModule;

int sockfd = -1;


//./udp_client server_ip server_port
int main(int argc, char *argv[])
{
    if(argc!=3)
    {
        LOG(LogLevel::ERROR)<<"Usage:" << argv[0] << " serverip serverport" ;
        Die(ARGV_ERR);
    }

    //1.创建sockfd
    sockfd = ::socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd < 0)
    {
        LOG(LogLevel::WARNING)<<"client sockfd fail...";
        Die(SOCKET_ERR);
    } 
    LOG(LogLevel::INFO)<<"client sockfd success...";

    //2.填充服务器信息
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    InetAddr ServerAddr(serverip,serverport);

    //3.发送请求给服务器
    while(true)
    {
        //3.1获取信息
        std::cout << "Please Enter# ";
        std::string message;
        std::getline(std::cin, message);

        //3.2发送信息给服务器
        ssize_t n = ::sendto(sockfd,message.c_str(),sizeof(message),0,ServerAddr.NetAddr(),ServerAddr.NetAddrLen());
        if(n < 0)
        {
            LOG(LogLevel::ERROR)<<"client sendto fail...";
            continue;
        }

        //3.3从服务器接收信息
        char buffer[1024];
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        ssize_t m = ::recvfrom(sockfd,buffer,sizeof(buffer)-1,0,CONV(&tmp),&len);
        if(m > 0)
        {
            buffer[m] = 0;
            std::cout<<buffer<<std::endl;
        }
        else
        {
            LOG(LogLevel::ERROR)<<"client recvfrom fail...";
        }
    }
    return 0;
}

同样,客户端要进行网络通信也需要创建套接字,但是不需要bind信息进入内核,因为在接收到网络信息时会自动进行bind

客户端需要输入服务器IP地址和端口号port

  • 网络地址类:

因为在进行网络通信时不可避免的需要频繁使用到相关信息,所以我们可以考虑将它们封装成为一个类,设置一些常用的方法

代码语言:javascript
代码运行次数:0
复制
#pragma once

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

namespace InetAddrModule
{
    class InetAddr
    {
    private:
        void PortNet2Host() // port网络转主机
        {
            _port = ::ntohs(_net_addr.sin_port);
        }
        void IpNet2Host() // IP网络转主机
        {
            char ipbuffer[64];
            const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
            _ip = ip;
        }

    public:
        InetAddr()
        {

        }

        InetAddr(const struct sockaddr_in &addr) : _net_addr(addr) // 获取传来的sockaddr的ip和port
        {
            PortNet2Host();
            IpNet2Host();
        }

        InetAddr(uint16_t port) : _port(port), _ip("")
        {
            _net_addr.sin_family = AF_INET;
            _net_addr.sin_port = htons(_port);//主机转网络
            _net_addr.sin_addr.s_addr = INADDR_ANY;//表示可以介绍任何ip地址
        }

        InetAddr(const std::string& ip,uint16_t port) : _port(port), _ip(ip)
        {
            _net_addr.sin_family = AF_INET;
            _net_addr.sin_port = htons(_port);//主机转网络
            _net_addr.sin_addr.s_addr = ::inet_addr(ip.c_str());
        }

        struct sockaddr *NetAddr() { return CONV(&_net_addr); }
        socklen_t NetAddrLen() { return sizeof(_net_addr); }
        std::string Ip() { return _ip; }
        uint16_t Port() { return _port; }
        ~InetAddr()
        {
        }

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

因为各种机器之间不兼容等例如大端/小端模式,所以在实际进行网络通信时我们需要将发送的ip地址和端口号从主机模式转换为网络模式。

在网络编程中,当一个进程需要绑定一个网络端口以进行通信时,可以使用INADDR_ANY 作为 IP 地址参数。这样做意味着该端口可以接受来自任何 IP 地址的连接请求,无论是本地主机还是远程主机。例如,如果服务器有多个网卡(每个网卡上有不同的 IP 地址),使用 INADDR_ANY 可以省去确定数据是从服务器上具体哪个网卡/IP 地址上面获取的。

结果如下:

在这里插入图片描述
在这里插入图片描述

2. Dict server

  上述echo server仅仅是将收到的消息回显给客户端,其实我们还可以在服务器中加一点业务处理,比如翻译功能。

  所以我们可以创建一个Dictionary类,将翻译词典封装起来:

Dict.txt:

代码语言:javascript
代码运行次数:0
复制
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

Dict.hpp:

代码语言:javascript
代码运行次数:0
复制
#pragma once

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

namespace DictionaryModule
{
    const std::string sep = ": "; // 分割符

    using namespace LogModule;
    class Dict
    {
    private:
    //将词典内容从Dict.txt中加载进来
        void DownloadDict()
        {
            std::ifstream in(_dictpath);
            if (!in.is_open())
            {
                LOG(LogLevel::WARNING) << "DownloadDict fail...";
                return;
            }

            std::string line;
            while (getline(in, line))
            {
                if (line.empty())
                    continue;

                // 加入词典
                size_t pos = line.find(sep);
                _dict.insert({line.substr(0, pos), line.substr(pos + sep.size())});
            }
        }

    public:
        Dict(const std::string &dictpath = "./Dict.txt") : _dictpath(dictpath)
        {
            DownloadDict(); // 加载词典
        }

        std::string Translate(const std::string &key)
        {
            auto iter = _dict.find(key);
            if (iter == _dict.end())
                return std::string("Unknown");
            else
                return iter->second;
        }
        ~Dict()
        {
        }

    private:
        std::string _dictpath;
        std::unordered_map<std::string, std::string> _dict;
    };
}

  有了翻译的功能后,我们就可以将其嵌入服务器内部使用,所以我们在UdpServer类成员中添加一个回调方法,并在Start函数中使用:

代码语言:javascript
代码运行次数:0
复制
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

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

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

using namespace LogModule;
using namespace InetAddrModule;

using func_t = std::function<std::string(const std::string&)>;
const static int gsockfd = -1;
const static uint16_t gdefaultport = 8080;

class UdpServer
{
public:
    UdpServer(func_t func,uint16_t port = gdefaultport)
        : _sockfd(gsockfd),
          _addr(port),
          _isrunning(false),
          _func(func)
    {
    }
    void InitServer()
    {
        // 1. 创建socket
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;

        // 2. bind : 设置进入内核中
        int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
            Die(BIND_ERR);
        }

        LOG(LogLevel::INFO) << "bind success";
    }
    bool Start()
    {
        _isrunning = true;
      
        while (true)
        {
            char inbuffer[1024]; // string
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 必须设定
            ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer)-1, 0, CONV(&peer),&len);
            if(n > 0)
            {
                inbuffer[n] = 0;
                InetAddr client(peer);//获取cilent相关信息
             
                std::string cilentmessage = client.Ip()+":"+std::to_string(client.Port())+"# "+ inbuffer;
                LOG(LogLevel::DEBUG)<<cilentmessage;
                
                //调用回调方法处理翻译业务
                std::string value = _func(inbuffer);

                //将获取到的信息写回client
                std::string echo_string = "Translate# ";
                echo_string += value;
                ::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,client.NetAddr(),client.NetAddrLen()); 
            }
        }
         _isrunning = false;
    }

    ~UdpServer()
    {
        if (_sockfd > gsockfd)
            ::close(_sockfd);
    }

private:
    InetAddr _addr; // 服务器地址包括ip和port
    int _sockfd;
    bool _isrunning; // 服务器运行状态
    func_t _func; //回调业务方法
};

#endif

  最后在定义服务器时使用lambda表达式将Dict类中的Translate方法绑定给UdpServer

代码语言:javascript
代码运行次数:0
复制
#include "UdpServer.hpp"
#include "Dict.hpp"

using namespace DictionaryModule;

// ./server_udp localport
int main(int argc, char *argv[])
{
    ENABLE_CONSOLE_LOG_STRATEGY();
    Dict dictionary;
    std::unique_ptr<UdpServer> svr_uptr;
    if (argc == 2)
    {
        uint16_t port = std::stoi(argv[1]);
        svr_uptr = std::make_unique<UdpServer>([&dictionary](const std::string& key){return dictionary.Translate(key);},port);
    }
    else
        svr_uptr = std::make_unique<UdpServer>([&dictionary](const std::string& key){return dictionary.Translate(key);});


    svr_uptr->InitServer();
    svr_uptr->Start();

    return 0;
}

客户端函数不需要改变可以直接使用,结果如下:

在这里插入图片描述
在这里插入图片描述

3. ChatServer

  对于聊天室的实现,我们需要对聊天对象进行管理,所以需要新建一个类usermanager以及描述聊天对象的类user:

代码语言:javascript
代码运行次数:0
复制
#pragma once

#include <iostream>
#include <list>
#include <memory>
#include <algorithm>
#include <sys/types.h>
#include <sys/socket.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
namespace UserModule
{
    using namespace InetAddrModule;
    using namespace LogModule;
    using namespace MutexModule;

    class UserInterface
    {
    public:
        virtual ~UserInterface() = default;
        virtual void SendTo(int sockfd, const std::string &message) = 0; // 纯虚函数
        virtual bool operator==(const InetAddr &u) const = 0;
        virtual std::string Id() = 0;
    };

	//描述对象
    class User : public UserInterface
    {
    public:
        User(const InetAddr &id) : _id(id)
        {
        }

        void SendTo(int sockfd, const std::string &message) override
        {
           // ssize_t n = ::sendto(sockfd, &message, message.size(), 0, _id.NetAddr(), _id.NetAddrLen());错误错误!!!!不能取地址message
            ssize_t n = ::sendto(sockfd, message.c_str(), message.size(), 0, _id.NetAddr(), _id.NetAddrLen());
            LOG(LogLevel::DEBUG) << "send message to " << _id.Addr() << " info: " << message;

            if (n < 0)
            {
                LOG(LogLevel::WARNING) << "Snedto fail...";
                return;
            }
        }
        bool operator==(const InetAddr &u) const override
        {
            return _id == u;
        }

        std::string Id()
        {
            return _id.Addr();
        }

        ~User()
        {
        }

    private:
        InetAddr _id;
    };
	
	//管理对象
    class UserManage
    {
    public:
        UserManage()
        {
        }
        void AddUser(InetAddr &id)
        {
            LockGuard lock(_mutex);//因为要访问公共资源所以要加锁保护
            // 1.先遍历整个链表查找是否已经添加过了
            for (auto &user : _online_user)
            {
                if (*user == id) // User已经重载==
                {
                    LOG(LogLevel::INFO) << id.Addr() << "用户已经存在...";
                    return;
                }
            }

            // 2.如果是新用户就添加
            _online_user.push_back(std::make_shared<User>(id));
            LOG(LogLevel::INFO) << "添加用户: " << id.Addr() << "成功...";
        }

        void DelUser(InetAddr &id)
        {
            LockGuard lock(_mutex);
            // 1.先遍历整个链表查找是否有该用户
            auto pos = std::remove_if(_online_user.begin(), _online_user.end(), [&id](std::shared_ptr<UserInterface> &user)
                                      { return *user == id; });

            // 2.如果有就删除
            _online_user.erase(pos, _online_user.end());
        }

        // 路由转发
        void Router(int sockfd, const std::string &message)
        {
            LockGuard lock(_mutex);
            for (auto &user : _online_user)
            {
                user->SendTo(sockfd, message);
            }
        }

        void PrintUser()
        {
            LockGuard lock(_mutex);
            for (auto user : _online_user)
            {
                LOG(LogLevel::DEBUG) << "在线用户-> " << user->Id();
            }
        }

        ~UserManage()
        {
        }

    private:
        std::list<std::shared_ptr<UserInterface>> _online_user;
        Mutex _mutex;
    };
};

对于描述对象参数我们可以使用之前实现的InetAddr类,对于对象的管理方法主要有添加对象、删除对象以及路由转发(群发)这三个部分;因为后续有多个线程而它们内部实现需要访问公共资源,所以需要加锁保护。

  在服务器代码中其他都与前面类似,我们只需要将服务器的Start方法修改一下即可:

代码语言:javascript
代码运行次数:0
复制
bool Start()
    {
        _isrunning = true;

        while (true)
        {
            char inbuffer[1024]; // string
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 必须设定
            ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len);
            if (n > 0)
            {
                inbuffer[n] = 0;
                InetAddr client(peer); // 1.获取cilent相关信息

                std::string message = client.Addr() + "# " + inbuffer;
                LOG(LogLevel::DEBUG) << message;

                // 2.判断是否为quit信息
                if (std::strcmp(inbuffer, "quit") == 0)
                {
                    _deluser(client);
                    message = client.Addr() + "# " + "我走了,你们聊!";
                }
                else
                {
                    // 3.添加新用户
                    _adduser(client);
                }
                    // 3. 构建转发任务,推送给线程池,让线程池进行转发
                    task_t task = std::bind(UdpServer::_router, _sockfd, message);
                    ThreadPool<task_t>::GetInstance()->Enqueue(task);
                
            }
        }
        _isrunning = false;
    }

服务器不再是简单的接收信息,还需要对接收的消息进行处理;因为转发任务消耗的时间可能较长,我们可以利用之前实现的线程池来处理多个转发任务,主线程则继续收消息然后往线程池里添加转发任务。

  除了Start方法,服务器类也需要添加几个回调方法(在Start方法中使用):

代码语言:javascript
代码运行次数:0
复制
using add_t = std::function<void(InetAddr &id)>;
using del_t = std::function<void(InetAddr &id)>;
using router_t = std::function<void(int sockfd, const std::string &message)>;
using task_t = std::function<void()>;
class UdpServer
{
public:
    UdpServer(add_t adduser, del_t deluser, router_t router, uint16_t port = gdefaultport)
        : _sockfd(gsockfd),
          _addr(port),
          _isrunning(false),
          _adduser(adduser),
          _deluser(deluser),
          _router(router)
    {
    }
               
private:
    InetAddr _addr; // 服务器地址包括ip和port
    int _sockfd;
    bool _isrunning; // 服务器运行状态
    add_t _adduser;
    del_t _deluser;
    router_t _router;
};

#endif

  在main函数中使用服务器对象时就需要绑定上述回调方法:

代码语言:javascript
代码运行次数:0
复制
#include "UdpServer.hpp"
#include "User.hpp"
using namespace UserModule;

// ./server_udp localport
int main(int argc, char *argv[])
{
    ENABLE_CONSOLE_LOG_STRATEGY();

    std::shared_ptr<UserManage> um = std::make_shared<UserManage>();

    std::unique_ptr<UdpServer> svr_uptr;
    if (argc == 2)
    {
        uint16_t port = std::stoi(argv[1]);
        svr_uptr = std::make_unique<UdpServer>([&um](InetAddr &id)
        { return um->AddUser(id); },
        [&um](InetAddr &id)
        { return um->DelUser(id); },
        [&um](int sockfd, const std::string &message)
        { return um->Router(sockfd, message); },port);
    }
    else
        svr_uptr = std::make_unique<UdpServer>([&um](InetAddr &id)
                                               { return um->AddUser(id); },
                                               [&um](InetAddr &id)
                                               { return um->DelUser(id); },
                                               [&um](int sockfd, const std::string &message)
                                               { return um->Router(sockfd, message); });

    svr_uptr->InitServer();
    svr_uptr->Start();

    return 0;
}

  对于客户端代码,我们也需要创建两个线程,主线程用来向服务器发送消息,另一个线程则用来接收群发的消息:

代码语言:javascript
代码运行次数:0
复制
 #include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>

int sockfd = -1;
struct sockaddr_in server;

void ClientQuit(int signo)
{
    (void)signo;
    const std::string quit = "QUIT";
    int n = ::sendto(sockfd, quit.c_str(), quit.size(), 0, CONV(&server), sizeof(server));
    exit(0);
}

void *Recver(void *args)
{
    while (true)
    {
        (void)args;
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        char buffer[1024];
        int n = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&temp), &len);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl; // 代码没问题,重定向也没问题,管道读写同时打开,才会继续向后运行
            // fprintf(stderr, "%s\n", buffer);
            // fflush(stderr);
        }
    }
}

// CS
// ./client_udp serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;
        Die(USAGE_ERR);
    }
    signal(2, ClientQuit);
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        Die(SOCKET_ERR);
    }
    std::cout<<"sockfd: "<<sockfd<<std::endl;
    // 1.1 填充server信息
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = ::htons(serverport);
    server.sin_addr.s_addr = ::inet_addr(serverip.c_str());

    pthread_t tid;
    pthread_create(&tid, nullptr, Recver, nullptr);

    // 1.2 启动的时候,给服务器推送消息即可
    const std::string online = " ... 来了哈!";
    int n = ::sendto(sockfd, online.c_str(), online.size(), 0, CONV(&server), sizeof(server));

    // 2. clientdone
    while (true)
    {
        std::cout << "Please Enter# ";
        std::string message;
        std::getline(std::cin, message);
        // client 不需要bind吗?socket <-> socket
        // client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
        // 而是,客户端首次sendto消息的时候,由OS自动进行bind
        // 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
        // 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
        int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));
        (void)n;
    }

    return 0;
}

在运行客户端代码之前,我们可以创建一个管道将其重定向到cerro,然后运行客户端,这样服务器群发收到的消息就会写入到管道中

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.Echo server
  • 2. Dict server
  • 3. ChatServer
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档