网络基础和UDP函数的简单使用

发布于:2024-12-07 ⋅ 阅读:(120) ⋅ 点赞:(0)

网络发展

最开始,计算机是独立的个体,因为需求需要计算机之间交换数据,由局域网(私网)–>广域网(公网),网络就逐渐发展起来了。

初识协议

  • 协议就是一种约定

  • 网络协议就是众多协议中的一种,又称TCP/IP协议,由ISO(国际标准组织)制定

  • 协议的本质也是软件,而软件横向模块纵向分层

  • 软件分层的好处:
    解耦的有效方式,可维护性好(对于其他人阅读容易,改变该部分的代码不会影响其他部分)
    封装继承和多态就是分层的。
    任何问题的解决都可以通过增加一层软件层来解决

  • 看待协议两个视角:

  1. 小白视角:同层协议,直接通信
  2. 工程师:同层协议,没有直接通信

OSI七层模型(标准)

这是标准
在这里插入图片描述

TCP/IP五层(四层)模型

OSI与TCP/IP的关系
在这里插入图片描述

TCP/IP协议族

为什么?

冯诺依曼体系就是网络,不过就是硬件与硬件之间的距离很短。
主机A与主机B距离变长,就会存在一些问题?

  1. 数据怎么给路由器的? 物理层和链路层
  2. 怎么定位主机C的? 网络层
  3. 数据丢失怎么办? 传输层
  4. 发数据不是目的,而是手段,用数据才是目的。也就是说主机发出的数据,怎么被处理? 应用层

所以,是为什么呢?,就是因为通信距离变长而引起的一些问题

是什么?

这些问题,种类不同,性质不同–>协议分层
TCP/IP是这些问题的解决方案

怎么办?

网络与OS

在这里插入图片描述
网络是OS中的一个模块

协议本质是结构体,先描述,再组织。

  1. TCP/IP网络,是os的一个模块,os使用c语言写的,网络也是用c语言写的
  2. win&&Linux网络代码一定是一样的
    结构体类型一样吗?
    一样
    –>这样就实现了,不同操作系统之间的网络通信
    总结:协议就是双方都认识的结构化类型

网络传输的基本流程

局域网(以太网为例)

在这里插入图片描述
原理:就是所有主机都能收到以太网上的信息

mac地址

在这里插入图片描述

ifconfig //命令查看mac地址

mac地址可以唯一标识局域网中的主机。48比特位,6字节。

  • 如何判断报文是否是给自己的呢?
    在数据链路层完成,通过mac地址

数据碰撞

  • 以太网一段时间内,只允许有一份报文。
  • 如果有多份就会发生碰撞,导致数据不一致,这就是数据碰撞。
  • 碰撞域:局域网本身就是一个碰撞域
  • 以太网是公共资源,也就是临界资源–>互斥–>碰撞检测和碰撞避免
    怎么检测碰撞?
    电压,多份报文的电压会比一份报文的电压大。
    主机越多,发生碰撞的概率越大。

流程

  • 报文 = 报头+有效载荷
  • 有效载荷:
    在这里插入图片描述
    在这里插入图片描述
    报头的共性:
  1. 报头和有效载荷分离的问题
  2. 报头内部,必须包含一个字段,叫做交给上层谁的字段—分用
    解包:就是分离报头和有效载荷
    分用:
  • 为什么要封装?
    网卡是硬件,由os管理,所以必须贯穿协议帧
  • 网卡有数据,怎么办?
    网卡有数据,就会给os发送中断信号,继而执行相应的处理方法。

跨网络传输

  • 就是局域网与局域网之间的数据传输
  • IP地址 4字节 4个部分 每个部分0~255
    在这里插入图片描述
  • IP协议有IPV4和IPV6,但现在常用的是IPV4
  • IP地址可以标识唯一的主机

MAC VS IP

在进行跨网络传输时,有两套地址

  1. 不变,源IP地址—>目的IP地址
  2. 变化,源MAC地址–>目的MAC地址
    而局域网通信需要MAC地址–>只有局域网中有效
  • 跨网络通信,路由器–>路由算法 是根据目的IP地址进行路由(路径选择)的
    IP几乎不变

大概过程

在这里插入图片描述

  • 以太网
  • 令牌环:就是谁持有令牌,谁就可以在该局域网中通信,类似于锁
  • 路由器:主机认为路由器也是主机,工作在网络层,有两套驱动程序

