16.udp_socket(三)

一.概念回顾

建议先学上篇博客,再向下学习,上篇博客的链接如下:

https://blog.csdn.net/weixin_60668256/article/details/154725707?fromshare=blogdetail&sharetype=blogdetail&sharerId=154725707&sharerefer=PC&sharesource=weixin_60668256&sharefrom=from_link

二.重定向补充知识

cpp 复制代码
#include <cstdio>
#include <iostream>


int main()
{
    //标志输出
    std::cout << "hello cout" << std::endl;
    printf("hello printf\n");
    
    //标志错误
    std::cerr << "hello cerr" << std::endl;
    fprintf(stderr,"hello fprintf\n");

    return 0;
}
cpp 复制代码
#include "UdpClient.hpp"
#include <iostream>
#include <cstring>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
#include <pthread.h>


int sockfd = -1;


void* Recver(void* args)
{
    while(true)
    {
        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::cerr << buffer << std::endl;
        }
    }
    return nullptr;
}

// ./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);
    }
    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 << "scoket error" << std::endl;
        Die(SOCKET_ERR);
    }
    //1.1填充server信息
    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());

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


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

    return 0;
}

服务器转发的是2号信号,所以会在上面汇总

这样就能很好的进行区分了

三.代码细节补充

1.对用户管理模块进行加锁

cpp 复制代码
class UserManager
{
public:
    UserManager()
    {

    }
    void AddUser(InetAddr& id)
    {
        LockGuard lockguard(_mutex);
        for(auto& user : _online_user)
        {
            if(*user == id)
            {
                return;
            }
        }
        LOG(LogLevel::INFO) << "新增该用户: " << id.Addr();
        _online_user.push_back(std::make_shared<User>(id));
    }
    void DelUser(const InetAddr& user)
    {

    }
    void Router(int sockfd,const std::string& message)
    {
        LockGuard lockguard(_mutex);
        for(auto& user:_online_user)
        {
            user->SendTo(sockfd,message);
        }
    }
    ~UserManager()
    {

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

2.消息发送的用户显示

cpp 复制代码
void Start()
    {
        _isrunning = true;
        while(true)
        {
            char inbuffer[1024];
            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)
            {
                //1.消息内容 && 2.谁给我发的
                InetAddr cli(peer);
                inbuffer[n] = 0;
                std::string message = cli.Addr() + "# " + inbuffer;
                //2.新增用户
                _adduser(cli);


                //3.新增转发模块
                task_t task = std::bind(UdpServer::_route,_sockfd,message);
                ThreadPool<task_t>::getInstance()->Equeue(task);

                std::string clientinfo = cli.Ip() + " : " + std::to_string(cli.Port()) + " # " + inbuffer;
                
                LOG(LogLevel::DEBUG) << clientinfo;

                std::string echo_string = "echo# ";
                echo_string += inbuffer;
                ::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),sizeof(peer));
            }
        }
        _isrunning = false;
    }

这样我们再启动的时候,绑定的端口就能识别到

3.加入用户提醒

cpp 复制代码
#include "UdpClient.hpp"
#include <iostream>
#include <cstring>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
#include <pthread.h>


int sockfd = -1;


void* Recver(void* args)
{
    while(true)
    {
        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::cerr << buffer << std::endl;
        }
    }
    return nullptr;
}

// ./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);
    }
    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 << "scoket error" << std::endl;
        Die(SOCKET_ERR);
    }
    //1.1填充server信息
    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());

    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.client done
    while(true)
    {
        std::cout << "please Enter# " << std::endl;
        std::string message;
        std::getline(std::cin,message);
        int n = ::sendto(sockfd,message.c_str(),message.size(),0,CONV(&server),sizeof(server));
        (void)n;
    }

    return 0;
}

4.退出消息提醒 及 功能实现

cpp 复制代码
#include "UdpClient.hpp"
#include <iostream>
#include <cstring>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
#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));
}


void* Recver(void* args)
{
    while(true)
    {
        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::cerr << buffer << std::endl;
        }
    }
    return nullptr;
}

// ./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 << "scoket error" << std::endl;
        Die(SOCKET_ERR);
    }
    //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.client done
    while(true)
    {
        std::cout << "please Enter# " << std::endl;
        std::string message;
        std::getline(std::cin,message);
        int n = ::sendto(sockfd,message.c_str(),message.size(),0,CONV(&server),sizeof(server));
        (void)n;
    }

    return 0;
}
cpp 复制代码
void RegisterService(adduser_t adduser,route_t route,remove_t remove)
    {
        _adduser = adduser;
        _route = route;
        _remove = remove;
    }
cpp 复制代码
#include "UdpServer.hpp"
#include "User.hpp"


// ./server_udp localport
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
        Die(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);
    ENABLE_CONSOLE_LOG();

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

    std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);

    
    svr_uptr->RegisterService(
        [&um](InetAddr& id){um->AddUser(id);},
        [&um](int sockfd,const std::string& message){um->Router(sockfd,message);},
        [&um](InetAddr& id){um->DelUser(id);}
    );
    svr_uptr->InitServer();
    svr_uptr->Start();
    return 0;
}

