UDP套接字编程

一、socket编程接口

以下是socket常见的API:

1.1socket套接字(创建套接字文件)

1.2bind绑定接口以及ip地址的转换

1.3recv接口接收消息

1.4popen创建管道和子进程读取命令

二、创建一个服务器(并让它启动)

2.1udp.hpp文件

cpp 复制代码
#pragma once
#include<stdio.h>
#include<iostream>
#include<memory>
#include<vector>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string> 
#include<arpa/inet.h>
#include<strings.h>
#include<functional>
#include<stdbool.h>
#include "Log.hpp"

typedef std::function<std::string(const std::string &, const std::string& , uint16_t)> func_t;
//也可以写成下面这种形式
//using func_t = std::function<std::string(const std::string&)>;

#define SIZE 1024

extern log lg;

enum
{
    SOCKET_ERR = 1,
    BIND_ERR = 2
};

//给ip地址设置一个缺省值
std::string defaultip = "0.0.0.0";
//给端口号设置一个缺省值
uint16_t defaultport = 8080;

class UdpServer
{
public:
    UdpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip)
    :_sockfd(0)
    ,_port(port)
    ,_ip(ip)
    ,_isrunning(false)
    {}

    void Run(func_t func)//对代码分层解耦
    {
        _isrunning = true;
        char in_buffer[SIZE];

        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer)-1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg.logmessage(Warning, "Recbfrom error, errno:%d, erroe string: %s",errno, strerror(errno));
                continue;
            }
            in_buffer[n] = '\0';

            //获取客户端的端口号和ip地址
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);

            //处理拿到的数据
            std::string info = in_buffer;
            std::string echo_string = func(info, clientip, clientport);

            std::cout<<echo_string<<std::endl;

            //把信息发送回去
            sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0 ,(const sockaddr*)&client, len);
        }
    }

    void Init()
    {
        //1、创建UDP套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            //创建套接字失败
            lg.logmessage(Fatal, "socket create error, sockfd: %d",_sockfd);
            exit(SOCKET_ERR);
        }
        else
        {
            lg.logmessage(Info, "socket create success, sockfd: %d",_sockfd);
        }

        //2、绑定端口号
        struct sockaddr_in local;
        bzero(&local, sizeof(local));//把local全部清理成0
        local.sin_family = AF_INET;//表明使用IPV4的网络结构
        local.sin_port = htons(_port);//需要绑定的端口号,要保证端口号是网络字节序列,因为端口号是要发送给对方的
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1、将ip地址的string转换成uint16_t  2、转换成的字节序也必须是网络的字节序

        //这里还有一种写法:
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        //绑定端口号
        int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            //绑定失败
            lg.logmessage(Fatal, "bind error, error:%d, error message:%s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        else
        {
            lg.logmessage(Info, "bind ssuccess");
        }
    }
    
    ~UdpServer()
    {
        if(_sockfd > 0)
        {
            close(_sockfd);
        }
    }
private:
    int _sockfd;//网络文件描述符
    uint16_t _port;//服务器的端口号(表明服务器的端口号)
    std::string _ip;//确定主机的ip地址
    bool _isrunning;//表示服务器是否运行
};

2.2main.cc文件

cpp 复制代码
#include "UdpServer.hpp"
#define SIZE1 4096

log lg;

void Usage(std::string usage)
{
    std::cout << "Usage:" << usage << "port[1024+]" << std::endl;
}

std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{
    std::cout << "[" << clientip << ": " << clientport << "]#" << info << std::endl;
    std::string res = "Server get a menssage:";
    res += info;
    return res;
}

bool SafeCheck(const std::string &cmd)
{
    int safe = false;

    std::vector<std::string> keyword = {"rm", "mv", "cp", "kill", "sudo", "yum", "unlink", "top", "uninstall"};

    for (auto &word : keyword)
    {
        auto pos = cmd.find(word);
        if (pos != std::string::npos)
        {
            return false;
        }
    }
    return true;
}

