Linux_Socket_UDP

✨✨ 欢迎大家来到小伞的大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:LInux**
小伞的主页:**xiaosan_blog****

gitee:许星让 (xu-xingrang) - Gitee.com

制作不易!点个赞吧!!谢谢喵!!

0.本节目录gitee链接

完整代码:

lesson39 · 许星让/linux gcc - 码云 - 开源中国

1. Socket预备知识

1.1 理解源IP地址和目的IP地址

  • IP在网络中,用来标识主机的唯一性
  • 注意:后面我们会讲IP 的分类,后面会详细阐述IP 的特点

数据传输的目的是给client使用的,所以当数据传输到目标主机时,会把数据交给对应进程,进行Server

1.2 认识端口号

端口号(port)是传输层协议的内容.

  • 端口号是一个2字节16位的整数
  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
  • IP地址+端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用

**浏览器,qq,迅雷这些进程,操作系统怎么知道把数据报文传给对应的进程呢,通过端口号,每个进程拥有不同的端口号,**如房间号,你打电话让前台送水,你要告诉他几楼(目标主机ip),房间号(端口号port),前台到达对应楼层,通过房间号(port)找到你的房间把水(数据报文)给你

1.2.1 端口号范围划分
  • 0-1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他们的端口号都是固定的
  • 1024-65535:操作系统动态分配的端口号.客户端程序的端口号,就是由操作系统从这个范围分配的.

1.3 理解socket

如图:客户端与服务端通过类似于文件描述符的进行读写

  • IP地址用来标识互联网中唯一的一台主机,port用来标识该主机上唯一的一个网络进程
  • IP+Port 就能表示互联网中唯一的一个进程
  • 所以,通信的时候,本质是两个互联网进程代表人来进行通信,{srclp,srcPort,dstlp,dstPort}(源地址+端口号 ,目的地址+端口号)这样的4元组就能标识互联网中唯二的两个进程
  • 所以,网络通信的本质,也是进程间通信
  • 我们把ip+port 叫做套接字socket

1.4 认识TCP 协议与UDP协议

TCP(Transmission Control Protocol 传输控制协议)

  • 传输层协议

  • 有连接

  • 可靠传输

  • 面向字节流
    UDP(User DatagramProtocol用户数据报协议)

  • 传输层协议

  • 无连接

  • 不可靠传输

  • 面向数据报

1.5 网络字节序

内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP 协议规定**,网络数据流应采用大端字节序,即低地址高字节.**
  • 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可;

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运

行,可以调用以下库函数做网络字节序和主机字节序的转换。

  • 这些函数名很好记,h表示host,n表示network,I表示32位长整数,s表示16位短整数。
  • 例如 htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

1.6 socket常见API

这里介绍一下,后续会讲解

cpp 复制代码
C /
/ 创建 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);

1.7 sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及

后面要讲的UNIX Domain Socket.然而,各种网络协议的地址格式并不相同.

由于存在本地连接和网络连接,存在不同的结构体,为了适配这两种连接方式,就使用struct sockaddr强转获取

  • IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包括16 位地址类型,16 位端口号和32位IP 地址.
  • IPv4、IPv6 地址类型分别定义为常数AF_INET、AF_INET6.这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容.
  • socketAPl可以都用struct sockaddr*类型表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性,可以接收IPv4,IPv6,以及UNIXDomainSocket各种类型的 sockaddr结构体指针做为参数;
1.7.1 sockaddr结构

1.7.2 sockaddr_in结构

虽然socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时, 使用的数据结构是 sockaddr_in**; 这个结构里主要有三部分信息: 地址类型, 端口号, IP 地址**

1.7.3in_addr结构

in_addr用来表示一个IPv4的IP地址.其实就是一个32位的整数;

2.Socket编程UDP

cpp 复制代码
        // struct sockaddr_in
        // {
        //     __SOCKADDR_COMMON(sin_);
        //     in_port_t sin_port;      // 端口号
        //     struct in_addr sin_addr; // 互联网地址
        //     unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
        // };

2.0 V1-Udpsocket

2.1 UdpServer.hpp

2.1.1 创建套接字
cpp 复制代码
 _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;

创建IPv4网络,以数据报的模式读取

