UDP_socket

进程与端口号

一个进程可对应多个端口号

一个端口号只能有一个进程

为什么client要OS自动bind端口号?

OS确定client端口号可避免不同client重复端口号,且client端口号不追求稳定性。

为什么sever端口号要手动设置?

sever端口号必须稳定,众所周知且不能轻易改变

netstat -naup

查看sever

云服务器禁止用户bind公网ip

虚拟机可以bind任何ip

sever不需要bind具体ip,这样只会限制sever接收信息范围到一个ip上。应用0(INADDR_ANY)表示任意ip

tips

子进程可以继承文件描述符

oldfd > newfd 命令行中重定向

注意,命令行中执行此操作,会打开newfd(清空),用>>避免清空

将两个fd重定向同一个新文件时,为了防止>打开时清空文件,应写为

1>newfile 2>&1

fifo文件必须读写段同时都打开才有效

板书笔记

code

link:code/lesson34/1. EchoServer · whb-helloworld/112 - 码云 - 开源中国

本文只提供CharServer代码, EchoServe与DictServer代码可在上链接中查看

ChatServer

Common.hpp
#pragma once 
#include <iostream>
#define Die(code) do{exit(code);}while(0)
#define CONV(v) (struct sockaddr *)(v)

enum
{
    USAGE_ERR = 1, 
    SOCKET_ERR,
    BIND_ERR
};
InetAddr.hpp
#pragma once 

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

class InetAddr
{
private:
    void  PortNet2Host()
    {
        _port = ::ntohs(_net_addr.sin_port);
    }

    void IpNet2Host()
    {
        char ipbuffer[64];
        const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        (void)ip;
    }

public:
    InetAddr()
    {

    }
    InetAddr(const struct sockaddr_in &addr):_net_addr(addr)
    {
        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;
    }
    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;
};
Log.hpp
#pragma once

#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"

// namespace LogModule
// {
    using namespace LockModule;

    // 获取一下当前系统的时间
    std::string CurrentTime()
    {
        time_t time_stamp = ::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5

        char buffer[1024];
        // bug
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr.tm_year + 1900,
                 curr.tm_mon + 1,
                 curr.tm_mday,
                 curr.tm_hour,
                 curr.tm_min,
                 curr.tm_sec);

        return buffer;
    }

    // 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)
    //  1. 日志文件的默认路径和文件名
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";

    // 2. 日志等级
    enum class LogLevel
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Level2String(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "None";
        }
    }

    // 3. 刷新策略.
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 3.1 控制台策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }
        ~ConsoleLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::cout << message << std::endl;
        }

    private:
        Mutex _lock;
    };

    // 3.2 文件级(磁盘)策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
            : _logpath(logpath),
              _logname(logname)
        {
            // 确认_logpath是存在的.
            LockGuard lockguard(_lock);

            if (std::filesystem::exists(_logpath))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }
        ~FileLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::string log = _logpath + _logname; // ./log/log.txt
            std::ofstream out(log, std::ios::app); // 日志写入,一定是追加
            if (!out.is_open())
            {
                return;
            }
            out << message << "\n";
            out.close();
        }

    private:
        std::string _logpath;
        std::string _logname;

        // 锁
        Mutex _lock;
    };

    // 日志类: 构建日志字符串, 根据策略,进行刷新
    class Logger
    {
    public:
        Logger()
        {
            // 默认采用ConsoleLogStrategy策略
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableConsoleLog()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableFileLog()
        {
            _strategy = std::make_shared<FileLogStrategy>();
        }
        ~Logger() {}
        // 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
                : _currtime(CurrentTime()),
                  _level(level),
                  _pid(::getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)
            {
                std::stringstream ssbuffer;
                ssbuffer << "[" << _currtime << "] "
                         << "[" << Level2String(_level) << "] "
                         << "[" << _pid << "] "
                         << "[" << _filename << "] "
                         << "[" << _line << "] - ";
                _loginfo = ssbuffer.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                if (_logger._strategy)
                {
                    _logger._strategy->SyncLog(_loginfo);
                }
            }

        private:
            std::string _currtime; // 当前日志的时间
            LogLevel _level;       // 日志等级
            pid_t _pid;            // 进程pid
            std::string _filename; // 源文件名称
            int _line;             // 日志所在的行号
            Logger &_logger;       // 负责根据不同的策略进行刷新
            std::string _loginfo;  // 一条完整的日志记录
        };

        // 就是要拷贝,故意的拷贝
        LogMessage operator()(LogLevel level, const std::string &filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

    private:
        std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案
    };

    Logger logger;

#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
// }
UdpClient.hpp
#pragma once
UdpClientMain.cc
#include "UdpClient.hpp"
#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>


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]);
    // 创建socket
    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        std::cerr<<"socker error"<<std::endl;
        Die(SOCKET_ERR);
    }
    // 填充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());

    while(true)
    {
        std::cout<<"Please Enter_# ";
        std::string message;
        std::getline(std::cin, message);
        printf("get line success:\n");
        int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));
        printf("sento success\n");
        // void(n);
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        char buffer[1024];
        printf("client waiting recvfrom\n");
        n = ::recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, CONV(&temp), &len);// recvfrom会更新倒数两个参数, 所以传递指针
        printf("client recvfrom success\n");
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout<<"server:" <<buffer << std::endl;
        }
        else 
        {
            printf("没有收到server echo\n");
        }
    }
    return 0;
}
UdpServer.hpp
#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 "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"