std::string ExcuteComand(const std::string &cmd)
{
    std::cout << "get a request cmand:" << cmd << std::endl;

    // 安全检查
    SafeCheck(cmd);

    FILE *fp = popen(cmd.c_str(), "r");
    if (fp == nullptr)
    {
        perror("popen faile");
        return "error";
    }

    std::string result;
    char buffer[SIZE1];
    while (true)
    {
        char *ok = fgets(buffer, sizeof(buffer), fp);
        if (ok == nullptr)
        {
            break;
        }
        result += buffer;
    }

    pclose(fp);
    return result;
}

int main(int argc, char *argv[])
{

    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init();
    svr->Run(Handler);

    return 0;
}

2.3运行后以及相关端口号和ip地址的设计细节

三、udp创建一个服务端和一个客户端,并实现通信

3.1服务端的实现

udpserver.hpp文件

cpp 复制代码
#pragma once
#include<stdio.h>
#include<iostream>
#include<memory>
#include<vector>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string> 
#include<arpa/inet.h>
#include<strings.h>
#include<functional>
#include<stdbool.h>
#include "Log.hpp"

typedef std::function<std::string(const std::string &, const std::string& , uint16_t)> func_t;
//也可以写成下面这种形式
//using func_t = std::function<std::string(const std::string&)>;

#define SIZE 1024

extern log lg;

enum
{
    SOCKET_ERR = 1,
    BIND_ERR = 2
};

//给ip地址设置一个缺省值
std::string defaultip = "0.0.0.0";
//给端口号设置一个缺省值
uint16_t defaultport = 8080;

class UdpServer
{
public:
    UdpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip)
    :_sockfd(0)
    ,_port(port)
    ,_ip(ip)
    ,_isrunning(false)
    {}

    void Run(func_t func)//对代码分层解耦
    {
        _isrunning = true;
        char in_buffer[SIZE];

        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer)-1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg.logmessage(Warning, "Recbfrom error, errno:%d, erroe string: %s",errno, strerror(errno));
                continue;
            }
            in_buffer[n] = '\0';

            //获取客户端的端口号和ip地址
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);

            //处理拿到的数据
            std::string info = in_buffer;
            std::string echo_string = func(info, clientip, clientport);

            std::cout<<echo_string<<std::endl;

            //把信息发送回去
            sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0 ,(const sockaddr*)&client, len);
        }
    }

    void Init()
    {
        //1、创建UDP套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            //创建套接字失败
            lg.logmessage(Fatal, "socket create error, sockfd: %d",_sockfd);
            exit(SOCKET_ERR);
        }
        else
        {
            lg.logmessage(Info, "socket create success, sockfd: %d",_sockfd);
        }

        //2、绑定端口号
        struct sockaddr_in local;
        bzero(&local, sizeof(local));//把local全部清理成0
        local.sin_family = AF_INET;//表明使用IPV4的网络结构
        local.sin_port = htons(_port);//需要绑定的端口号,要保证端口号是网络字节序列,因为端口号是要发送给对方的
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1、将ip地址的string转换成uint16_t  2、转换成的字节序也必须是网络的字节序

        //这里还有一种写法:
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        //绑定端口号
        int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            //绑定失败
            lg.logmessage(Fatal, "bind error, error:%d, error message:%s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        else
        {
            lg.logmessage(Info, "bind ssuccess");
        }
    }
    
    ~UdpServer()
    {
        if(_sockfd > 0)
        {
            close(_sockfd);
        }
    }
private:
    int _sockfd;//网络文件描述符
    uint16_t _port;//服务器的端口号(表明服务器的端口号)
    std::string _ip;//确定主机的ip地址
    bool _isrunning;//表示服务器是否运行
};

main.cc文件:

cpp 复制代码
#include "UdpServer.hpp"
#define SIZE1 4096

log lg;

void Usage(std::string usage)
{
    std::cout << "Usage:" << usage << "port[1024+]" << std::endl;
}

std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{
    std::cout << "[" << clientip << ": " << clientport << "]#" << info << std::endl;
    std::string res = "Server get a menssage:";
    res += info;
    return res;
}

bool SafeCheck(const std::string &cmd)
{
    int safe = false;

    std::vector<std::string> keyword = {"rm", "mv", "cp", "kill", "sudo", "yum", "unlink", "top", "uninstall"};

    for (auto &word : keyword)
    {
        auto pos = cmd.find(word);
        if (pos != std::string::npos)
        {
            return false;
        }
    }
    return true;
}