为什么要把数据交给路由器?

首先自己的主机是由网络层的,也是有路由功能的
目的IP与主机IP不同,同一个局域网中IP类似,也就是不与主机在同一个局域网中,把数据交给路由器。
将数据交给路由器本质就是进行一次局域网通信
在这里插入图片描述

在这里插入图片描述
源IP与目的IP不变,源MAC和目的MAC改变
网络层全网同一层,拿到的报文是一样的,统一的。
IP以下不同,IP以上相同
这样就屏蔽了网络的底层差异。
即使局域网的实现类型多样,也能实现不同的局域网之间的通信。
在这里插入图片描述

Socket变成预备

源IP与目的IP的理解

就是用于两端主机的通信
在这里插入图片描述

数据到达主机不是目的,而是手段,而把数据交给进程才是目的
进程(用户)+网络(os)/主机—>网络(os)/主机+用户(对端用户)

端口号

在这里插入图片描述

  • 可以唯一标识某一主机的某一进程
    为2字节

  • 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的
    端口号都是固定的.

  • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作
    系统从这个范围分配的

  • IP+端口号–>标识全网的唯一 一个进程–>叫做socket套接字

  • 那么主机是如何知道IP和端口号的呢?
    IP:可以通过域名解析知道
    端口号:是内置的

  • PID和端口号都可以唯一标识一个进程,那么为什么要引入端口号呢?
    为了解耦

传输层

在这里插入图片描述

  • TCP协议
    • 传输层协议
    • 有连接
    • 可靠传输
    • 面向字节流
  • UDP 协议
    • 无连接
    • 不可靠传输
    • 面向数据报

面相字节序

每个机器的可能是大端的也可能是小端的。

  • 网路规定,所有发送到网络上的数据,都必须是大端的。
  • 先发出的数据是高位的,后发出的数据是低位的.
    原因:可能是高位是报头

字节序转换的系统调用

在这里插入图片描述
h:host
n: net
l: long
s: short

Socket的系统调用

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

bind , accept,connect都有struct sockaddr这个结构体。
在这里插入图片描述
in:inet 用于网络
un:unix 用于本地
socket的种类多一些

  1. 网络socket
  2. 本地socket(unix域间socket)–>本地(类似于管道)
    OS提供系统调用
    socket接口统一–>区分是本地还是网络–>有结构体最开始的16个公共字段来确定–>继承和多态

实现一个EchoServer的网络通信案例

用udp实现

Server_Udp.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <cstring>  //strerror
#include <cerrno> //errno
#include <unistd.h>
#include "Log.hpp"
#include "Comm.hpp"

static const int default_sockfd = -1;
std::string default_ip = "127.0.0.1";
uint16_t default_port = 8080;
using namespace LogModule;
class Server
{

public:
    Server(std::string ip = default_ip, uint16_t port = default_port)
        : _sockfd(default_sockfd),
          _ip(ip),
          _port(port),
          _is_running(false)
    {
        // 1.创建socket套接字
        // 返回值是文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            std::cerr << strerror(errno);
            Die(SOCKETERR);
        }
        LOG(LogLevel::INFO) << "socket success sockfd:" << _sockfd;
        // 2.填充网络信息并绑定
        // struct sockaddr_in local;
        // 清零
        bzero(&_local, sizeof(struct sockaddr_in));
        _local.sin_family = AF_INET;
        _local.sin_port = htons(_port);
        // local.sin_addr这是一个结构体,结构体只能整体初始化,不能整体赋值
        //_local.sin_addr.s_addr = inet_addr(_ip.c_str());
        //任意绑定
        _local.sin_addr.s_addr = INADDR_ANY;
        

        // 填充网络信息成功,但没有填充进内核

        // 设置进内核
        int n = bind(_sockfd, CONV(&_local), sizeof(struct sockaddr_in));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            std::cerr << strerror(errno);
            Die(BINDERR);
        }
        LOG(LogLevel::INFO) << "bind success ip:" << _ip << " port:" << std::to_string(_port);
    }
    ~Server()
    {
        if(_sockfd>default_sockfd)
        {
            close(_sockfd);
        }
    }
    void Start()
    {
        _is_running = true;
        while (true)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t peer_len = sizeof(struct sockaddr_in);
            // flags 为 0(默认)阻塞
            int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &peer_len);
            if (n >= 0)
            {
                buffer[n] = 0;
                char peer_ip[64];
                inet_ntop(AF_INET,&peer.sin_addr.s_addr,peer_ip,sizeof(peer_ip));
                uint16_t peer_port = ntohs(peer.sin_port);
                LOG(LogLevel::INFO) << "-" <<peer_ip<<":"<<peer_port  <<"#"<< buffer;
                std::string info = "echo client#";
                info += buffer;
                sendto(_sockfd, info.c_str(), info.size(), 0, CONV(&peer), peer_len);
            }
            else
            {
                std::cout<<"server error:";
                std::cerr<<strerror(errno);
                Die(SEV_RECV_ERR);
            }
        }
    }

