Socket 编程 UDP

发布于:2025-05-09 ⋅ 阅读:(18) ⋅ 点赞:(0)

UDP 网络编程

  • 声明:下面代码的验证都是用Windows作为客户端的,如果你有两台云服务器可以直接粘贴我在Linux下的客户端代码。这里我的客户端使用Windows写的如果用发现你的Windows无法连接到服务器就要去修改你的云服务器的安全组,这里就不详细赘述了,大家感兴趣可以自行搜索

V1 版本 - echo server

  • 代码用到的log.hpp在我的专栏系统部分可以找到这里就不贴了
//UdpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
using namespace LogModule;
using func_t = std::function<std::string(std::string message)>;
class UdpServe
{
public:
    UdpServe(uint16_t port, func_t func)
        : _port(port),
          _func(func)
    {
    }
    ~UdpServe() {}
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 1.接受消息
            sockaddr_in peer; // 客户端
            socklen_t len = sizeof(peer);
            char buffer[1024];
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &len);
            
            if (n > 0)
            {
                buffer[n] = 0;
                std::string peer_ip=inet_ntoa(peer.sin_addr);
                int peer_port=ntohs(peer.sin_port);
                std::string ret = _func(std::string(buffer));
                LOG(LogLevel::INFO)<<"[" << peer_ip << ":" << peer_port<< "]# " << ret;
            }
            
            //2.发消息
            std::string sermes="serve say@"+std::string(buffer);
            //std::cout<<sermes<<std::endl;
            ssize_t n2=sendto(_sockfd,sermes.c_str(),sermes.size(),0,(sockaddr*)&peer,len);

        }
    }
    void Init()
    {
        // 1.创建我们的sockfd
        // 前两个参数就规定了我们用的就是udp
        int n = socket(AF_INET, SOCK_DGRAM, 0);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "sock error";
            exit(2);
        }
        _sockfd=n;
        LOG(LogLevel::INFO) << "sock success";
        // 创建成功了
        // 进行绑定
        sockaddr_in local;
        // 首先进行清零
        bzero(&local, sizeof(local));
        // 填充字段
        local.sin_family = AF_INET;
        // 把本地转为网络序列
        local.sin_port = htons(_port);
        // 这里就是不显示绑定而是绑定该太机器上任意的ip地址
        local.sin_addr.s_addr = INADDR_ANY;
        int ret = bind(_sockfd, (sockaddr *)&local, sizeof(local));
        if (ret < 0)
        {
            LOG(LogLevel::FATAL) << "bind failsure";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind success";
    }

private:
    int _sockfd;
    // std::string _ip;//服务器端一台机器上可能有多个ip我们最好的就是不显示绑定我们的ip地址
    uint16_t _port;
    bool _isrunning;
    func_t _func;
};
//UdpServe.cc
#include"UdpServe.hpp"
#include<memory>
std::string defaultfunc(std::string messges)
{
    return messges;
}





int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    ENABLE_CONSOLE_LOG_STRATEGY();
    uint16_t port=std::stoi(argv[1]);
    std::unique_ptr<UdpServe> udpserveptr=std::make_unique<UdpServe>(port,defaultfunc);
    udpserveptr->Init();
    udpserveptr->Start();
    return 0;
}
//UdpClient.cc Linux
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
using namespace LogModule;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    ENABLE_CONSOLE_LOG_STRATEGY();
    std::string serve_ip = argv[1];
    int serve_port = std::stoi(argv[2]);
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket failsure";
        exit(2);
    }
    // 客户端不用显示绑定在我们第一次发消息的时候就os会自动帮我们绑定客户端机器的ip,端口号随机生成
    sockaddr_in serve;
    bzero(&serve, sizeof(serve));
    socklen_t len = sizeof(serve);
    serve.sin_port = htons(serve_port);
    serve.sin_family = AF_INET;
    serve.sin_addr.s_addr = inet_addr(serve_ip.c_str());
    while (true)
    {
        std::string mesage;
        std::cout << "Please Enter:" << std::endl;
        // 从标准输入流读取mesage
        getline(std::cin, mesage);
        int n = sendto(sockfd, mesage.c_str(), mesage.size(), 0, (sockaddr *)&serve, len);
        (void)n;

        // 收消息
        char buffer[1024];
        sockaddr_in peer;
        bzero(&peer, sizeof(peer));
        socklen_t peerlen=sizeof(peer);
        int m= recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(sockaddr*)&peer,&peerlen);
        if(m>0)
        {
            buffer[m]=0;
            std::cout<<buffer<<std::endl;
        }
    }
    return 0;
}

