UDP网络通信

UDP网络通信:

步骤1 创建套接字:

cpp 复制代码
#include <sys/types.h>        
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

参数一 domain:

AF_UNIX Local communication unix(7) 本地通信

AF_INET IPv4 Internet protocols ip(7) 网络通信

参数二 type:

SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). 无连接不可靠面向数据报文,也就是UDP

参数三 protocol:

一般省略,直接为0.

RETURN VALUE

On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.(成功返回一个文件描述符,失败返回-1)。

步骤2 绑定 将IP端口与套接字绑定起来:

绑定的过程,需要将IP和端口号关联起来,使用 sockaddr_in 对信息进行关联。

使用 sockaddr_in 需要头文件 #include <netinet/in.h> 与 #include <arpa/inet.h>

需要将本体服务端的 IP 和 port 填充进 sockaddr_in 的结构体中。

结构体包含三个需要填充的字段:

1.sin_family : 通信的类型。这个和套接字选择的类型要相同。

2.sin_port : 端口号,如果手动写入的端口号是16位主机序列,不符合网络序列,因此需要利用 htons(uint16_t) 将其转换为网络序列

3.sin_addr.s_addr : 网络地址。传入的肯定是一个字符串,但是网络中需要的是4字节 IP,需要的是网络序列的 IP,因此又需要转换。其转换函数为:inet_addr(string)。

完成上述的填充工作后,bind函数可以将填充好的字段和套接字关联起来。关联如下:

cpp 复制代码
#include <sys/types.h>        
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

RETURN VALUE

On success, zero is returned. On error, -1 is returned, and errno is set appropriately.(成功返回0,失败-1)

这里的第二个参数是 struct sockaddr* ,而填充的时候用的是struct sockaddr_in,因此需要强转如下。

完整代码如下:

步骤三 使用函数读写数据。

UDP不能使用 read 和 write,

读数据需要使用 recvfrom:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数分别为:fd,缓冲区和缓冲区的大小(读出的数据放在哪里,期望接受多少数据),flags设置为0表示为阻塞读取。

后两个参数为:输入型参数,这两个参数放的就是谁给我传的网络信息,放的就是对方的 IP 和 port。

返回值大于 0 读取成功。后两个参数的作用就是得到客户端是谁。

recvfrom 的返回值就是接受了多少数据。

写数据用的是 sendto:

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

后两个参数为输入型参数,分别为:

const struct sockaddr *dest_addr:代表客户端。

socklen_t addrlen:代表客户端的长度。

RETURN VALUE

On success, these calls return the number of bytes sent. On error, -1 is returned, and errno is set appropriately.(成功返回值大于0,失败-1)

客户端读数据,recvfrom的后两个参数直接给一个初始的无意义的参数给上就行。

网络转主机和主机转网络序列。

代码如下:

cpp 复制代码
//udpserver.hpp
#pragma once

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <string>
#include "nocopy.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

static const int gsockfd = -1;
static const uint16_t glocalport = 8888;
enum{
    SOCKET_ERROR = 1,
    BIND_ERROR,
};

// UdpServer user("192.1.1.1", 8888)
class UdpServer : public nocopy
{
public:
    UdpServer(const std::string& localip, uint16_t localport = glocalport)
        :_sockfd(gsockfd), 
         _localport(localport),
         _localip(localip),
         _isrunning(false)
    {}

    void InitServer()
    {
        //1 创建套接字
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            std::cout << "socket error! " << std::endl;
            exit(SOCKET_ERROR);
        }
        std::cout << "socket success, _sockfd: %d " << _sockfd << std::endl; //预计是 3

        //2 绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_localport);
        local.sin_addr.s_addr = inet_addr(_localip.c_str());

        int n = ::bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(n<0)
        {
            std::cout << "bind error! " << std::endl;
            exit(BIND_ERROR);
        }
        std::cout << "bind success" << std::endl;
    }

    void Start()
    {
        _isrunning = true;
        char inbuffer[1024];
        while(_isrunning)
        {
            struct sockaddr peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, &inbuffer, sizeof(inbuffer)-1, 0, (struct sockaddr*)&peer, &len);
            if(n > 0)
            {
                inbuffer[n] = 0;
                std::cout << "clint say# " << inbuffer << std::endl;
                std::string echo_string = "[udp_server echo] #";
                echo_string += inbuffer;

                sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, len);
            }
        }
    }


    ~UdpServer()
    {
        if(_sockfd > gsockfd) ::close(_sockfd);
    }

private:
    int _sockfd;
    uint16_t _localport;
    std::string _localip;
    bool _isrunning;
};
cpp 复制代码
//udpserver.cc
#include "UdpServer.hpp"
#include  <memory>

int main()
{
    uint16_t port = 8899;
    std::string ip = "0";
    std::unique_ptr<UdpServer> user = std::make_unique<UdpServer>(ip, port);
    user->InitServer();
    user->Start();
    return 0;
}
cpp 复制代码
//udpclient.cc


#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 客户端得知道服务器的 IP 地址和端口号才能操作
// 因此,在调用客户端的时候,可以附上服务端的IP和port。
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        std::cerr <<"Usage: " << argv[0] << "server-ip server-port " << std::endl;
        exit(0);
    }

    //拿到服务器的IP和端口
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    //1 创建套接字
    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "client socket error!"<< std::endl;
        exit(1);
    }

    //2 bind 绑定客户端的IP和端口
    // client的端口不会被用户去设定,而是OS去随机选择端口

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

    while(1)
    {
        std::string line;
        std::cout << "Please Enter: ";
        std::getline(std::cin, line);

        int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));
        if(n > 0)
        {
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            char buffer[1024];
            int m = recvfrom(sockfd, &buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len);
            if(m>0)
            {
                buffer[m] = 0;
                std::cout << buffer << std::endl;
            }
            else
            {
                std::cout << "recvfrom error" << std::endl;
                break;
            }
        }
        else
        {
            break;
        }

    }
    

    ::close(sockfd);
    return 0;
}
相关推荐
Sheffield20 小时前
Docker的跨主机服务与其对应的优缺点
linux·网络协议·docker
JaguarJack1 天前
FrankenPHP 原生支持 Windows 了
后端·php·服务端
BingoGo1 天前
FrankenPHP 原生支持 Windows 了
后端·php
JaguarJack2 天前
PHP 的异步编程 该怎么选择
后端·php·服务端
BingoGo2 天前
PHP 的异步编程 该怎么选择
后端·php
JaguarJack3 天前
为什么 PHP 闭包要加 static?
后端·php·服务端
ServBay4 天前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户962377954484 天前
CTF 伪协议
php
YuMiao5 天前
gstatic连接问题导致Google Gemini / Studio页面乱码或图标缺失问题
服务器·网络协议
BingoGo6 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php