private:
    struct sockaddr_in _local;
    int _sockfd;
    std::string _ip;
    uint16_t _port;
    bool _is_running;
};

socket 创建套接字

在这里插入图片描述

在这里插入图片描述

  • socket用来创建套接字

  • domain:域
    在这里插入图片描述

  • type:
    在这里插入图片描述

  • protocol:0,这里不使用协议

  • 返回值:int是一个文件描述符
    在这里插入图片描述

填充网络信息,bind 绑定

在这里插入图片描述

  • struct sockaddr_in 的结构
/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
  };
#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

##是连接的作用,结果就是 sin_family
sin_addr 是结构体,结构体只能整体初始化,不能整体赋值,

struct in_addr
  {
    in_addr_t s_addr;
  };

所以,赋值(填充)的是sin_addr中的s_addr.

inet_addr

在这里插入图片描述
将字符串风格的ip转为网络格式的ip
1.std::string—>4byte
2.network order(网络格式)

  • 命令
netstat -uapn

u:udp
a:all
p:进程
n:将LocalAdress转为数字形式

  • 固定绑定
    在这里插入图片描述
    在这里插入图片描述

  • 任意绑定
    在这里插入图片描述
    在这里插入图片描述

接收和发送消息

recvfrom和sendto用的是一个文件描述符,是全双工的,既可以发送,也可以接收。
在这里插入图片描述

recvfrom接收消息

在这里插入图片描述

  1. sockfd:文件描述符
  2. flags:0 (默认)阻塞的等待接收消息
  3. src_addr 和 addrlen都是输出型参数,表示发送端的网络信息
sendto

在这里插入图片描述

  1. flags: 0
  2. dest_addr和addr_len,表示要将信息发送到的主机的进程的网络信息。
CONV() 和Die()

是自己宏定义的一个强制类型转换和进程退出

#define CONV(ptr) ((struct sockaddr *)(ptr))

#define Die(code)   \
    do              \
    {               \
        exit(code); \
    }               \
    while (0)       \
        ;
inet_ntop

在这里插入图片描述
将网络格式的IP转为字符数组的形式

Server_Udp.cpp

#include "Server_Udp.hpp"
int main()
{
    Server s;
    s.Start();
    return 0;
}

Comm.hpp

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

#define Die(code)   \
    do              \
    {               \
        exit(code); \
    }               \
    while (0)       \
        ;

#define CONV(ptr) ((struct sockaddr *)(ptr))
enum
{
    SOCKETERR = 1,
    BINDERR,
    USAGEERR,
    SEV_RECV_ERR
};

Client_Udp.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <cerrno>
#include "Comm.hpp"
#include "Log.hpp"
using namespace LogModule;
int main(int argc, char *argv[])
{

    if (argc != 3)
    {
        std::cout << "Usage Error:" << "id port" << std::endl;
        Die(USAGEERR);
    }
    std::string ip = argv[1];
    uint16_t port = std::atoi(argv[2]);
    // 1.创建socket套接字
    int sockfd;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket error";
        std::cerr << strerror(errno);
        Die(SOCKETERR);
    }
    LOG(LogLevel::INFO) << "socket success sockfd:" << sockfd;
    // 客户端不需要绑定?
    // client必须要有自己的ip和port
    //不需要显示调用bind,而是客户端首次sendto,由os自动调用bind
    //client自动随机bind  port     server显示bind
    //client一个端口号只能被一个进程bind,如果显示bind有可能port冲突
    //server服务器的端口号必须稳定!必须众所周知且不能轻易改变
    struct sockaddr_in local;
    socklen_t len = sizeof(struct sockaddr_in);
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip.c_str());
    while(true)
    {
        std::cout<<"Please Enter# ";
        struct sockaddr_in peer;
        socklen_t peer_len;
        std::string s;
        std::cin>>s;
        sendto(sockfd,s.c_str(),s.size(),0,CONV(&local),len);
        std::cout<<"sento success"<<std::endl;
        char buffer[1024];
        int n = recvfrom(sockfd,buffer,sizeof(buffer),0,CONV(&peer),&peer_len);
        if(n>0)
        {
            buffer[n] = 0;
            std::cout<<buffer<<std::endl;
        }
    }


    return 0;
}
  • 客户端不需要绑定?
    client必须要有自己的ip和port
    不需要显示调用bind,而是客户端首次sendto,由os自动调用bind
    client自动随机bind port server显示bind
    client一个端口号只能被一个进程bind,如果显示bind有可能port冲突
    server服务器的端口号必须稳定!必须众所周知且不能轻易改变
  1. 云服务器可能有多个IP,禁止用户bind公网IP
  2. 虚拟机可以bind你的任何IP