验证效果:
在这里插入图片描述

//windows作为客户端
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>
#include <io.h>
#include <fcntl.h>

#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")

std::string serverip = ""; // 填写你的云服务器 ip
uint16_t serverport =;              // 填写你的云服务开放的端口号

// 设置控制台编码为 UTF-8
void SetConsoleToUTF8() {
    SetConsoleOutputCP(65001);          // 设置控制台输出编码为 UTF-8
    SetConsoleCP(65001);               // 设置控制台输入编码为 UTF-8
    _setmode(_fileno(stdout), _O_U8TEXT);  // 设置宽字符输出模式
    _setmode(_fileno(stdin), _O_U8TEXT);    // 设置宽字符输入模式
}

int main() {
    SetConsoleToUTF8();  // 必须在其他操作前调用

    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());

    SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == INVALID_SOCKET) {
        std::wcout << L"socket error: " << WSAGetLastError() << std::endl;
        return 1;
    }

    std::wstring wmessage;
    char buffer[1024];

    while (true) {
        std::wcout << L"Please Enter@ ";
        std::getline(std::wcin, wmessage);
        if (wmessage.empty()) continue;

        // 宽字符转 UTF-8
        int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wmessage[0], (int)wmessage.size(), NULL, 0, NULL, NULL);
        std::string message(size_needed, 0);
        WideCharToMultiByte(CP_UTF8, 0, &wmessage[0], (int)wmessage.size(), &message[0], size_needed, NULL, NULL);

        sendto(sockfd, message.c_str(), (int)message.size(), 0,
            (struct sockaddr*)&server, sizeof(server));

        struct sockaddr_in temp;
        int len = sizeof(temp);
        int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);

        if (s > 0) {
            buffer[s] = 0;
            // UTF-8 转宽字符显示
            int wsize_needed = MultiByteToWideChar(CP_UTF8, 0, buffer, s, NULL, 0);
            std::wstring wbuffer(wsize_needed, 0);
            MultiByteToWideChar(CP_UTF8, 0, buffer, s, &wbuffer[0], wsize_needed);
            std::wcout << wbuffer << std::endl;
        }
    }

    closesocket(sockfd);
    WSACleanup();
    return 0;
}

V2 版本 - DictServer

  • 代码用到的其余的模块在我的专栏系统部分可以找到代码这里就不贴了,只贴了服务端代码和客户端代码