所以,后续如果我们执行对应的移除方法,那我们使用的就必定是对应的um->DelUser()了

删除用户实现

cpp 复制代码
void DelUser(InetAddr& id)
    {
        //v1
        auto pos = std::remove_if(_online_user.begin(),_online_user.end(),[&id](std::shared_ptr<UserInterface>& user){
            return *user == id;
        });
        _online_user.erase(pos,_online_user.end());
        //v2
        // for(auto user : _online_user)
        // {
        //     if(*user == id)
        //     {
        //         _online_user.erase(user);
        //         break;
        //     }
        // }
    }
cpp 复制代码
void PrintUser()
    {
        for(auto user : _online_user)
        {
            LOG(LogLevel::DEBUG) << "在线用户->" <<user->Id();
        }
    }

这里将我们所有的列表的在线用户全都打印一遍

5.总结

四.服务器防止拷贝

cpp 复制代码
#pragma once
#include <iostream>

class nocopy
{
public:
    nocopy(){}
    nocopy(const nocopy &) = delete;
    const nocopy& operator = (const nocopy &) = delete;
    ~nocopy(){}
};

当我们的服务器不想被拷贝,我们可以进行继承上面的nocopy的类即可

cpp 复制代码
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

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

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

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



using namespace LogModule;
using namespace ThreadPoolModule;


const static int gsockfd = -1;
const static std::string gdefaultip = "127.0.0.1";//表示本地主机
const static uint16_t gdefaultport = 8080;

using adduser_t = std::function<void(InetAddr& id)>;
using task_t = std::function<void()>;
using route_t = std::function<void (int sockfd,const std::string& message)>;
using remove_t = std::function<void(InetAddr& id)>;




class nocopy
{
public:
    nocopy(){}
    nocopy(const nocopy &) = delete;
    const nocopy& operator = (const nocopy &) = delete;
    ~nocopy(){}
};


class UdpServer : public nocopy
{
public:
    UdpServer(uint16_t port = gdefaultport)
    :_sockfd(gsockfd),
     _addr(port),
     _isrunning(false)
    {
    }
    void InitServer()
    {
        // 1.创建套接字
        _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.1 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";
    }
    void RegisterService(adduser_t adduser,route_t route,remove_t remove)
    {
        _adduser = adduser;
        _route = route;
        _remove = remove;
    }
    void Start()
    {
        _isrunning = true;
        while(true)
        {
            char inbuffer[1024];
            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)
            {
                //1.消息内容 && 2.谁给我发的
                InetAddr cli(peer);
                inbuffer[n] = 0;
                std::string message;

                if(strcmp(inbuffer,"Quit!") == 0)
                {
                    //移除观测者
                    _remove(cli);
                    message = cli.Addr() + "# " + "我走了,你们聊!";
                }
                else
                {
                    //2.新增用户
                    _adduser(cli);
                    message = cli.Addr() + "# " + inbuffer;
                }
                

                //3.新增转发模块
                task_t task = std::bind(UdpServer::_route,_sockfd,message);
                ThreadPool<task_t>::getInstance()->Equeue(task);

                std::string clientinfo = cli.Ip() + " : " + std::to_string(cli.Port()) + " # " + inbuffer;
                
                LOG(LogLevel::DEBUG) << clientinfo;

                std::string echo_string = "echo# ";
                echo_string += inbuffer;
                ::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),sizeof(peer));
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
        if(_sockfd > gsockfd)
        {
            ::close(gsockfd);
        }
    }

private:
    int _sockfd;
    InetAddr _addr;
    bool _isrunning; //服务器运行状态

    //新增用户
    adduser_t _adduser;
    //移除用户
    remove_t _remove;
    //数据转发
    route_t _route;
};

#endif
相关推荐
不会写程序的未来程序员2 小时前
Linux 虚拟机设置静态 IP 地址指南
linux·运维·tcp/ip
长不大的蜡笔小新2 小时前
掌握NumPy:ndarray核心特性与创建
开发语言·python·numpy
Yue丶越2 小时前
【C语言】深入理解指针(三)
c语言·开发语言
luoganttcc2 小时前
已知 空间 三个 A,B C 点 ,求 顺序 经过 A B C 三点 圆弧 轨迹 ,给出 python 代码 并且 画出图像
c语言·开发语言·python
今天的砖很烫2 小时前
ThreadLocal 结构设计的精妙之处
java·开发语言
麦麦鸡腿堡3 小时前
Java_HashMap底层机制与原码解读
java·开发语言·jvm
草莓熊Lotso3 小时前
C++ 抽象类与多态原理深度解析:从纯虚函数到虚表机制(附高频面试题)
java·运维·服务器·开发语言·c++·人工智能·笔记
Rock_yzh3 小时前
LeetCode算法刷题——49. 字母异位词分组
数据结构·c++·学习·算法·leetcode·职场和发展·哈希算法
小欣加油3 小时前
leetcode 2654 使数组所有元素变成1的最少操作次数
数据结构·c++·算法·leetcode·职场和发展