预期结果

在这里插入图片描述

Server_Udp.hpp的封装

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h> 
#include <memory.h>
#include <cstring>
#include <cerrno>
#include "Comm.hpp"
#include "Mutex.hpp"
#include "Log.hpp"
using namespace LogModule;
class InetAddr
{
private:
    void Ntohs()
    {
        _port = ntohs(_local.sin_port);
    }
    void Ntoa()
    {
       // _ip = inet_ntoa(&(_local.sin_addr));
       char buffer[64];
       inet_ntop(AF_INET,&(_local.sin_addr.s_addr),buffer,sizeof(buffer));
       _ip = buffer;
    }
public:
    InetAddr()
    {

    }
    InetAddr(uint16_t port):_port(port),_ip("0.0.0.0")
    {
        _local.sin_family = AF_INET;
        _local.sin_port = htons(_port);
        _local.sin_addr.s_addr = INADDR_ANY;//任意绑定
    }
    InetAddr(const struct sockaddr_in& addr):_local(addr)
    {
        Ntohs();
        Ntoa();
    }

    ~InetAddr()
    {

    }
    const struct sockaddr* Addr()
    {
        return CONV(&_local);
    }
    int In_Size()
    {
        return sizeof(struct sockaddr_in);
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
private:
    struct sockaddr_in _local;
    std::string _ip;
    uint16_t _port;
};

将网络信息和string风格的ip和host的port封装成一个InetAddr类,这样用起来就很方便了。

实现一个翻译的服务DictServer

基于上面EchoServer的代码,只需要为server添加一个业务就可以,这里添加业务的方式是通过回调

这是简单的字典
在这里插入图片描述

Dict.hpp

字典类

#pragma once
#include <iostream>
#include <fstream>
#include <unordered_map>
#include <cstring>
#include "Log.hpp"
std::string gpath = "./";
std::string gfilename = "Dictionary.txt";
std::string gseprator = ": ";
using namespace LogModule;
class Dictionary
{
    //分割单词和翻译
    bool Split(std::string &s)
    {
        int pos = s.find(_seprator);
        if(pos==std::string::npos)  
            return false;
        std::string key = s.substr(0,pos);
        std::string val = s.substr(pos+_seprator.size());
        if(key.empty()||val.empty())
        {
            return false;
        }
        _dictionary[key] = val;
        return true;
    }
    bool LoadDictionary()
    {
        std::string file = _path + _filename;
        std::ifstream in(file);
        if (!in.is_open())
        {
            LOG(LogLevel::ERROR) << "open file " << file << "error";
            exit(1);
        }
        char inbuffer[64] = {0};
        while(in.getline(inbuffer,sizeof(inbuffer)))
        {
            std::string line = inbuffer;
            //分割单词和翻译
            Split(line);
            memset(inbuffer, 0, sizeof(inbuffer));
        }
        in.close();
        return true;
    }

public:
    Dictionary(std::string &path = gpath, std::string &filename = gfilename,std::string& seprator = gseprator)
        : _path(path),
          _filename(filename),
          _seprator(seprator)
    {
        //加载字典
        LoadDictionary();
    }
    //翻译
    std::string Translate(std::string& word)
    {
        std::unordered_map<std::string, std::string>::iterator it = _dictionary.find(word);
        if(it==_dictionary.end())
        {
            return "None";
        }
        return it->second;
    }
    ~Dictionary()
    {
        
    }

private:
    std::string _path;
    std::string _filename;
    std::string _seprator;
    std::unordered_map<std::string, std::string> _dictionary;
};

Server_Udp.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <cstring>  //strerror
#include <cerrno> //errno
#include <unistd.h>
#include <functional>
#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"

using func_t = std::function<std::string(std::string)>;
static const int default_sockfd = -1;
std::string default_ip = "127.0.0.1";
uint16_t default_port = 8080;
using namespace LogModule;
class Server
{

public:
    Server(func_t func,uint16_t port = default_port)
        : _sockfd(default_sockfd),
          _inet_addr(port),
          _is_running(false),
          _func(func)
    {
        // 1.创建socket套接字
        // 返回值是文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            std::cerr << strerror(errno);
            Die(SOCKETERR);
        }
        LOG(LogLevel::INFO) << "socket success sockfd:" << _sockfd;
        bind(_sockfd,_inet_addr.Addr(),_inet_addr.In_Size());
        LOG(LogLevel::INFO) << "bind success ip:" << _inet_addr.Ip() << " port:" << _inet_addr.Port();
        // 2.填充网络信息并绑定
        // struct sockaddr_in local;
        // 清零
        // bzero(&_local, sizeof(struct sockaddr_in));
        // _local.sin_family = AF_INET;
        // _local.sin_port = htons(_port);
        // // local.sin_addr这是一个结构体,结构体只能整体初始化,不能整体赋值
        // //_local.sin_addr.s_addr = inet_addr(_ip.c_str());
        // //任意绑定
        // _local.sin_addr.s_addr = INADDR_ANY;
        