//UdpServe.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t = std::function<std::string(std::string message, InetAddr peer)>;
class UdpServe
{
public:
    UdpServe(uint16_t port, func_t func)
        : _port(port),
          _func(func)
    {
    }
    ~UdpServe() {}
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 1.接受消息
            sockaddr_in peer; // 客户端
            socklen_t len = sizeof(peer);
            char buffer[1024];
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &len);
            std::string ret;
            if (n > 0)
            {
                buffer[n] = 0;
                // std::string peer_ip=inet_ntoa(peer.sin_addr);
                // int peer_port=ntohs(peer.sin_port);
                InetAddr client(peer);
                ret = _func(std::string(buffer), client);
                // LOG(LogLevel::INFO)<<"[" << peer_ip << ":" << peer_port<< "]# " << ret;
            }

            // 2.发消息
            // std::string sermes="serve say@"+std::string(buffer);
            // std::cout<<sermes<<std::endl;
            ssize_t n2 = sendto(_sockfd, ret.c_str(), ret.size(), 0, (sockaddr *)&peer, len);
        }
    }
    void Init()
    {
        // 1.创建我们的sockfd
        // 前两个参数就规定了我们用的就是udp
        int n = socket(AF_INET, SOCK_DGRAM, 0);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "sock error";
            exit(2);
        }
        _sockfd = n;
        LOG(LogLevel::INFO) << "sock success";
        // 创建成功了
        // 进行绑定
        sockaddr_in local;
        // 首先进行清零
        bzero(&local, sizeof(local));
        // 填充字段
        local.sin_family = AF_INET;
        // 把本地转为网络序列
        local.sin_port = htons(_port);
        // 这里就是不显示绑定而是绑定该太机器上任意的ip地址
        local.sin_addr.s_addr = INADDR_ANY;
        int ret = bind(_sockfd, (sockaddr *)&local, sizeof(local));
        if (ret < 0)
        {
            LOG(LogLevel::FATAL) << "bind failsure";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind success";
    }

private:
    int _sockfd;
    // std::string _ip;//服务器端一台机器上可能有多个ip我们最好的就是不显示绑定我们的ip地址
    uint16_t _port;
    bool _isrunning;
    func_t _func;
};
//UdpServe.cc
#include "UdpServe.hpp"
#include "dict.hpp"
#include <memory>
#include "InetAddr.hpp"
std::string defaultfunc(std::string messges)
{
    return messges;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    ENABLE_CONSOLE_LOG_STRATEGY();
    uint16_t port = std::stoi(argv[1]);
    // 字典
    Dict dict;
    dict.LoadDict();
    std::unique_ptr<UdpServe> udpserveptr = std::make_unique<UdpServe>(port, [&dict](std::string mess, InetAddr peer) -> std::string {
            return dict.Translate(mess,peer);
    });
    udpserveptr->Init();
    udpserveptr->Start();
    return 0;
}
//UdpClient.cc Linux
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
using namespace LogModule;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    ENABLE_CONSOLE_LOG_STRATEGY();
    std::string serve_ip = argv[1];
    int serve_port = std::stoi(argv[2]);
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket failsure";
        exit(2);
    }
    // 客户端不用显示绑定在我们第一次发消息的时候就os会自动帮我们绑定客户端机器的ip,端口号随机生成
    sockaddr_in serve;
    bzero(&serve, sizeof(serve));
    socklen_t len = sizeof(serve);
    serve.sin_port = htons(serve_port);
    serve.sin_family = AF_INET;
    serve.sin_addr.s_addr = inet_addr(serve_ip.c_str());
    while (true)
    {
        std::string mesage;
        std::cout << "Please Enter:" << std::endl;
        // 从标准输入流读取mesage
        getline(std::cin, mesage);
        int n = sendto(sockfd, mesage.c_str(), mesage.size(), 0, (sockaddr *)&serve, len);
        (void)n;

        // 收消息
        char buffer[1024];
        sockaddr_in peer;
        bzero(&peer, sizeof(peer));
        socklen_t peerlen=sizeof(peer);
        int m= recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(sockaddr*)&peer,&peerlen);
        if(m>0)
        {
            buffer[m]=0;
            std::cout<<buffer<<std::endl;
        }
    }
    return 0;
}
//InetAddr.hpp
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string>
class InetAddr
{
    public:
    InetAddr(sockaddr_in &sock)
    :_sockaddrin(sock)
    {
        _ip=inet_ntoa(_sockaddrin.sin_addr);
        _port=ntohs(_sockaddrin.sin_port);
    }
    ~InetAddr()
    {
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    private:
    sockaddr_in _sockaddrin;
    std::string _ip;
    uint16_t _port;

};
//windows作为客户端
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>
#include <io.h>
#include <fcntl.h>

#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")

std::string serverip = ""; // 填写你的云服务器 ip
uint16_t serverport =;              // 填写你的云服务开放的端口号

// 设置控制台编码为 UTF-8
void SetConsoleToUTF8() {
    SetConsoleOutputCP(65001);          // 设置控制台输出编码为 UTF-8
    SetConsoleCP(65001);               // 设置控制台输入编码为 UTF-8
    _setmode(_fileno(stdout), _O_U8TEXT);  // 设置宽字符输出模式
    _setmode(_fileno(stdin), _O_U8TEXT);    // 设置宽字符输入模式
}

int main() {
    SetConsoleToUTF8();  // 必须在其他操作前调用

    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());

    SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == INVALID_SOCKET) {
        std::wcout << L"socket error: " << WSAGetLastError() << std::endl;
        return 1;
    }

    std::wstring wmessage;
    char buffer[1024];

    while (true) {
        std::wcout << L"Please Enter@ ";
        std::getline(std::wcin, wmessage);
        if (wmessage.empty()) continue;

        // 宽字符转 UTF-8
        int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wmessage[0], (int)wmessage.size(), NULL, 0, NULL, NULL);
        std::string message(size_needed, 0);
        WideCharToMultiByte(CP_UTF8, 0, &wmessage[0], (int)wmessage.size(), &message[0], size_needed, NULL, NULL);

        sendto(sockfd, message.c_str(), (int)message.size(), 0,
            (struct sockaddr*)&server, sizeof(server));

        struct sockaddr_in temp;
        int len = sizeof(temp);
        int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);

        if (s > 0) {
            buffer[s] = 0;
            // UTF-8 转宽字符显示
            int wsize_needed = MultiByteToWideChar(CP_UTF8, 0, buffer, s, NULL, 0);
            std::wstring wbuffer(wsize_needed, 0);
            MultiByteToWideChar(CP_UTF8, 0, buffer, s, &wbuffer[0], wsize_needed);
            std::wcout << wbuffer << std::endl;
        }
    }

    closesocket(sockfd);
    WSACleanup();
    return 0;
}

验证效果:
在这里插入图片描述

V3 版本 - 简单聊天室

//linux udpserve.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t1 = std::function<void(int sockfd,std::string message, InetAddr peer)>;
class UdpServe
{
public:
    UdpServe(uint16_t port, func_t1 func)
        : _port(port),
          _func(func)
    {
    }
    ~UdpServe() {}
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 1.接受消息
            sockaddr_in peer; // 客户端
            socklen_t len = sizeof(peer);
            char buffer[1024];
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &len);
            std::string ret;
            if (n > 0)
            {
                buffer[n] = 0;
                // std::string peer_ip=inet_ntoa(peer.sin_addr);
                // int peer_port=ntohs(peer.sin_port);
                InetAddr client(peer);
                _func(_sockfd,std::string(buffer), client);
                // LOG(LogLevel::INFO)<<"[" << peer_ip << ":" << peer_port<< "]# " << ret;
            }

            // 2.发消息
            // std::string sermes="serve say@"+std::string(buffer);
            // std::cout<<sermes<<std::endl;
            //ssize_t n2 = sendto(_sockfd, ret.c_str(), ret.size(), 0, (sockaddr *)&peer, len);
        }
    }
    void Init()
    {
        // 1.创建我们的sockfd
        // 前两个参数就规定了我们用的就是udp
        int n = socket(AF_INET, SOCK_DGRAM, 0);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "sock error";
            exit(2);
        }
        _sockfd = n;
        LOG(LogLevel::INFO) << "sock success";
        // 创建成功了
        // 进行绑定
        sockaddr_in local;
        // 首先进行清零
        bzero(&local, sizeof(local));
        // 填充字段
        local.sin_family = AF_INET;
        // 把本地转为网络序列
        local.sin_port = htons(_port);
        // 这里就是不显示绑定而是绑定该太机器上任意的ip地址
        local.sin_addr.s_addr = INADDR_ANY;
        int ret = bind(_sockfd, (sockaddr *)&local, sizeof(local));
        if (ret < 0)
        {
            LOG(LogLevel::FATAL) << "bind failsure";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind success";
    }