2.1.2 填充socket
cpp 复制代码
 struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清零
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // 网络序列为大端序列
                                       //  IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 ->                             in_addr_t inet_addr(const char *cp);
        //local.sin_addr.s_addr = inet_addr(_ip.c_str());
        local.sin_addr.s_addr = INADDR_ANY;//可以通过端口号连接,而不是指定IP地址

将相应的信息填充,AF_INET表示接收所有Ip地址的,将起设为0x0000000地址,等待客户端传输

2.1.3 绑定socket
cpp 复制代码
  int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(2); // 1,2以致于迅速找到报错位置
        }

我们创建了AF_INET, SOCK_DGRAM的套接字,将其与本地sockaddr绑定

注意:这里的bind是需要sockaddr,而不是sockaddr_in

以上三步是创建网络的基本步骤

2.1.4 接收客户端的信息处理

对于服务端来说,我们先要接收到客户端的请求,服务端进行处理请求

sockfd:套接字

//输出型参数

buf:接收缓冲区

len:buf大小

flags:标志符(目前设置为0)

src_addr:(struct sockaddr)一般要强制转换

addrlen:struct sockaddr大小

cpp 复制代码
 ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                int peer_port = ntohs(peer.sin_port);           // 从网络中拿到的!网络序列
                std::string peer_ip = inet_ntoa(peer.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
                buffer[s] = 0;

                LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port<< "]# " << buffer;
                // 2. 发消息
                std::string echo_string = "server echo@ ";
                echo_string += buffer;
                sendto(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, len);
            }
2.1.5 完整代码
cpp 复制代码
#pragma once

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

using namespace LogModule;

const int defaultfd = -1;

class UdpServer
{
public:
    //UdpServer(const std::string &ip, uint16_t port)
    UdpServer(uint16_t port)
        : _sockfd(defaultfd),
          //_ip(ip),
          _port(port),
          _isrunning(false)
    {
    }
    void Init()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
        // 2. 绑定socket信息,ip和端口
        // 2.1 填充sockaddr_in结构体
        // struct sockaddr_in
        // {
        //     __SOCKADDR_COMMON(sin_);
        //     in_port_t sin_port;      // 端口号
        //     struct in_addr sin_addr; // 互联网地址
        //     unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
        // };
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清零
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // 网络序列为大端序列
                                       //  IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp);
        //local.sin_addr.s_addr = inet_addr(_ip.c_str());
        local.sin_addr.s_addr = INADDR_ANY;//可以通过端口号连接,而不是指定IP地址
        // 绑定网络
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(2); // 1,2以致于迅速找到报错位置
        }
        LOG(LogLevel::INFO) << "bind success, sockfd : " << _sockfd;
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            //服务端收消息,处理数据
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                int peer_port = ntohs(peer.sin_port);           // 从网络中拿到的!网络序列
                std::string peer_ip = inet_ntoa(peer.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
                buffer[s] = 0;

                LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port<< "]# " << buffer;
                // 2. 发消息
                std::string echo_string = "server echo@ ";
                echo_string += buffer;
                sendto(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;  // 端口号
    //std::string _ip; // 用的是字符串风格,点分十进制
    bool _isrunning;
};

2.2 UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp"

// std::string defaulthandler(const std::string &message)
// {
//     std::string hello = "hello, ";
//     hello += message;
//     return hello;
// }

// ./udpserver ip + port
// ./udpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << "ip port" << std::endl;
        return 1;
    }
    //std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]); // 字符串转整形
    
    Enable_Console_Log_Strategy();      // 开启日志

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);//智能指针
    usvr->Init();
    usvr->Start();
    return 0;
}

2.3 UdpClient.cc

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

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    //判断是否符合格式
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]); // stoi,字符串转整形

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }
    // 绑定IP地址和端口号:服务器端需要绑定一个特定的IP地址和端口号,
    // 以便客户端能够找到并连接到服务器。如果服务器没有绑定IP和端口,客户端将无法知道如何连接到服务器

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    while (true)
    {
        std::string input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input); // 从标准输入流读取字符串
        // 客户端向服务器发送信息
        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        (void)n;

        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //客户端从服务器中收消息
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
         if(m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }

    return 0;
}

2.4 Makefile

cpp 复制代码
.PHONY:all
all:udpclient udpserver

udpclient:UdpClient.cc
	g++ -o $@ $^ -std=c++17 -static
