Linux网络:使用UDP实现网络通信(服务端&&客户端)

文章目录

      • [1. UDP网络程序的服务端](#1. UDP网络程序的服务端)
        • [1.1 如何在UDP中读取数据](#1.1 如何在UDP中读取数据)
        • [1.2 如何在UDP中发送数据](#1.2 如何在UDP中发送数据)
        • [1.3 谈谈IP地址和Port端口号](#1.3 谈谈IP地址和Port端口号)
      • [2. UDP网络程序的客户端](#2. UDP网络程序的客户端)
        • [2.1 创建UDP套接字](#2.1 创建UDP套接字)
        • [2.2 绑定套接字](#2.2 绑定套接字)
        • [2.3 获取服务端信息](#2.3 获取服务端信息)
        • [2.4 读取和发送数据](#2.4 读取和发送数据)
  • 序:在上以章中,我们对使用UDP实现网络通信的服务端部分实现了套接字的创建与绑定,深入了解了UDP是面向数据报的,以及了解到在绑定过程对网络接口统一抽象化的struct sockaddr结构体,深入了解了##的作用,而本篇文章将继续了解使用UDP实现网络通信的服务端和客户端细节,深入探寻服务端与客户端的接收和发送。

1. UDP网络程序的服务端

1.1 如何在UDP中读取数据

UDP特殊就特殊在没法使用read和write,这两个接口是面向字节流的,而UDP是面向数据报的,所以UDP要获取数据就要使用recvfrom函数接口
recvfrom函数接口:

复制代码
需要包含下面两个库
#include<sys/types.h>
#include<sys/socket.h>

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

该函数的第一个参数表示从指定的套接字中获取数据
第二个参数表示读到的报文的时候所需要的缓冲区,第三个参数表示该缓冲区的长度,这样读到数据后,该数据就会被放在该缓冲区当中
第四个参数设为0,默认使用阻塞方式,当然该参数不止能设置为0,还能设置为其他数字,但在该片文章的讨论中,默认设置为0

问题一:收到一条消息,需不需要知道这条消息是谁发的?为什么要知道?

因为我们收到对应的信息的时候,基本上都需要返回消息给发送者
所以,要知道是谁给服务器发的信息就看看第五个和第六个参数,这两个参数是输出型参数,其中第六个参数是输入输出参数,表示该结构体对象的大小,也就是说,如果有人访问该服务器,那么此时是谁给服务器发的消息,发送端的套接字信息就会保存到第五个参数的指针中,所以,该内存空间是需要我们自己去定义的,因为这是一个输出型参数,但由于这个指针的类型是struct sockaddr,但是我们在进行udp网络通信时,用到的是struct sockaddr_in,所以我们要定义一个sockaddr_in的结构体,然后强转为sockaddr后再传进去,因为网络接口统一抽象化!!!
返回值RETURN_VALUE,如果成功了就会返回收到了多少个字节,失败的话,-1被返回,错误码被设置

复制代码
class UdpServer{
public:
    void Run(func_t func)
    {
	    _is_running = true;
        char inbuffer[size];
        while(_is_running)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            ssize_t n = recvfrom(_sockfd, inbuffer,sizeof(inbuffer) - 1, 0, 
            (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                log(Warning, "recvfrom error,errno: %d ,strerror: %s", errno, strerror(errno));
                continue;
            }
            inbuffer[n]=0;
        }
    }
    
private:
	int _sockfd; //网络文件描述符
	std::string _ip; //IP地址, 0.0.0.0 任意地址绑定
    uint16_t _port;  //表示服务器进程的端口号
    bool _is_running;//表示服务器是否在运行
};
1.2 如何在UDP中发送数据

由于UDP是面向数据报,无法使用read和write,所以UDP要发送数据就要使用sendto函数接口
sendto函数接口:


sendto函数的接口和recvfrom一模一样,只要搞懂了recvfrom就会用sendto,要注意的事,该函数的最后两个参数是输入型参数,由于在recvfrom的时候,我们就知道了是谁发数据给我们的

复制代码
class UdpServer{
public:
    void Run(func_t func)
    {
	    _is_running = true;
        char inbuffer[size];
        while(_is_running)
        {
            //读数据
            //......
            inbuffer[n]=0;
            // std::cout<<n<<":"<<inbuffer<<std::endl;
            //充当一次数据处理
            std::string info = inbuffer;
            std::string echo_string = func(info); 
            // std::cout<<echo_string<<std::endl;
            sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&client, len);
        }
    }
    
private:
	int _sockfd; //网络文件描述符
	std::string _ip; //IP地址, 0.0.0.0 任意地址绑定
    uint16_t _port;  //表示服务器进程的端口号
    bool _is_running;//表示服务器是否在运行
};

至此我们已经实现了UDP网络通信的服务端的收和发的基本功能。

问题二:如何看我们的服务器成功跑起来了呢?

复制代码
netstat -naup//使用该命令就能查看

其中n表示,能显示出数字的数据全部显示成数字,不带n的话,有些数据就会显示为字符串
其中p表示,将对应的pid进程信息页表示出来
其中a表示all,将所有相关的信息列出来
其中u表示udp信息
Proto表示用的协议是什么
Recv-Q表示收到的报文的个数
Send-Q表示发出的报文的个数
Local Address本地的地址(IP地址+端口号)
Foreign Address表示远端(只要有这个0.0.0.0或者 * 的就表示,当前主机能接受任何客户端给当前主机发的消息)
State表示状态

从上图我们看到,我们程序已经启动了

1.3 谈谈IP地址和Port端口号

关于IP地址:

公网IP绑定问题

当我们将自己的云服务器的公网IP传进去后,就会报错:

这段代码如果是虚拟机_运行,代码就是可以运行的,但是由于云服务器禁止直接bind公网IP,所以就会报错,一般写服务器的时候,_ 不会直接绑定IP地址,有可能一台主机上有两到三个网卡,有两到三个IP,如果绑定了某个IP,就无法获取到发送到另外两个IP的数据,所以一般bind(IP:0),直接绑定0,即凡是发给我这台主机的数据,我们都要根据端口号向上交付(好处:1. 好写2. 收到的数据是全的,完整的)

关于Port端口号:

当我们将端口号改为80时:

[0,1023]:系统内定的端口号,一般都要有固定的应用层协议使用,http:80,https:443,mysql:3306... ,一般选择绑定的端口是1024+的端口号
服务端的端口号一般是固定,但是客户端的端口号就是系统自由随机分配的,只要保证唯一性就行
127.0.0.1:本地环回地址,通常用来进行客户端,服务端的测试

2. UDP网络程序的客户端

2.1 创建UDP套接字

既然要实现网络通信,就必须先创建套接字

复制代码
int main()
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd < 0) 
    {
        log(Fatal, "create socket error,errno: %d ,strerror: %s", errno, strerror(errno));
        return 1;
    }
    log(Info,"create socket success, sockfd: %d",sockfd);
    return 0;
}
2.2 绑定套接字

client要bind吗?肯定是要的,但是不需要用户去显式的bind!!!一般由操作系统自由随机选择
一个端口号只能被一个进程bind,对server是如此,对client,也是如此!!!
其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以了
系统什么时候给我bind?首次发送数据的时候

2.3 获取服务端信息

想要获取服务端的信息,可以通过命令行参数来获取,规定好格式
将对应的数据填充进struct sockaddr_in server中

复制代码
// ./udpclient serverip serverport
void Usage(const string &proc)
{
    cout<<"\n\rUsage:"<< proc<<" serverip serverport\n"<<endl;
    return ;
}

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }

    string serverip =argv[1];
    uint16_t serverport =stoi(argv[2]);
    
    struct sockaddr_in server;
    socklen_t len = sizeof(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());
    
    return 0;
}
2.4 读取和发送数据

发什么,给谁发?

复制代码
int main(int argc,char* argv[])
{
    //......
     string message;
    char buffer[size];
    while(true)
    {
        cout<<"Please Enter# ";
        getline(cin,message);

        //1.数据 2.发给谁
        ssize_t s = sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,len);
       
        struct sockaddr_in temp;
        //bzero(&server, sizeof(temp));
        socklen_t lenp = sizeof(temp);
        ssize_t n = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&lenp);

        if(n > 0)
        {
            buffer[n]=0;
            cout<<buffer<<endl;
        }    
    }
    close(sockfd);
    return 0;
}

总结:

本篇博客详细介绍了UDP网络编程的核心内容,包括服务端和客户端的实现。服务端重点讲解了如何使用recvfrom接收数据和sendto发送数据,以及如何处理客户端地址信息;客户端则介绍了套接字创建、隐式绑定、服务端信息配置及数据收发流程。此外,还探讨了IP与端口相关知识,如公网IP绑定限制、知名端口范围等。

相关推荐
半桔4 小时前
【网络编程】TCP 粘包处理:手动序列化反序列化与报头封装的完整方案
linux·网络·c++·网络协议·tcp/ip
ZeroNews内网穿透4 小时前
新版发布!“零讯”微信小程序版本更新
运维·服务器·网络·python·安全·微信小程序·小程序
<但凡.5 小时前
Linux 修炼:进程控制(一)
linux·运维·服务器·bash
✎﹏赤子·墨筱晗♪6 小时前
Ansible Playbook 入门指南:从基础到实战
linux·服务器·ansible
乌萨奇也要立志学C++7 小时前
【Linux】进程概念(六):进程地址空间深度解析:虚拟地址与内存管理的奥秘
linux·运维
月殇_木言11 小时前
Linux 线程
linux
wangjialelele11 小时前
Linux中的线程
java·linux·jvm·c++
2301_8000509913 小时前
DNS 服务器
linux·运维·笔记
Lin_Aries_042113 小时前
容器化简单的 Java 应用程序
java·linux·运维·开发语言·docker·容器·rpc