private:
    int _sockfd;
    // std::string _ip;//服务器端一台机器上可能有多个ip我们最好的就是不显示绑定我们的ip地址
    uint16_t _port;
    bool _isrunning;
    func_t1 _func;
};

//linux udpserve.cc
#include "UdpServe.hpp"
#include <memory>
#include "InetAddr.hpp"
#include "Route.hpp"
#include "PthreadPool.hpp"
#include <functional>
using funcpopl = std::function<void()>;
using namespace PthreadModlue;
std::string defaultfunc(std::string messges)
{
    return messges;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    ENABLE_CONSOLE_LOG_STRATEGY();
    uint16_t port = std::stoi(argv[1]);

    // 1.创建route对象提供吧消息转发给所有的在线用户
    Route r;
    
    // 2.创建线程池
    auto pt = PthreadPoolModule::PthreadPool<funcpopl>::GetInstance();
     pt->Start();
    std::unique_ptr<UdpServe> udpserveptr = std::make_unique<UdpServe>(port, [&r, &pt](int sockfd, std::string message, InetAddr peer){
        funcpopl fun=std::bind(&Route::RouteMessage,&r,sockfd,message,peer);
        pt->Enqueue(fun);
    });
    udpserveptr->Init();
    udpserveptr->Start();
    return 0;
}

//udpclient.cc linux
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
#include "pthread.hpp"
using namespace LogModule;
using namespace PthreadModlue;
std::string serve_ip;
int serve_port;
int sockfd;
socklen_t len;
sockaddr_in serve;
pthread_t reid;
void recive()
{
    while (true)
    {
        char buffer[1024];
        sockaddr_in peer;
        bzero(&peer, sizeof(peer));
        socklen_t peerlen = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &peerlen);
        if (m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }
}
void sends()
{
    const std::string online = "inline";
    sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&serve, sizeof(serve));

    while (true)
    {
        std::string mesage;
        std::cout << "Please Enter:" << std::endl;
        // 从标准输入流读取mesage
        getline(std::cin, mesage);
        int n = sendto(sockfd, mesage.c_str(), mesage.size(), 0, (sockaddr *)&serve, len);
        (void)n;

        if (mesage == std::string("QUIT"))
        {
            pthread_cancel(reid);
            break;
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    ENABLE_CONSOLE_LOG_STRATEGY();
    serve_ip = argv[1];
    serve_port = std::stoi(argv[2]);
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket failsure";
        exit(2);
    }
    // 客户端不用显示绑定在我们第一次发消息的时候就os会自动帮我们绑定客户端机器的ip,端口号随机生成
    bzero(&serve, sizeof(serve));
    len = sizeof(serve);
    serve.sin_port = htons(serve_port);
    serve.sin_family = AF_INET;
    serve.sin_addr.s_addr = inet_addr(serve_ip.c_str());

    Pthread p1(recive);
    Pthread p2(sends);

    p1.Start();
    p2.Start();

    reid = p1.Ip();

    p1.Join();
    p2.Join();
    return 0;
}
//window udpclient.cc
#include <iostream>
#include <string>
#include <winsock2.h>
#include <process.h>
#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable : 4996)
#define BUFFER_SIZE 1024

SOCKET sockfd;
sockaddr_in server_addr;
bool running = true;

// 接收线程函数
unsigned __stdcall RecvThread(void* param) {
    char buffer[BUFFER_SIZE];
    int server_len = sizeof(server_addr);

    while (running) {
        int n = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
            (sockaddr*)&server_addr, &server_len);
        if (n > 0) {
            buffer[n] = '\0';
            std::cout << "\n[Server] " << buffer << std::endl;
            std::cout << "Enter message: ";
        }
    }
    return 0;
}