        // 填充网络信息成功,但没有填充进内核


        // 设置进内核
        //int n = bind(_sockfd, CONV(&_local), sizeof(struct sockaddr_in));

        // if (n < 0)
        // {
        //     LOG(LogLevel::FATAL) << "bind error";
        //     std::cerr << strerror(errno);
        //     Die(BINDERR);
        // }
        // LOG(LogLevel::INFO) << "bind success ip:" << _ip << " port:" << std::to_string(_port);
    }
    ~Server()
    {
        if(_sockfd>default_sockfd)
        {
            close(_sockfd);
        }
    }
    void Start()
    {
        _is_running = true;
        while (true)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t peer_len = sizeof(struct sockaddr_in);
            // flags 为 0(默认)阻塞
            int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &peer_len);
            if (n >= 0)
            {
                InetAddr cli(peer);
                buffer[n] = 0;
                std::string word = buffer;
                //char peer_ip[64];
                //inet_ntop(AF_INET,&peer.sin_addr.s_addr,peer_ip,sizeof(peer_ip));
                //uint16_t peer_port = ntohs(peer.sin_port);
                std::string trans = _func(word);//翻译
                LOG(LogLevel::INFO) << "-" <<cli.Ip()<<":"<<cli.Port()  <<"#"<< buffer;
                // std::string info = "echo client#";
                // info += buffer;
                sendto(_sockfd, trans.c_str(), trans.size(), 0, CONV(&peer), peer_len);
            }
            else
            {
                std::cout<<"server error:";
                std::cerr<<strerror(errno);
                Die(SEV_RECV_ERR);
            }
        }
    }

private:
    InetAddr _inet_addr;
    //struct sockaddr_in _local;
    int _sockfd;
    //std::string _ip;
    //uint16_t _port;
    bool _is_running;
    func_t _func;//翻译
};

在这里插入图片描述

  • 需要一个传一个函数类,这个类的调用需要完成相应的翻译任务
    在这里插入图片描述
  • 收到消息后翻译
    在这里插入图片描述

Server_Udp.cpp

#include "Server_Udp.hpp"
#include "Dict.hpp"
int main()
{
    Dictionary dict;
    Server s([&dict](std::string word){
        return dict.Translate(word);
    });
    s.Start();
    return 0;
}

预期效果

在这里插入图片描述

简单实现一个聊天室服务ChatServer

在这里插入图片描述

User.hp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <list>
#include <algorithm>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
using namespace LogModule;
class UserInterface
{
public:
    virtual ~UserInterface() = default;
    virtual void SendTo(int sockfd,std::string& message) = 0;
};
//一个用户
class User : public UserInterface
{
public:
    User(InetAddr &id)
        :_id(id)
    {
    }
    ~User()
    {
    }
    //将信息发送给这个用户
    void SendTo(int sockfd,std::string& message) override
    {
        sendto(sockfd, message.c_str(),message.size(),0,_id.Addr(),_id.In_Size());
    }
    bool operator==(InetAddr& id)
    {
        if(Sock()==id.Sock())
            return true;
        return false;
    }
    std::string Sock()
    {
        return _id.Sock();
    }
private:
    InetAddr _id;
};