udpserver:UdpServer.cc
	g++ -o $@ $^ -std=c++17

.PHONY:clean
clean:
	rm -f udpclient udpserver

3.Udpsocket实现简单的翻译功能

3.1 InetAddr.hpp

实现一个类用于网络地址和主机地址的转换

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// 网络地址和主机地址之间进行转换的类

class InetAddr
{
public:
    InetAddr(struct sockaddr_in &addr) : _addr(addr)
    {
        _port = ntohs(_addr.sin_port);  // 从网络中拿到的!网络序列
        _ip = inet_ntoa(addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
    }
    uint16_t Port() { return _port; }
    std::string Ip() { return _ip; }

    ~InetAddr()
    {
    }
private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

3.2 Dict.hpp

cpp 复制代码
#pragma once

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

using namespace LogModule;

const std::string defaultdict = "./dictionary.txt"; // 当前目录
const std::string sep = ": ";                       // apple: 苹果 :为分隔符

class Dict
{
public:
    Dict(const std::string &path = defaultdict) : _dict_path(path)
    {
    }
    bool LoadDict()
    {
        std::ifstream in(_dict_path);
        if (!in.is_open())
        {
            LOG(LogLevel::DEBUG) << "打开字典" << _dict_path << "失败";
            return false;
        }
        std::string line;
        while (std::getline(in, line))
        {
            auto pos = line.find(sep);
            if (pos == std::string::npos) // npos == -1, 表示没有找到
            {
                LOG(LogLevel::WARNING) << "解析: " << line << " 失败";
                continue; // 此时跳过,等待下次的查询
            }
            std::string english = line.substr(0, pos);// apple: 苹果 :为分隔符
            std::string chinese = line.substr(pos + sep.size());
            if (english.empty() || chinese.empty())
            {
                LOG(LogLevel::WARNING) << "没有有效内容: " << line;
                continue;
            }
            _dict.insert(std::make_pair(english, chinese));
            LOG(LogLevel::DEBUG) << "加载: " << line;
        }
        in.close();
        return true;
    }

    std::string Translate(const std::string &word, InetAddr &client)
    {
        auto iter = _dict.find(word);
        if (iter == _dict.end()) // 如果没有找到会返回该字符串最后一位
        {
            LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->None";
            return "None";
        }
        LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->" << iter->second;
        return iter->second;
    }

private:
    std::string _dict_path;                             // 翻译文件// 路径+文件名
    std::unordered_map<std::string, std::string> _dict; // 哈希表
};

3.3 UdpServer.hpp

cpp 复制代码
#pragma once

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

using namespace LogModule;

const int defaultfd = -1;

using func_t = std::function<std::string(const std::string &, InetAddr &)>;

class UdpServer
{
public:
    // UdpServer(const std::string &ip, uint16_t port)
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd),
          //_ip(ip),
          _port(port),
          _isrunning(false),
           _func(func)
    {
    }
    void Init()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
        // 2. 绑定socket信息,ip和端口
        // 2.1 填充sockaddr_in结构体
        // struct sockaddr_in
        // {
        //     __SOCKADDR_COMMON(sin_);
        //     in_port_t sin_port;      // 端口号
        //     struct in_addr sin_addr; // 互联网地址
        //     unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
        // };
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清零
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // 网络序列为大端序列
                                       //  IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        local.sin_addr.s_addr = INADDR_ANY; // 可以通过端口号连接,而不是指定IP地址
        // 绑定网络
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(2); // 1,2以致于迅速找到报错位置
        }
        LOG(LogLevel::INFO) << "bind success, sockfd : " << _sockfd;
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 服务端收消息,处理数据
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                // int peer_port = ntohs(peer.sin_port);           // 从网络中拿到的!网络序列
                // std::string peer_ip = inet_ntoa(peer.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP

                InetAddr client(peer);
                buffer[s] = 0;