// 发送线程函数
unsigned __stdcall SendThread(void* param) {
    const std::string online = "online";
    sendto(sockfd, online.c_str(), online.size(), 0,
        (sockaddr*)&server_addr, sizeof(server_addr));

    while (running) {
        std::string message;
        std::cout << "Enter message: ";
        std::getline(std::cin, message);

        if (message == "QUIT") {
            running = false;
            break;
        }

        sendto(sockfd, message.c_str(), message.size(), 0,
            (sockaddr*)&server_addr, sizeof(server_addr));
    }

    // 清理
    closesocket(sockfd);
    WSACleanup();
    return 0;
}

int main() {
    // 初始化Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed\n";
        return 1;
    }

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == INVALID_SOCKET) {
        std::cerr << "Socket creation failed: " << WSAGetLastError() << "\n";
        WSACleanup();
        return 1;
    }

    // 硬编码服务器信息
    const char* SERVER_IP = "116.205.122.71";
    const unsigned short SERVER_PORT = 8080;

    // 配置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    // 验证地址有效性
    if (server_addr.sin_addr.s_addr == INADDR_NONE) {
        std::cerr << "Invalid server IP format!\n";
        closesocket(sockfd);
        WSACleanup();
        return 1;
    }

    std::cout << "=== Connecting to " << SERVER_IP << ":" << SERVER_PORT << " ===\n";

    // 创建线程
    HANDLE hRecv = (HANDLE)_beginthreadex(nullptr, 0, RecvThread, nullptr, 0, nullptr);
    HANDLE hSend = (HANDLE)_beginthreadex(nullptr, 0, SendThread, nullptr, 0, nullptr);

    // 等待线程结束
    WaitForSingleObject(hRecv, INFINITE);
    WaitForSingleObject(hSend, INFINITE);

    CloseHandle(hRecv);
    CloseHandle(hSend);

    return 0;
}

效果显示:
在这里插入图片描述

补充参考内容

地址转换函数

字符串转 in_addr 的函数:
在这里插入图片描述
in_addr 转字符串的函数:
在这里插入图片描述
其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的
in6_addr,因此函数接口是 void *addrptr。
代码示例:

在这里插入图片描述

关于 inet_ntoa

inet_ntoa 这个函数返回了一个 char*, 很显然是这个函数自己在内部为我们申请了一块
内存来保存 ip 的结果. 那么是否需要调用者手动释放呢?
在这里插入图片描述
man 手册上说, inet_ntoa 函数, 是把这个返回结果放到了静态存储区. 这个时候不需要
我们手动进行释放. 那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:
在这里插入图片描述
运行结果如下:
在这里插入图片描述
因为 inet_ntoa 把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆
盖掉上一次的结果

  • 思考: 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?
  • 在 APUE 中, 明确提出 inet_ntoa 不是线程安全的函数;
  • 但是在 centos7 上测试, 并没有出现问题, 可能内部的实现加了互斥锁;
  • 在多线程环境下, 推荐使用 inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题

因此我们上面的InetAddr类就可以改写了

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include<string.h>
class InetAddr
{
public:
    //网络转主机
    InetAddr(sockaddr_in &sock)
        : _addr(sock)
    {
        //_ip=inet_ntoa(_sockaddrin.sin_addr);

        _port = ntohs(_addr.sin_port);
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
        _ip = ipbuffer;
    }
    //主机转网络
     InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)
    {
        // 主机转网络
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
        _addr.sin_port = htons(_port);
    }
    InetAddr(uint16_t port) : _port(port), _ip("0")
    {
        // 主机转网络
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_addr.s_addr = INADDR_ANY;
        _addr.sin_port = htons(_port);
    }
    ~InetAddr()
    {
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    bool operator==(const InetAddr &peer)
    {
        return _ip == peer._ip && _port == peer._port;
    }
    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    socklen_t NetAddrLen()
    {
        return sizeof(_addr);
    }
    const struct sockaddr_in &NetAddr() { return _addr; }

private:
    sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

网站公告

今日签到

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