std::string ExcuteComand(const std::string &cmd)
{
    std::cout << "get a request cmand:" << cmd << std::endl;

    // 安全检查
    SafeCheck(cmd);

    FILE *fp = popen(cmd.c_str(), "r");
    if (fp == nullptr)
    {
        perror("popen faile");
        return "error";
    }

    std::string result;
    char buffer[SIZE1];
    while (true)
    {
        char *ok = fgets(buffer, sizeof(buffer), fp);
        if (ok == nullptr)
        {
            break;
        }
        result += buffer;
    }

    pclose(fp);
    return result;
}

int main(int argc, char *argv[])
{

    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init();
    svr->Run(Handler);

    return 0;
}

3.2客户端的实现

udpClient.cc文件:

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

void Usage(std::string proc)
{
    std::cout << "\n\rUsage:" << proc << "serverip serverport\n"
              << std::endl;
}

// ./udpClient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 构建客户端套接字信息
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    socklen_t len = sizeof(server);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cout << "socket fail" << std::endl;
        return 1;
    }

    // 客户端也是需要绑定,只不过不需要用户显示的绑定,一般由操作系统随机绑定、
    // 一个端口号只能被一个进程绑定,对于客户端和服务端都是如此
    // 其实客户端的端口号是多少不重要,只要能保证主机上的唯一性就行
    // 系统会在首次发送细心的时候给绑定
    std::string messager;
    char buffer[1024];

    while (true)
    {
        // 1、拿到信息
        std::cout << "please enter:";
        std::getline(std::cin, messager);

        std::cout << messager << std::endl;

        // 2、发送信息
        sendto(sockfd, messager.c_str(), messager.size(), 0, (struct sockaddr *)&server, len);

        // 3、接收消息
        struct sockaddr_in temp;
        socklen_t ln = sizeof(temp);

        ssize_t n = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &ln);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
    }

    close(sockfd);
    return 0;
}

3.3运行结果:

四、实现Linux(服务端)和windows通信(客户端)

4.1Linux服务端的代码

udpserver.hpp文件:

cpp 复制代码
#pragma once
#include<stdio.h>
#include<iostream>
#include<memory>
#include<vector>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string> 
#include<arpa/inet.h>
#include<strings.h>
#include<functional>
#include<stdbool.h>
#include "Log.hpp"

typedef std::function<std::string(const std::string &, const std::string& , uint16_t)> func_t;
//也可以写成下面这种形式
//using func_t = std::function<std::string(const std::string&)>;

#define SIZE 1024

extern log lg;

enum
{
    SOCKET_ERR = 1,
    BIND_ERR = 2
};

//给ip地址设置一个缺省值
std::string defaultip = "0.0.0.0";
//给端口号设置一个缺省值
uint16_t defaultport = 8080;

class UdpServer
{
public:
    UdpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip)
    :_sockfd(0)
    ,_port(port)
    ,_ip(ip)
    ,_isrunning(false)
    {}

    void Run(func_t func)//对代码分层解耦
    {
        _isrunning = true;
        char in_buffer[SIZE];

        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer)-1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg.logmessage(Warning, "Recbfrom error, errno:%d, erroe string: %s",errno, strerror(errno));
                continue;
            }
            in_buffer[n] = '\0';

            //获取客户端的端口号和ip地址
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);

            //处理拿到的数据
            std::string info = in_buffer;
            std::string echo_string = func(info, clientip, clientport);

            std::cout<<echo_string<<std::endl;

            //把信息发送回去
            sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0 ,(const sockaddr*)&client, len);
        }
    }

    void Init()
    {
        //1、创建UDP套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            //创建套接字失败
            lg.logmessage(Fatal, "socket create error, sockfd: %d",_sockfd);
            exit(SOCKET_ERR);
        }
        else
        {
            lg.logmessage(Info, "socket create success, sockfd: %d",_sockfd);
        }

        //2、绑定端口号
        struct sockaddr_in local;
        bzero(&local, sizeof(local));//把local全部清理成0
        local.sin_family = AF_INET;//表明使用IPV4的网络结构
        local.sin_port = htons(_port);//需要绑定的端口号,要保证端口号是网络字节序列,因为端口号是要发送给对方的
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1、将ip地址的string转换成uint16_t  2、转换成的字节序也必须是网络的字节序

        //这里还有一种写法:
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        //绑定端口号
        int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            //绑定失败
            lg.logmessage(Fatal, "bind error, error:%d, error message:%s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        else
        {
            lg.logmessage(Info, "bind ssuccess");
        }
    }
    
    ~UdpServer()
    {
        if(_sockfd > 0)
        {
            close(_sockfd);
        }
    }