                std::string result = _func(buffer, client);
                // LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port<< "]# " << buffer;
                //  2. 发消息
                // std::string echo_string = "server echo@ ";
                // echo_string += buffer;
                sendto(_sockfd, result.c_str(), sizeof(result), 0, (struct sockaddr *)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port; // 端口号
    // std::string _ip; // 用的是字符串风格,点分十进制
    bool _isrunning;
    func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};

3.4 UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"

// std::string defaulthandler(const std::string &message)
// {
//     std::string hello = "hello, ";
//     hello += message;
//     return hello;
// }

// ./udpserver ip + port
// ./udpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << "ip port" << std::endl;
        return 1;
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]); // 字符串转整形

    Enable_Console_Log_Strategy(); // 开启日志

    // 1. 字典对象提供翻译功能
    Dict dict;
    dict.LoadDict();
    // 2. 网络服务器对象,提供通信功能
    // std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, InetAddr &cli) -> std::string
    { return dict.Translate(word, cli); });
    
    usvr->Init();
    usvr->Start();
    return 0;
}

3.5 UdpClient.cc

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

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    //判断是否符合格式
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]); // stoi,字符串转整形

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }
    // 绑定IP地址和端口号:服务器端需要绑定一个特定的IP地址和端口号,
    // 以便客户端能够找到并连接到服务器。如果服务器没有绑定IP和端口,客户端将无法知道如何连接到服务器

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    while (true)
    {
        std::string input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input); // 从标准输入流读取字符串
        // 客户端向服务器发送信息
        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        (void)n;

        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //客户端从服务器中收消息
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
         if(m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }

    return 0;
}

4. Udpsocket实现聊天室

4.1 Cond.hpp(多线程)

cpp 复制代码
#pragma once

#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"

using namespace MutexModule;

namespace CondModule
{
    class Cond
    {
    public:
        Cond()
        {
            pthread_cond_init(&_cond, nullptr);
        }
        void Wait(Mutex &mutex)
        {
            int n = pthread_cond_wait(&_cond, mutex.Get());
            (void)n;
        }
        void Signal()
        {
            // 唤醒在条件变量下等待的一个线程
            int n = pthread_cond_signal(&_cond);
            (void)n;
        }
        void Broadcast()
        {
            // 唤醒所有在条件变量下等待的线程
            int n = pthread_cond_broadcast(&_cond);
            (void)n;
        }
        ~Cond()
        {
            pthread_cond_destroy(&_cond);
        }
    private:
        pthread_cond_t _cond;
    };
};

4.2 Route.hpp(路由服务)

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"

using namespace MutexModule;
using namespace LogModule;

class Route
{
private:
    bool IsExist(InetAddr &peer)
    {
        for (auto &user : _online_user)
        {
            if (user == peer)
            {
                return true;
            }
        }
        return false;
    }

    void AddUser(InetAddr &peer)
    {
        LOG(LogLevel::INFO) << "新增一个在线用户: " << peer.StringAddr();
        _online_user.push_back(peer);
    }

    void DeleteUser(InetAddr &peer)
    {
        for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
        {
            if (*iter == peer)
            {
                LOG(LogLevel::INFO) << "删除一个在线用户:" << peer.StringAddr() << "成功";
                _online_user.erase(iter);
                break;
            }
        }
    }

public:
    Route()
    {
    }
    void MessageRoute(int sockfd, const std::string &message, InetAddr &peer)
    {
        //这里并不是原子的,当扫描用户发消息时,可能存在扫描的同时添加或删除成员
        LockGuard lockguard(_mutex);//创建临时变量锁,当锁运行完该函数析构释放
        
        if (!IsExist(peer))
        {
            AddUser(peer);
        }

        std::string send_message = peer.StringAddr() + "# " + message; // 127.0.0.1:8080# 你好
        for (auto &user : _online_user)
        {
            sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
        }

        // 这个用户一定已经在线了
        if (message == "QUIT")
        {
            LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr();
            DeleteUser(peer);
        }
    }
    ~Route()
    {
    }

private:
    // 首次给我发消息,等同于登录
    std::vector<InetAddr> _online_user; // 在线用户
    Mutex _mutex;
};

4.3 UdpServer.hpp

cpp 复制代码
#pragma once

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

using namespace LogModule;

const int defaultfd = -1;

using func_t = std::function<void(int sockfd,const std::string &, InetAddr &)>;