// using namespace LogModule;

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 InitSever()
    {
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            LOG(LogLevel::FATAL)<<"socker: "<<strerror(errno);
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO)<<"socker success, sockfd is:"<<_sockfd;
        // 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 Start()
    {
        _isrunning = true;
        while(true)
        {
            char inbuffer[1024];
            struct sockaddr_in peer;// peer中存储的永远是client信息,sever信息已经bind到内核(_addr中也有)
            socklen_t len = sizeof(peer);
            printf("server waiting recvfrom\n");
            ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) -1, 0, CONV(&peer), &len);
            printf("server waiting recvfrom success\n");
            printf("recvfrom over\n");
            if(n > 0)
            {
                InetAddr cli(peer);
                inbuffer[n] = 0;
                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(_sockfd);
    }

private:
    int _sockfd;
    InetAddr _addr;
    bool _isrunning;
};

#endif
UdpServerMain.cc
#include "UdpServer.hpp"

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        std::cerr<<"Usage:"<<argv[0]<<"lockport"<<std::endl;
        Die(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);
    ENABLE_CONSOLE_LOG();
    std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);
    svr_uptr->InitServer();
    svr_uptr->Start();
    return 0;
}
相关推荐
学Linux的语莫2 分钟前
k8s中,一.pod污点,二.pod容器污点容忍策略,三.pod优先级(PriorityClass类)
linux·docker·容器·kubernetes
m0_748249542 分钟前
Linux环境下Tomcat的安装与配置详细指南
linux·运维·tomcat
随缘与奇迹10 分钟前
linux中,软硬链接的作用和使用
linux·运维·服务器
m0_7482556513 分钟前
Linux环境下的事件驱动力量:探索Libevent的高性能IO架构
linux·架构·php
努力成为DBA的小王32 分钟前
Oracle(windows安装遇到的ORA-12545、ORA-12154、ORA-12541、ORA-12514等问题)
linux·运维·服务器·数据库·oracle
DA02211 小时前
CentOS 7.9-2207更换实时内核
linux·运维·centos
Channing Lewis2 小时前
Linux 中为什么进程是休眠的,但是还是处理了数据
linux·运维·服务器
深度Linux2 小时前
Linux性能优化实战,网络丢包问题分析
linux·性能优化·linux内核
赶紧写完去睡觉2 小时前
尚硅谷课程【笔记】——大数据之Shell【二】
大数据·linux·shell编程
激进的猴哥2 小时前
day33-数据同步rsync
linux·运维·服务器