UDP Socket编程的三级跳:简单到复杂的优雅过渡

目录

[1. 基础篇:UdpEchoServer(V1)](#1. 基础篇:UdpEchoServer(V1))

[- 核心代码剖析](#- 核心代码剖析)

[1.1 UDPServer.hpp](#1.1 UDPServer.hpp)

[1.2 UDPServer.cc](#1.2 UDPServer.cc)

[1.3 UDPClient.cc](#1.3 UDPClient.cc)

[1.4 演示代码编译结果](#1.4 演示代码编译结果)

[2. 进阶篇:DictionaryServer(V2)](#2. 进阶篇:DictionaryServer(V2))

[- 功能扩展:单词查询服务](#- 功能扩展:单词查询服务)

[2.1 Dictionary.hpp](#2.1 Dictionary.hpp)

[2.2 配置文件dic.txt](#2.2 配置文件dic.txt)

[2.3 DicServer.hpp](#2.3 DicServer.hpp)

[2.4 DicServer.cc](#2.4 DicServer.cc)

[2.5 DicClient.cc](#2.5 DicClient.cc)

[2.6 演示代码实现结果](#2.6 演示代码实现结果)

[3. 实战篇:聊天室ChatServerDemo(V3)](#3. 实战篇:聊天室ChatServerDemo(V3))

[- 封装网络地址](#- 封装网络地址)

[3.1 InetAddr.hpp](#3.1 InetAddr.hpp)

[- 路由器设置](#- 路由器设置)

[3.2 Route.hpp](#3.2 Route.hpp)

[- 服务端设置](#- 服务端设置)

[3.3 ChatServer.hpp](#3.3 ChatServer.hpp)

[3.4 Server.cc](#3.4 Server.cc)

[- 客户端设置](#- 客户端设置)

[3.5 Client.cc](#3.5 Client.cc)

[- 代码实现结](#- 代码实现结)果

1. 基础篇:UdpEchoServer(V1)

- 核心代码剖析

1.1 UDPServer.hpp

我们采用面向对象方式封装socket相关操作,使用RAII思想管理资源,状态标志标识服务器的启停,并且我还使用到了前面文章讲过的日志实现:https://blog.csdn.net/whispers421/article/details/154695948?spm=1001.2014.3001.5502

cpp 复制代码
#pragma once

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<strings.h>
#include<cstdlib>
#include"Log.hpp"

int default_sockfd = -1;

class UDPServer
{
public:
    UDPServer(uint16_t port)
    :_sockfd(default_sockfd)
    ,_port(port)
    ,_isrunning(false)
    {}
    //初始化socket
    void Init()
    { 
        //... 
    }
    //开启服务器
    void Start()
    { 
        //... 
    }
    //关闭服务器
    void Stop()
    { 
        //... 
    }
    ~UDPServer(){}
private:
    int _sockfd;            // socket 文件描述符
    uint16_t _port;         // 监听端口
    bool _isrunning;        // 运行状态标志
};

初始化socket:创建socket -> 绑定端口和IP地址

cpp 复制代码
void Init()
    {
        //创建socketfd
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        //AF_INET:网络通信   SOCK_DGRAM:TCP通信  
        if(_sockfd<0)
        {
            LOG(4)<<" create socket error";
            exit(1);
        }
        LOG(1)<<" create socket success,sockfd: "<<_sockfd;
        //bind
        //1. 填充IP和port
        struct sockaddr_in local;
        bzero(&local,sizeof(local));//把local全部清零
        //重新开始填充
        local.sin_family = AF_INET;//网络通信
        local.sin_port = htons(_port);
        //local.sin_addr.s_addr=inet_addr(_ip.c_str());//将字符串转整数IP,整数IP是网络序列的
        //不明确具体IP,只要是发给对应的主机对应的port,都能收到!
        local.sin_addr.s_addr = htonl(INADDR_ANY);//任意IP绑定
        //和socket进行bind
        int b = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(b<0)
        {
            LOG(4)<<" bind socket error";
            exit(2);
        }
        LOG(1)<<" bind socket success,sockfd:"<<_sockfd;
    }

网络字节序转换:

  • htons(): host to network short(16位端口转换)

  • htonl(): host to network long(32位IP转换)

  • INADDR_ANY = 0.0.0.0,表示接收任意来源的数据

服务器启动代码

cpp 复制代码
void Start()
    {
        _isrunning = true;

        while(_isrunning)
        {
            char buffer[1024];
            buffer[0] = 0;//清空缓存区
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            //读取数据
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
            if(n>0)
            {
                //读取成功

                //获取client的IP和port
                uint16_t clientport = ntohs(peer.sin_port);
                std::string clientip = inet_ntoa(peer.sin_addr);

                buffer[n] = 0;
                LOG(0)<<"[ip:"<<clientip<<" port:"<<clientport<<"]# "<<buffer;
                std::string echo_string = " server encho# ";
                echo_string+=buffer;
                sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);
            }
        }
        _isrunning = false;
    }
  • recvfrom() 会阻塞直到收到数据

  • 同时获取数据和客户端地址信息

  • UDP 是无连接的,每次通信都需要指定目标地址

1.2 UDPServer.cc
cpp 复制代码
#include"UDPServer.hpp"
#include<memory>

//服务器使用手册------要输入端口号
void UDPServerManual()
{
    std::cout<<"UDPServerManual"<<std::endl;
    std::cout<<"Please enter the port"<<std::endl;
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        UDPServerManual();  // 显示使用说明
        return 1;
    }
    
    // 启用日志策略
    EnableConsoleLogStrategy();
    
    // 使用智能指针管理 UDPServer 对象
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<UDPServer> usvr = std::make_unique<UDPServer>(port);
    
    // 启动服务器
    usvr->Init();
    usvr->Start();
    
    return 0;
}
  • 验证参数,提供使用提示手册

  • 使用 unique_ptr 避免内存泄漏

  • 模块化启动流程

1.3 UDPClient.cc
cpp 复制代码
#include <iostream>
#include <cstring>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

//客户端使用手册
void UDPClientManual()
{
    std::cout << "UDPClientManual" << std::endl;
    std::cout << "Please enter the port and IP" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        UDPClientManual();
        return 1;
    }
    //参数解析
    std::string serverip = argv[1];//获取服务器ip
    uint16_t serverport = std::stoi(argv[2]);//服务器端口号
    // 创建客户端socketfd
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    // AF_INET:网络通信   SOCK_DGRAM:TCP通信
    if (sockfd < 0)
    {
        std::cout << "client create socket error" << std::endl;
        exit(1);
    }
    // 创建并设置服务器地址
    struct sockaddr_in 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());

    //循环交互
    while (true)
    {
        std::cout << " Please Enter@ ";
        // 获取要发的内容
        std::string line;
        std::getline(std::cin, line);
        // 发送信息
        sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)&server, sizeof(server));
        // 回显服务端数据
        //  struct sockaddr_in tmp;
        //  socklen_t len = sizeof(tmp);
        //  char buffer[1024];
        //  int r = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp,&len);
        //  if(r>0)
        //  {
        //      buffer[r] = 0;
        //      std::cout<<buffer<<std::endl;
        //  }
    }
    return 0;
}

客户端特点:

  • 不需要 bind(),系统自动分配端口

  • 每次 sendto() 都需要指定服务器地址

  • 可设计为同步或异步接收模式

1.4 演示代码编译结果

编译代码:

先启动服务器:

再开启客户端:

客户端发送消息:

服务器接收消息并回显:

2. 进阶篇:DictionaryServer(V2)

在基础的UdpEchoServer基础上增加了单词查询服务,也就是将前面我们服务器在接收到客户端信息直接回显的基础上,变成了回显为接收到的消息的翻译结果,达到翻译效果

- 功能扩展:单词查询服务

2.1 Dictionary.hpp

封装翻译功能的实现

cpp 复制代码
#pragma once

#include<iostream>
#include<string>
#include<filesystem>
#include<unordered_map>

std::string sep = ": "; //分隔标识符

class Dictionary
{
private:
    //加载配置文件
    void LoadConf()
    {
        std::ifstream in(_path);
        if(!in.is_open())
        {
            LOG(4)<<" open infile error";
            return;
        }
        std::string line;
        while(std::getline(in,line))
        {
            LOG(0)<<"load dic message:"<<line;
            auto pos = line.find(sep);//line中找到sep开始下标
            if(pos<0)
            {
                //如果没有找到sep分隔符
                LOG(2)<<"find error";
                continue;
            }
            std::string word = line.substr(0,pos);// [)
            std::string value = line.substr(pos+sep.size());
            if(word.empty()||value.empty())
            {
                LOG(2)<<"word || value empty, getline:"<<line;
                continue;
            }
            _dic.insert(std::make_pair(word,value));
        }
        in.close();
    }
public:
    Dictionary(std::string path)
    :_path(path)
    {
        LoadConf();
    }
    //实现翻译功能
    std::string Translate(const std::string& word,const std::string& whoip,uint16_t& whoport)
    {
        auto pos = _dic.find(word);
        if(pos == _dic.end())
        {
            return "Sorry, I can't find";
        }
        return pos->first+": "+pos->second;
    }
    ~Dictionary(){}
private:
    std::string _path;  //配置文件路径
    std::unordered_map<std::string,std::string> _dic; //相当于词典,存储{英文,中文}的结构
};
2.2 配置文件dic.txt
2.3 DicServer.hpp

在UDPServer.hpp的基础上添加了一个用于处理翻译任务的回调函数

cpp 复制代码
#pragma once

#include<iostream>
#include<string>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<strings.h>
#include<cstdlib>
#include<functional>
#include"Log.hpp"

//回调函数------用于翻译
using callback_t = std::function<std::string \
(const std::string& word,const std::string& whoip,uint16_t& whoport)>;

int default_sockfd = -1;
class DicServer
{
    public:
    DicServer(uint16_t port,callback_t cb)
    :_sockfd(default_sockfd)
    ,_port(port)
    ,_isrunning(false)
    ,_cb(cb)
    {}

    void Init()
    {
        //创建socketfd
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        //AF_INET:网络通信   SOCK_DGRAM:TCP通信  
        if(_sockfd<0)
        {
            LOG(4)<<" create socket error";
            exit(1);
        }
        
        LOG(1)<<" create socket success,sockfd: "<<_sockfd;

        //bind
        //1. 填充IP和port
        struct sockaddr_in local;
        bzero(&local,sizeof(local));//把local全部清零
        //重新开始填充
        local.sin_family = AF_INET;//网络通信
        local.sin_port = htons(_port);
        //local.sin_addr.s_addr=inet_addr(_ip.c_str());//将字符串转整数IP,整数IP是网络序列的
        //不明确具体IP,只要是发给对应的主机对应的port,都能收到!
        local.sin_addr.s_addr = htonl(INADDR_ANY);//任意IP绑定
        //和socket进行bind
        int b = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(b<0)
        {
            LOG(4)<<" bind socket error";
            exit(2);
        }
        LOG(1)<<" bind socket success,sockfd:"<<_sockfd;
    }
    void Start()
    {
        _isrunning = true;

        while(_isrunning)
        {
            char buffer[1024];
            buffer[0] = 0;//清空缓存区
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            //读取数据
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
            if(n>0)
            {
                //读取成功
                buffer[n] = 0;
                //获取client的IP和port
                uint16_t clientport = ntohs(peer.sin_port);
                std::string clientip = inet_ntoa(peer.sin_addr); 
                // LOG(0)<<"[ip:"<<clientip<<" port:"<<clientport<<"]# "<<buffer;
                std::string ret = _cb(buffer,clientip,clientport);
                std::string echo_string = " server encho# ";
                echo_string+=ret;
                std::cout<<echo_string<<std::endl;
                //把数据发给客户端
                sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);
            }
        }
        _isrunning = false;
    }
    void Stop()
    {
        _isrunning = false;
    }
    ~DicServer(){}
    private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;
    bool _isrunning;

    callback_t _cb;
};
2.4 DicServer.cc
cpp 复制代码
#include"DicServer.hpp"
#include"Dictionary.hpp"
void DicServerManual()
{
    std::cout<<"DicServerManual"<<std::endl;
    std::cout<<"Please enter the port"<<std::endl;
}

int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        DicServerManual();
        return 1;
    }
    EnableConsoleLogStrategy();
    uint16_t port = std::stoi(argv[1]);
    Dictionary dic("./dic.txt");
    //std::unique_ptr<DicServer> usvr = std::make_unique<DicServer>(ip,port,Translate);
    std::unique_ptr<DicServer> usvr = std::make_unique<DicServer>(port,[&dic]
        (const std::string& word,const std::string& whoip,uint16_t& whoport)->std::string
    {
        return dic.Translate(word,whoip,whoport);
    });
    
    usvr->Init();
    usvr->Start();
    return 0;
}
2.5 DicClient.cc
cpp 复制代码
#include<iostream>
#include<cstring>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
void DicClientManual()
{
    std::cout<<"DicClientManual"<<std::endl;
    std::cout<<"Please enter the port and IP"<<std::endl;
}

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        DicClientManual();
        return 1;
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    //创建socketfd
        int sockfd = socket(AF_INET,SOCK_DGRAM,0);
        //AF_INET:网络通信   SOCK_DGRAM:TCP通信  
        if(sockfd<0)
        {
           std::cout<<"client create socket error"<<std::endl;
            exit(1);
        }
    //创建服务端结构体,并清空内容
    struct sockaddr_in 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());
    while(true)
    {
        std::cout<<" Please Enter@ ";
        //获取要发的内容
        std::string line;
        std::getline(std::cin,line);
        //发送信息
        sendto(sockfd,line.c_str(),line.size(),0,(struct sockaddr*)&server,sizeof(server));
        //读取服务端数据
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        int r = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp,&len);
        if(r>0)
        {
            buffer[r] = 0;
            std::cout<<buffer<<std::endl;
        }

    }
    return 0;
}
2.6 演示代码实现结果

编译:

启动服务器,启动后会加载出配置文件内容:

启动服务器,就可以直接输入单词查询了。如果所查询的单词在配置文件里,则返回查询结果;如果查询的单词没有在配置文件里,服务器返回"Sorry,I can't find"

服务器端显示:

3. 实战篇:聊天室ChatServerDemo(V3)

上面我们都实现的是单线程,这次我们实现多线程,我们把之前写过的线程池拿过来使用,创建多线程。线程池的实现在这篇博客里有详细讲解:https://blog.csdn.net/whispers421/article/details/154848095?spm=1001.2014.3001.5502

这里就不过多介绍了,我们直接拿来使用

- 封装网络地址

3.1 InetAddr.hpp
cpp 复制代码
#pragma once

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include <cstring>
#define  Conv(addr)   ((struct sockaddr*)&addr)
class InetAddr
{
    private:
    void NetToHost()
    {
        _port = ntohs(_addr.sin_port);
        _ip = inet_ntoa(_addr.sin_addr);   
    }
    void HostToNet()
    {
        memset(&_addr,0,sizeof(_addr));
        _addr.sin_family =  AF_INET;
        _addr.sin_port = htons(_port);
        _addr.sin_addr.s_addr = inet_addr(_ip.c_str());
    }
    public:
    InetAddr(const struct sockaddr_in& addr)
    :_addr(addr)
    {
        NetToHost();
    }
    InetAddr(uint16_t port,std::string ip = "0.0.0.0")//"0.0.0.0" -> INADDR_ANY
    :_port(port)
    ,_ip(ip)
    {
        HostToNet();
    }
    
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    struct sockaddr* Addr()
    {
        return Conv(_addr);
    }
    socklen_t Len()
    {
        return sizeof(_addr);
    }
    std::string ToString()
    {
        return _ip+"-"+std::to_string(_port);
    }

    bool operator==(const InetAddr& addr)
    {
        return (_ip == addr._ip&& _port == addr._port);
        //return (_ip == addr._ip);

    }
    ~InetAddr(){}
    private:
    struct sockaddr_in _addr;//网络风格地址
    //主机风格地址
    std::string _ip;
    uint16_t _port;

};

- 路由器设置

3.2 Route.hpp
cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Mutex.hpp"
class Route
{
private:
    //判断该用户是否在聊天室内
    bool IsExists(const InetAddr &addr)
    {
        for (auto &user : _online_user)
        {
            if (user == addr)
                return true;
        }
        return false;
    }
    //添加新用户
    void AddUser(const InetAddr &addr)
    {
        MutexGuard MG(&_lock);
        if (!IsExists(addr))
        {
            _online_user.push_back(addr);
        }
        LOG(1)<<" AddUser success";
    }
    //删除用户
    void DeleteUser(const std::string &message, const InetAddr &addr)
    {
        MutexGuard MG(&_lock);
        if (message == "QUIT")
        {
            auto iter = _online_user.begin();
            for (iter; iter != _online_user.end(); iter++)
            {
                if (*iter == addr)
                {
                    _online_user.erase(iter);
                    LOG(1)<<" DeleteUser success";
                    break;
                }
            }
        }
    }
    //发送消息
    void SendMessageToAll(int sockfd, const std::string &message)
    {
        MutexGuard MG(&_lock);

        // 将消息message转发给所有在线用户
        for (auto &user : _online_user)
        {
            std::string info = user.ToString();
            info += ("# " + message);
            info.insert(info.begin(),'\n');
            sendto(sockfd, info.c_str(), info.size(), 0, user.Addr(), user.Len());
        }
    }

public:
    Route() {};
    void RouteMessageToAll(int sockfd, const std::string &message, InetAddr &addr)
    {
        //首次发消息等同于登录
        if(!IsExists(addr))
            AddUser(addr);

        SendMessageToAll(sockfd, message);
        DeleteUser(message, addr);
    }
    ~Route() {};

private:
    std::vector<InetAddr> _online_user; // 在线用户
    // 加锁
    Mutex _lock;
};

- 服务端设置

3.3 ChatServer.hpp
cpp 复制代码
#pragma once
#include<iostream>
#include<string>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<strings.h>
#include<cstdlib>
#include<functional>
#include"Log.hpp"
#include"InetAddr.hpp"
//回调函数
using callback_t = std::function<void(int sockfd,const std::string& message,InetAddr& addr)>;
int default_sockfd = -1;

class ChatServer
{
    public:
    ChatServer(uint16_t port,callback_t cb)
    :_sockfd(default_sockfd)
    ,_port(port)
    ,_isrunning(false)
    ,_cb(cb)
    {}

    void Init()
    {
        //创建socketfd
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        //AF_INET:网络通信   SOCK_DGRAM:TCP通信  
        if(_sockfd<0)
        {
            LOG(4)<<" create socket error";
            exit(1);
        }
        
        LOG(1)<<" create socket success,sockfd: "<<_sockfd;

        //这里就可以直接使用我们封装好的网络地址
        InetAddr local(_port);
        
        //和socket进行bind
        int b = bind(_sockfd,local.Addr(),local.Len());

        if(b<0)
        {
            LOG(4)<<" bind socket error";
            exit(2);
        }
        LOG(1)<<" bind socket success,sockfd:"<<_sockfd;
    }
    void Start()
    {
        _isrunning = true;

        while(_isrunning)
        {
            char buffer[1024];
            buffer[0] = 0;//清空缓存区
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            //读取数据
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
            if(n>0)
            {
                //读取成功
                buffer[n] =0;
                //获取client的IP和port
                InetAddr clientaddr(peer);
                //输出日志信息及client信息
                LOG(0)<<"get a client info# IP: "<<clientaddr.Ip()
                <<", Port:"<<clientaddr.Port()<<", message:"<<buffer;

                std::string message = buffer;
                //回调函数_cb任务:把sockfd中收到的内容buffer,传给clientaddr
                _cb(_sockfd,message,clientaddr);
            }
        }
        _isrunning = false;
    }
    void Stop()
    {
        _isrunning = false;
    }
    ~ChatServer(){}
    private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;
    bool _isrunning;

    callback_t _cb;
};
3.4 Server.cc
cpp 复制代码
#include"ChatServer.hpp"
#include"ThreadPool.hpp"
#include"Route.hpp"
void ServerManual()
{
    std::cout<<"ServerManual"<<std::endl;
    std::cout<<"Please enter the port"<<std::endl;
}

// //执行任务(for debug)
// void Chat(int sockfd,const std::string message,InetAddr addr)
// {
//     LOG(0)<<"sockfd:"<<sockfd;
//     LOG(0)<<"message:"<<message;
//     LOG(0)<<"addrinfo:"<<addr.ToString();
//     sendto(sockfd,message.c_str(),sizeof(message),0,addr.Addr(),addr.Len());
// }

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

int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        ServerManual();
        return 1;
    }
    EnableConsoleLogStrategy();
    uint16_t port = std::stoi(argv[1]);
    
    //1.消息转发功能
    std::unique_ptr<Route> r = std::make_unique<Route>();
    //2. 线程池创建
    auto tp = ThreadPool<task_t>::GetInstance();
    //3. 构建服务器对象
    std::unique_ptr<ChatServer> usvr = std::make_unique<ChatServer>(port,
    [&r,&tp](int sockfd,const std::string message,InetAddr addr)
    {
        task_t task = std::bind(&Route::RouteMessageToAll,r.get(),sockfd,message,addr);
        tp->Enqueue(task);
    }
    );

    usvr->Init();
    usvr->Start();
    return 0;
}

- 客户端设置

3.5 Client.cc
cpp 复制代码
#include <iostream>
#include <cstring>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <thread> //我们使用一下C++中的多线程
#include "InetAddr.hpp"
void ClientManual()
{
    std::cout << "ClientManual" << std::endl;
    std::cout << "Please enter the port and IP" << std::endl;
}

int sockfd = -1;
std::string serverip;
uint16_t serverport;

void InitClient(const std::string &serverip, uint16_t serverport)
{
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    // AF_INET:网络通信   SOCK_DGRAM:TCP通信
    if (sockfd < 0)
    {
        std::cout << "client create socket error" << std::endl;
    }
}

// 接收消息
void recver()
{
    while (true)
    {
        // 读取服务端数据
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        int r = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&tmp, &len);
        if (r > 0)
        {
            buffer[r] = 0;
            std::cerr << buffer << std::endl;
            //将消息打印到err文件里,再把err文件输出重定向到另一个界面就可以实现收发消息在不同的界面了

        }
    }
}
// 发送消息
void sender()
{
    // 创建服务端结构体
    InetAddr server(serverport,serverip);
    while (true)
    {
       std::cout << " Please Enter@ ";
        // 获取要发的内容
        std::string line;
        std::getline(std::cin, line);
        // 发送信息
        int sr = sendto(sockfd, line.c_str(), line.size(), 0, server.Addr(), server.Len());
        if (sr < 0)
        {
            perror("client sendto error");
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        ClientManual();
        return 1;
    }
    serverip = argv[1];
    serverport = std::stoi(argv[2]);
    // 创建socketfd
    InitClient(serverip, serverport);

    std::thread trecv(recver); // 创建接收消息线程
    std::thread tsend(sender); // 创建发送消息线程

    trecv.join();
    tsend.join();
    return 0;
}

- 代码实现结果

编译生成可执行文件

启动服务器:

同时启动两个客户端:

只有一个客户端登录发信息时:

另一个客户端也登陆并发送消息,其他用户也能收到信息:

全文完整代码已上传到我的Gitee:https://gitee.com/da-bai-classmate/linux-test/tree/master/UdpSocket

大家感兴趣的话可以去看看哦!

相关推荐
那我掉的头发算什么3 小时前
【javaEE】IP协议详解
网络·网络协议·tcp/ip·计算机网络
爱尔兰极光4 小时前
计算机网络-- TCP
网络·tcp/ip·计算机网络
NBD诺必达4 小时前
攻克日本TikTok直播网络难关:专线选型与延迟优化指南
网络·tiktok直播·日本tiktok·跨境直播·网络专线
Neolnfra4 小时前
华为中小型企业网络建设
网络·华为·毕业设计·ensp代做
皮蛋皮0074 小时前
如何在VMware Workstation Pro安装eNSP Pro?
网络
晚风(●•σ )4 小时前
【华为 ICT & HCIA & eNSP 习题汇总】——题目集26
网络·计算机网络·华为
甲虫机4 小时前
超详细教程--电脑同时使用内网和外网上网
运维·服务器·网络
爬山算法4 小时前
Netty(22)如何实现基于Netty的HTTP客户端和服务器?
服务器·网络协议·http
万粉变现经纪人4 小时前
Python系列Bug修复PyCharm控制台pip install报错:如何解决 pip install 网络报错 企业网关拦截 User-Agent 问题
网络·python·pycharm·beautifulsoup·bug·pandas·pip