private:
    int _sockfd;//网络文件描述符
    uint16_t _port;//服务器的端口号(表明服务器的端口号)
    std::string _ip;//确定主机的ip地址
    bool _isrunning;//表示服务器是否运行
};

main.cc文件:

cpp 复制代码
#include "UdpServer.hpp"
#define SIZE1 4096

log lg;

void Usage(std::string usage)
{
    std::cout << "Usage:" << usage << "port[1024+]" << std::endl;
}

std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{
    std::cout << "[" << clientip << ": " << clientport << "]#" << info << std::endl;
    std::string res = "Server get a menssage:";
    res += info;
    return res;
}

bool SafeCheck(const std::string &cmd)
{
    int safe = false;

    std::vector<std::string> keyword = {"rm", "mv", "cp", "kill", "sudo", "yum", "unlink", "top", "uninstall"};

    for (auto &word : keyword)
    {
        auto pos = cmd.find(word);
        if (pos != std::string::npos)
        {
            return false;
        }
    }
    return true;
}

std::string ExcuteComand(const std::string &cmd)
{
    std::cout << "get a request cmand:" << cmd << std::endl;

    // 安全检查
    SafeCheck(cmd);

    FILE *fp = popen(cmd.c_str(), "r");
    if (fp == nullptr)
    {
        perror("popen faile");
        return "error";
    }

    std::string result;
    char buffer[SIZE1];
    while (true)
    {
        char *ok = fgets(buffer, sizeof(buffer), fp);
        if (ok == nullptr)
        {
            break;
        }
        result += buffer;
    }

    pclose(fp);
    return result;
}

int main(int argc, char *argv[])
{

    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init();
    svr->Run(Handler);

    return 0;
}

4.2windows客户端代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include <WinSock2.h>
#include<Windows.h>
#include<string>
#include<string.h>

#pragma warning(disable:4996)

#pragma comment(lib, "ws2_32.lib")

uint16_t serverport = 8080;
std::string serverip = "182.254.175.111";

int main()
{
	WSADATA wsd;
	WSAStartup(MAKEWORD(2, 2), &wsd);

    // 构建客户端套接字信息
    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());

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < SOCKET_ERROR)
    {
        std::cout << "socket fail" << std::endl;
        return 1;
    }

    std::string messager;
    char buffer[1024];

    while (true)
    {
        // 1、拿到信息
        std::cout << "please enter:";
        std::getline(std::cin, messager);

        std::cout << messager << std::endl;

        // 2、发送信息
        sendto(sockfd, messager.c_str(), messager.size(), 0, (struct sockaddr*)&server, sizeof(server));

        // 3、接收消息
        struct sockaddr_in temp;
        int ln = sizeof(temp);

        int n = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &ln);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
    }


	closesocket(sockfd);

	WSACleanup();
	return 0;
}

4.3运行结果展示

五、创建一个聊天室,完成群聊

UDP套接字是全双工的,是允许同时被写入和同时读出的,所以我们可以创建两个或者多个线程进行同时的收发信息。

5.1客户端---创建两个线程实现接收消息和发送消息

udpClient.cc文件:

cpp 复制代码
#include"Terminal.hpp"
#include <iostream>
#include <memory>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <cstdlib>
#include<pthread.h>

void Usage(std::string proc)
{
    std::cout << "\n\rUsage:" << proc << "serverip serverport\n"
              << std::endl;
}

struct ThreadData
{
    struct sockaddr_in server;
    int sockfd;
};