//管理所有用户
class UserManager
{
public:
    UserManager()
    {

    }
    //添加用户
    void AddUser(InetAddr& id)
    {
    	//list是临界资源需要加锁
        LockGuard lockguard(_mutex);
        for(auto& e:_list)
        {
            if(e==id)
            {        
                LOG(LogLevel::INFO)<<"用户已存在 "<<id.Sock();
                return;
            }
        }
        User user(id);
        LOG(LogLevel::INFO)<<"添加新用户 "<<id.Sock();
        _list.push_front(user);
    }
    //路由 就是把消息转发给所有用户
    //路由的本质就是遍历list并将信息发给每个用户
    void Router(int sockfd,std::string& message)
    {
        for(auto& u:_list)
        {
            LOG(LogLevel::INFO)<<"message send to "<<u.Sock();
            u.SendTo(sockfd,message);
        }
    }
    //删除用户
    void DelUser(InetAddr& id)
    {
        auto pos = remove_if(_list.begin(),_list.end(),[&id](User& user){
            return user==id;
        });
        LOG(LogLevel::INFO)<<"删除用户"<<id.Sock();
        _list.erase(pos,_list.end());
    }
    ~UserManager()
    {

    }
private:
    std::list<User> _list;//用链表管理所有用户
    Mutex _mutex;
};

具体思想:就是先描述再组织
描述一个用户,然后再用一个结构去管理用户

Server_Udp.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <cstring> //strerror
#include <cerrno>  //errno
#include <unistd.h>
#include <functional>
#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
static const int default_sockfd = -1;
std::string default_ip = "127.0.0.1";
uint16_t default_port = 8080;

using add_t = std::function<void(InetAddr &)>;
using router_t = std::function<void(int sockfd, std::string &message)>;
using del_t = std::function<void(InetAddr &)>;
using namespace LogModule;
using namespace ThreadPoolModule;
using task_t = std::function<void()>;
class Server
{

public:
    Server(add_t adduser, router_t router, del_t deluser, uint16_t port = default_port)
        : _sockfd(default_sockfd),
          _inet_addr(port),
          _is_running(false),
          _adduser(adduser),
          _router(router),
          _deluser(deluser)
    {
        // 1.创建socket套接字
        // 返回值是文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            std::cerr << strerror(errno);
            Die(SOCKETERR);
        }
        LOG(LogLevel::INFO) << "socket success sockfd:" << _sockfd;
        bind(_sockfd, _inet_addr.Addr(), _inet_addr.In_Size());
        LOG(LogLevel::INFO) << "bind success ip:" << _inet_addr.Ip() << " port:" << _inet_addr.Port();
        // 2.填充网络信息并绑定
        // struct sockaddr_in local;
        // 清零
        // bzero(&_local, sizeof(struct sockaddr_in));
        // _local.sin_family = AF_INET;
        // _local.sin_port = htons(_port);
        // // local.sin_addr这是一个结构体,结构体只能整体初始化,不能整体赋值
        // //_local.sin_addr.s_addr = inet_addr(_ip.c_str());
        // //任意绑定
        // _local.sin_addr.s_addr = INADDR_ANY;

        // 填充网络信息成功,但没有填充进内核

        // 设置进内核
        // int n = bind(_sockfd, CONV(&_local), sizeof(struct sockaddr_in));

