[Linux]套接字编程--UDP 通信

104 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

IP 地址和端口号

IP 地址可以确定网络中的一台主机,端口号可以确定一台主机中的进程。端口号和进程 PID 不同的是,并不是所有进程都要对外提供网络请求,端口号是网络级别的概念,PID 是系统概念。

网络字节序

发送主机通常将发送缓冲区的数据按照从低到高的顺序发出,而接收主机把从网络上接收到的字节依次保存在接收缓冲区中,也是按照从低到高的顺序保存。但是不同主机的字节序可能不同,这就造成数据读取错误。为了解决这个问题,网络协议对网络数据流的字节序做了统一规定。但是在发送 IP 地址和端口号时,仍需要手动转换。

套接字 API

创建套接字 int socket(int domain, int type, int protocol)

所需头文件:

  • <sys/types.h>
  • <sys/socket.h> 参数说明:
  • domain 协议,如 IPV4,IPV6,本地通信
  • type 传输层提供服务类型:TCP、UDP
  • protocol 如果为 0,将自动推导

本质上,创建套接字和创建文件相同,因此还需要将文件和网络进行关联(绑定)。

绑定套接字 int bind(int fd, const sockaddr* addr, socklen_t len)

所需头文件:

  • <sys/types.h>
  • <sys/socket.h> 参数说明:
  • fd 文件描述符
  • addr 网络相关信息数据,包含 16 位端口号,32 位 IP 地址
  • len a网络相关信息的长度 绑定套接字本质上就是将网络相关信息填充到套接字,将文件与网络 IP 和端口号相关联。

服务器程序绑定端口的原因:服务器程序是为了提供服务,需要其他人知道服务的 IP 和端口,并且 bind 之后,不能轻易改变。

发送数据 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)

所需头文件:

  • <sys/socket.h>
  • <sys/types.h> 函数功能为往接收端发送大小为 len 的消息 buf,函数返回实际发送的数据大小。发送时,需要设置接收方的 IP 和端口。

接收数据 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)

所需头文件:

  • <sys/types.h>
  • <sys/socket.h>

函数功能为接收大小为 len 的消息并存储到 buf,函数返回实际接收到数据的大小。接收时,无需设置发送方的 IP 和端口。

实现 UDP 通信

// Server
lass UdpServer
{
private:
    std::string ip;
    int port;
    int sockfd;
public:
    UdpServer(std::string _ip, int _port = DEFAULT):ip(_ip),port(_port),sockfd(-1)
    {}
    bool InitUdpServer()
    {
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0) 
        {
            std::cerr << "socket error" << std::endl;
            return false;
        }
        std::cout << "socket create success, sockfd: " << sockfd << std::endl;

        struct sockaddr_in local;
        memset(&local, '\0', sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        // local.sin_addr.s_addr = INADDR_ANY;
        local.sin_addr.s_addr = inet_addr(ip.c_str());
        

        if (bind(sockfd, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            std::cerr << "bind error" << std::endl;
            return false;
        }
        std::cout << "bind suceess" <<std::endl;
        return true;
    }

    void Start()
    {
        for (;;)
        {
    # define SIZE 128
            char buf[SIZE] = {0};
            struct sockaddr_in peer;
            socklen_t len;
            ssize_t size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer, &len);
            if (size > 0)
            {
                buf[size] = 0;
                int port = ntohs(peer.sin_port);
                std::string _ip = inet_ntoa(peer.sin_addr);
                std::cout <<_ip << ":" << port << "#" << buf << std::endl;

                std::string echo_msg = "server get!->";
                echo_msg += buf;
                sendto(sockfd, echo_msg.c_str(), echo_msg.size(), 0, (struct sockaddr*)&peer, len);
            }
            else
            {
                std::cerr << "recvfrom error" << std::endl;
            }
        }
    }
    ~UdpServer()
    {
        if (sockfd >= 0) close(sockfd);
    }
};
// Client
class UdpClient
{
private:
    int sockfd;
    std::string server_ip;
    int server_port;
public:
    UdpClient(std::string _ip, int _port)
        :server_ip(_ip), server_port(_port)
    {}
    bool InitClient()
    {
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return false;
        }
        return true;
    }
    void Start()
    {
        std::string msg;
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
        peer.sin_port = htons(server_port);

        for(;;)
        {
            std::cout << "Please enter#";
            std::cin >> msg;
            sendto(sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));

            char buf[128];
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            ssize_t size = recvfrom(sockfd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&temp, &len);
            if (size > 0)
            {
                buf[size] = '\0';
                std::cout << buf << std::endl;
            }
        }
    }

    ~UdpClient()
    {
        if (sockfd >= 0) close(sockfd);
    }
};

通过在本机进行 UDP 通信实验,实现结果如下。 image.png