class UdpServer
{
public:
    // UdpServer(const std::string &ip, uint16_t port)
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd),
          //_ip(ip),
          _port(port),
          _isrunning(false),
           _func(func)
    {
    }
    void Init()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
        // 2. 绑定socket信息,ip和端口
        // 2.1 填充sockaddr_in结构体
        // struct sockaddr_in
        // {
        //     __SOCKADDR_COMMON(sin_);
        //     in_port_t sin_port;      // 端口号
        //     struct in_addr sin_addr; // 互联网地址
        //     unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
        // };
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清零
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // 网络序列为大端序列
                                       //  IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        local.sin_addr.s_addr = INADDR_ANY; // 可以通过端口号连接,而不是指定IP地址
        // 绑定网络
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(2); // 1,2以致于迅速找到报错位置
        }
        LOG(LogLevel::INFO) << "bind success, sockfd : " << _sockfd;
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 服务端收消息,处理数据
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                // int peer_port = ntohs(peer.sin_port);           // 从网络中拿到的!网络序列
                // std::string peer_ip = inet_ntoa(peer.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP

                InetAddr client(peer);
                buffer[s] = 0;

               _func(_sockfd,buffer, client);
                // LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port<< "]# " << buffer;
                //  2. 发消息
                // std::string echo_string = "server echo@ ";
                // echo_string += buffer;
                //sendto(_sockfd, result.c_str(), sizeof(result), 0, (struct sockaddr *)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port; // 端口号
    // std::string _ip; // 用的是字符串风格,点分十进制
    bool _isrunning;
    func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};

4.4 UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Route.hpp"
#include "ThreadPool.hpp"
// std::string defaulthandler(const std::string &message)
// {
//     std::string hello = "hello, ";
//     hello += message;
//     return hello;
// }

using namespace ThreadPoolModule;

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

// ./udpserver ip + port
// ./udpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << "ip port" << std::endl;
        return 1;
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]); // 字符串转整形

    Enable_Console_Log_Strategy(); // 开启日志

    // 1. 路由服务
    Route r;
    // 2. 线程池
    auto tp = ThreadPool<task_t>::GetInstance();
    // 3. 网络服务器对象,提供通信功能
    // 2. 网络服务器对象,提供通信功能
    // std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);
    // std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r](int sockfd, const std::string &message, InetAddr &peer)
    // { r.MessageRoute(sockfd, message, peer); });
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r, &tp](int sockfd, const std::string &message, InetAddr &peer)
    { auto t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);
                        //使用r对象的MessageRoute方法, sockfd, message, peer引用的参数
    tp->Enqueue(t); });
    usvr->Init();
    usvr->Start();
    return 0;
}

4.5 UdpClient.cc

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

int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id;

using namespace ThreadModlue;

void Recv()
{
    while (true)
    {
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // 客户端从服务器中收消息
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }
}

void Send()
{
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    while (true)
    {
        std::string input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input); // 从标准输入流读取字符串
        // 客户端向服务器发送信息
        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        (void)n;
    }
}

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    // 判断是否符合格式
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    server_ip = argv[1];
    server_port = std::stoi(argv[2]); // stoi,字符串转整形

    // 1. 创建socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }
    // 绑定IP地址和端口号:服务器端需要绑定一个特定的IP地址和端口号,
    // 以便客户端能够找到并连接到服务器。如果服务器没有绑定IP和端口,客户端将无法知道如何连接到服务器

    Thread recver(Recv);
    Thread sender(Send);

    recver.Start();
    sender.Start();

    recver.Join();
    sender.Join();
    
    return 0;
}
相关推荐
2301_816073832 小时前
Chrony服务器
运维·服务器
安全不再安全3 小时前
免杀技巧 - 早鸟注入详细学习笔记
linux·windows·笔记·学习·测试工具·web安全·网络安全
八个程序员3 小时前
自定义函数(C++)
开发语言·c++·算法
学网络的APang3 小时前
Apache HTTP Server 2.4.65 详细安装教程(基于 CentOS 7)
运维·网络
pursue.dreams3 小时前
Ubuntu安装Jenkins完整教程
linux·ubuntu·jenkins
实心儿儿3 小时前
Linux系统 —— 基础命令1
linux·运维·服务器
大龄Python青年3 小时前
C#快入教程:Linux安装.NET
linux·c#·.net
微露清风4 小时前
系统性学习C++-第十讲-stack 和 quene
java·c++·学习
抠脚学代码4 小时前
Linux开发-->驱动开发-->字符设备驱动框架
linux·数据结构·驱动开发