void *recv_messager(void *args)
{
    //让收发消息分离,直接终端重定向
    //OpenTerminal();

    
    ThreadData* td = static_cast<ThreadData*>(args);
    char buffer[1024];
    while(true)
    {
        // 接收消息
        struct sockaddr_in temp;
        socklen_t ln = sizeof(temp);

        ssize_t n = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &ln);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cerr << buffer << std::endl;
        }
    }
}

void *send_messager(void *args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
    std::string messager;
    socklen_t len = sizeof(td->server);
    while(true)
    {
        // 1、拿到信息
        std::cout << "please enter@:";
        std::getline(std::cin, messager);

        std::cout << messager << std::endl;

        // 2、发送信息
        sendto(td->sockfd, messager.c_str(), messager.size(), 0, (struct sockaddr *)&td->server, len);
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct ThreadData td;

    // 构建客户端套接字信息
    bzero(&td.server, sizeof(td.server));
    td.server.sin_family = AF_INET;
    td.server.sin_port = htons(serverport);
    td.server.sin_addr.s_addr = inet_addr(serverip.c_str());
    

    td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (td.sockfd < 0)
    {
        std::cout << "socket fail" << std::endl;
        return 1;
    }

    //创建收发信息的两个线程
    pthread_t recvr;
    pthread_t sender;
    pthread_create(&recvr, nullptr, recv_messager, &td);
    pthread_create(&sender, nullptr, send_messager, &td);

    pthread_join(recvr,nullptr);
    pthread_join(sender, nullptr);

    close(td.sockfd);
    return 0;
}

5.2服务端实现接收信息和把信息发给所有人

udpServer.hpp文件:

cpp 复制代码
#pragma once
#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>
#include <unordered_map>
#include "Log.hpp"

typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> func_t;

#define SIZE 1024

extern log lg;

enum
{
    SOCKET_ERR = 1,
    BIND_ERR = 2
};

// 给ip地址设置一个缺省值
std::string defaultip = "0.0.0.0";
// 给端口号设置一个缺省值
uint16_t defaultport = 8080;

class UdpServer
{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip)
        : _sockfd(0), _port(port), _ip(ip), _isrunning(false)
    {
    }

    void CheckUser(struct sockaddr_in &client)
    {
        uint16_t clientport = ntohs(client.sin_port);
        std::string clientip = inet_ntoa(client.sin_addr);

        auto iterator = _online_user.find(clientip);
        if (iterator == _online_user.end())
        {
            // 添加用户
            _online_user.insert({clientip, client});
            std::cout << "[" << clientip << ":" << clientport << "]:add online user success" << std::endl;
        }
    }

    void BoardCast(const std::string &info, const std::string clientip, const uint16_t &clientport)
    {
        for (const auto user : _online_user)
        {
            std::string message = "[";
            message += clientip;
            message += ":";
            message += std::to_string(clientport);
            message += "]#";
            message += info;
            // 把信息发送回去
            socklen_t len = sizeof(user.second);
            sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)(&user.second), len);
        }
    }

    void Run() // 对代码分层解耦
    {
        _isrunning = true;
        char in_buffer[SIZE];

        while (_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer) - 1, 0, (struct sockaddr *)&client, &len);
            if (n < 0)
            {
                lg.logmessage(Warning, "Recbfrom error, errno:%d, erroe string: %s", errno, strerror(errno));
                continue;
            }

            // 获取客户端的端口号和ip地址
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);

            // 检查该客户是否已经在表中,不在就在表中添加用户
            CheckUser(client);

            in_buffer[n] = '\0';
            // 处理拿到的数据
            std::string info = in_buffer;

            // 给所有人广播拿到的消息
            BoardCast(info, clientip, clientport);
        }
    }

    void Init()
    {
        // 1、创建UDP套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            // 创建套接字失败
            lg.logmessage(Fatal, "socket create error, sockfd: %d", _sockfd);
            exit(SOCKET_ERR);
        }
        else
        {
            lg.logmessage(Info, "socket create success, sockfd: %d", _sockfd);
        }

        // 2、绑定端口号
        struct sockaddr_in local;
        bzero(&local, sizeof(local));  // 把local全部清理成0
        local.sin_family = AF_INET;    // 表明使用IPV4的网络结构
        local.sin_port = htons(_port); // 需要绑定的端口号,要保证端口号是网络字节序列,因为端口号是要发送给对方的
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1、将ip地址的string转换成uint16_t  2、转换成的字节序也必须是网络的字节序

        // 这里还有一种写法:
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        // 绑定端口号
        int n = bind(_sockfd, (const struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            // 绑定失败
            lg.logmessage(Fatal, "bind error, error:%d, error message:%s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        else
        {
            lg.logmessage(Info, "bind ssuccess");
        }
    }

    ~UdpServer()
    {
        if (_sockfd > 0)
        {
            close(_sockfd);
        }
    }

private:
    int _sockfd;     // 网络文件描述符
    uint16_t _port;  // 服务器的端口号(表明服务器的端口号)
    std::string _ip; // 确定主机的ip地址
    bool _isrunning; // 表示服务器是否运行
    std::unordered_map<std::string, struct sockaddr_in> _online_user;
};

main.cc文件:

cpp 复制代码
#include "UdpServer.hpp"
#define SIZE1 4096

log lg;

void Usage(std::string usage)
{
    std::cout << "Usage:" << usage << "port[1024+]" << std::endl;
}

int main(int argc, char *argv[])
{

    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init();
    svr->Run();

    return 0;
}

5.3在运行时,如果想看见收发信息的分离,可以使用以下两种方法:

5.3.1使用一个函数,进行重定向(如以下Terminal.hpp文件)

cpp 复制代码
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

std::string terminal = "/dev/pts/2";

int OpenTerminal()
{
    int fd = open(terminal.c_str(), O_WRONLY);
    if (fd < 0)
    {
        std::cerr << "open terminal error!!!" << std::endl;
        return 1;
    }
    dup2(fd, 2);
    return 0;
}

5.3.2直接在运行客户端的时候进行重定向

总结:

(1)UDP协议支持全双工,一个sockfd,既可以读取,又可以写入,对于客户端和服务端同样如此

(2)多线程客户端,同时读取和写入

六、地址转换函数

以上只是基于IPv4的socket网络编程,sockaddrin中的成员struct in_addr sin_addr表示32位的IP地址。但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换

6.1字符串转in_addr的函数:

6.2in_addr转字符串的函数:

其中inet_pton和inet ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6 addr,因此函数接口是void *addrptr

6.3inet_ntoa

inet ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果。并且这个函数是不需要调用者手动释放的。

注意:因为inetntoa会把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果那么如果有多个线程调用inet_ntoa,是否会出现异常情况呢?

在APUE中,明确提出inet_ntoa不是线程安全的函数。但是在centos7上测试,并没有出现问题,可能内部的实现加了互斥锁。综上所述,在多线程环境下,推荐使用inet ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。

相关推荐
AC赳赳老秦2 小时前
OpenClaw多平台部署:Windows+Linux跨系统协同,实现全场景覆盖
linux·服务器·前端·网络·windows·deepseek·openclaw
来日可期13142 小时前
计算机存储视角下的有符号数:不止是“正负”那么简单
c++
愚者游世2 小时前
variadic templates(可变参数模板)各版本异同
开发语言·c++·程序人生·面试
爱学习的小囧2 小时前
VMware vCenter Server 9.0.2.0 资源详解+完整部署教程+下载指南+常见问题
运维·服务器·esxi·vmware·虚拟化·esxi9.0.2.0
rannn_1112 小时前
【Redis|原理篇2】Redis网络模型、通信协议、内存回收
java·网络·redis·后端·缓存
徐新帅2 小时前
4181:【GESP2603七级】拆分
c++·学习·算法·信奥赛
无忧.芙桃2 小时前
现代C++精讲之处理类型
开发语言·c++
VOOHU_20182 小时前
VOOHU沃虎:音频变压器的主要作用是什么?什么情况下必须使用?
网络·物联网·音视频·电子元器件
黎梨梨梨_2 小时前
C++入门基础(下)(重载,引用,inline,nullptr)
开发语言·c++·算法