        // if (n < 0)
        // {
        //     LOG(LogLevel::FATAL) << "bind error";
        //     std::cerr << strerror(errno);
        //     Die(BINDERR);
        // }
        // LOG(LogLevel::INFO) << "bind success ip:" << _ip << " port:" << std::to_string(_port);
    }
    ~Server()
    {
        if (_sockfd > default_sockfd)
        {
            close(_sockfd);
        }
    }
    void Start()
    {
        _is_running = true;
        while (true)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t peer_len = sizeof(struct sockaddr_in);
            // flags 为 0(默认)阻塞
            int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &peer_len);

            if (n >= 0)
            {
                InetAddr cli(peer);
                buffer[n] = 0;
                LOG(LogLevel::INFO)<<cli.Sock()<<"# "<<buffer;
                std::string message = buffer;
                if (strcmp(buffer, "quit") == 0)
                {
                    _deluser(cli); // 删除用户
                    message = cli.Sock() + "....走了,你们聊";
                    //_router(_sockfd, message);
                }
                else
                {
                    _adduser(cli);            // 添加用户
                    //_router(_sockfd, message); // 路由给所有在线用户
                }
                task_t task = std::bind(_router,_sockfd,message);
                ThreadPool<task_t>::GetInstance()->Equeue(task);
                // char peer_ip[64];
                // inet_ntop(AF_INET,&peer.sin_addr.s_addr,peer_ip,sizeof(peer_ip));
                // uint16_t peer_port = ntohs(peer.sin_port);
                //  LOG(LogLevel::INFO) << "-" <<cli.Ip()<<":"<<cli.Port()  <<"#"<< buffer;
                //  std::string info = "echo client#";
                //  info += buffer;
                //  sendto(_sockfd, info.c_str(), info.size(), 0, CONV(&peer), peer_len);
            }
            else
            {
                std::cout << "server error:";
                std::cerr << strerror(errno);
                Die(SEV_RECV_ERR);
            }
        }
    }

private:
    InetAddr _inet_addr;
    // struct sockaddr_in _local;
    int _sockfd;
    // std::string _ip;
    // uint16_t _port;
    bool _is_running;
    add_t _adduser;
    router_t _router;
    del_t _deluser;
};
  1. 需要给服务器添加路由,增删用户的功能
    在这里插入图片描述
  2. 将路由当做任务,交给线程池来处理
    在这里插入图片描述
    在这里插入图片描述

Server_Udp.cpp

#include "Server_Udp.hpp"
#include "User.hpp"
using namespace ThreadPoolModule;
int main()
{
    ThreadPool<task_t>::GetInstance()->Start();
    UserManager um;
    
    //添加用户
    Server s([&um](InetAddr& id){um.AddUser(id);},
             [&um](int sockfd,std::string& message){um.Router(sockfd,message);},
             [&um](InetAddr& id){um.DelUser(id);});
    s.Start();
    return 0;
}

Client_Udp.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <cerrno>
#include <pthread.h>
#include <unistd.h>
#include "Comm.hpp"
#include "Log.hpp"
using namespace LogModule;
int sockfd;
void *Recver(void *args)
{
    while (true)
    {
        struct sockaddr_in peer;
        socklen_t peer_len;
        char buffer[1024];
        int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, CONV(&peer), &peer_len);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cerr << buffer << std::endl;
        }
    }
}
int main(int argc, char *argv[])
{

    if (argc != 3)
    {
        std::cout << "Usage Error:" << "id port" << std::endl;
        Die(USAGEERR);
    }
    std::string ip = argv[1];
    uint16_t port = std::atoi(argv[2]);
    pthread_t tid;
    pthread_create(&tid, nullptr, Recver, nullptr);
    // 1.创建socket套接字

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket error";
        std::cerr << strerror(errno);
        Die(SOCKETERR);
    }
    // LOG(LogLevel::INFO) << "socket success sockfd:" << sockfd;
    //  客户端不需要绑定?
    //  必须要有自己的ip和port
    // 不需要显示调用bind,而是客户端首次sendto,由os自动调用bind

    struct sockaddr_in local;
    socklen_t len = sizeof(struct sockaddr_in);
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip.c_str());
    // 刚登上就上线,不需要发完消息后才能上线
    std::string message = "...来了";
    sendto(sockfd, message.c_str(), message.size(), 0,CONV(&local), len);
    while (true)
    {
        std::cout << "Please Enter# ";
        std::string s;
        std::cin >> s;
        int n = sendto(sockfd, s.c_str(), s.size(), 0, CONV(&local), len);
        if (n < 0)
        {
            std::cout << "send error" << std::endl;
            std::cerr << strerror(errno) << std::endl;
        }

        if (s == "quit")
        {
            exit(0);
        }
        // std::cout<<"sento success"<<std::endl;
        //  char buffer[1024];
        //  int n = recvfrom(sockfd,buffer,sizeof(buffer),0,CONV(&peer),&peer_len);
        //  if(n>0)
        //  {
        //      buffer[n] = 0;
        //      std::cout<<buffer<<std::endl;
        //  }
    }

    return 0;
}

相较于以前收消息交给另一个线程来完成
在这里插入图片描述

预期结果

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到