linux【网络编程】之UDP网络程序模拟实现

news2024/9/23 7:18:20

linux【网络编程】之UDP网络程序模拟实现

  • 一、开发环境
  • 二、服务端实现
    • 2.1 接口认识
      • 2.1.1 socket创建网络通信套接字
      • 2.1.2 bind:绑定Ip和端口号
      • 2.1.3 sockaddr_in结构体
      • 2.1.4 IP地址转换函数:inet_addr、inet_ntoa
      • 2.1.5 recvfrom:读取数据
    • 2.2 头文件udpServer.hpp
    • 2.3 绑定IP和port问题
    • 2.4 源文件udpServer.cc
  • 三、客户端实现
    • 3.1 接口认识
      • 3.1.1 数据发送:sendto
    • 3.2 头文件udpClient.hpp
    • 3.3 源文件udpClient.cc
  • 四、结果展示

一、开发环境

本次实验是在腾讯云服务器上进行

二、服务端实现

做完这次实验,感受最深的就是函数接口方面的问题,我们先来介绍一下需要用到的接口。

2.1 接口认识

2.1.1 socket创建网络通信套接字


#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

参数domain:域,未来套接字是进行网络通信(AF_INET)还是本地通信(AF_UNIX, AF_LOCAL)

参数type:套接字提供服务的类型,如SOCK_STREAM:流式服务TCP策略,SOCK_DGRAM:数据报服务,UDP策略

参数protocol:缺省为0,可由前两个类型确定

返回值:失败返回-1,成功返回文件描述符

2.1.2 bind:绑定Ip和端口号

绑定端口号和ip
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数sockfd:文件描述符,也就是调用socket的返回值

参数addr:利用struct sockaddr_in强转

参数addrlen:结构体的大小

返回值:成功返回0,失败返回-1

2.1.3 sockaddr_in结构体

struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);//协议家族,对应AF_INET
    in_port_t sin_port;		//端口号,in_port_t是对port的重命名
    struct in_addr sin_addr;//IP地址,in_addr结构体里封装了一个32位整数

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };

#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family
//sa_family_t //16位整数

typedef uint16_t in_port_t;

typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

宏中的**##**是将两个字符串合并成一个新字符串,也就是将接收到的sa_prefix与family合并起来,形成了sa_prefix_family

创建结构体后要先清空数据

#include <strings.h>
void bzero(void *s, size_t n);

2.1.4 IP地址转换函数:inet_addr、inet_ntoa

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
inet_addr内部完成两件事情:1.把字符串转化成整数;2.再把整数转化成对应的网络序列**
in_addr_t inet_addr(const char *cp);
//const char*cp:点分十进制风格的IP地址

//1.网络->主机 2.uint32_t -> 点分十进制
char *inet_ntoa(struct in_addr in);

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

sockfd:特定的套接字,buf:读取到特定缓冲区,len:结构体大小
flags:读取的方式,默认为0,阻塞读取
src_addr:输入输出型参数,收到消息除了本身,还得知道是那个IP+port发过来的数据
len:大小是多少
返回-1表示失败,成功返回字节数

2.2 头文件udpServer.hpp

头文件中:构造负责初始化参数
initServer函数负责初始化服务器:1.创建套接字;2.将套接字与IP地址和端口号绑定。
start()函数服务器本质是一个死循环,在start启动的时候,通过recvfrom读取通过网络发来的数据、源ip+源端口号分别保存到缓冲区和struct sockaddr这个结构体中

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <functional>
namespace Server
{
    using namespace std;

    //默认的点分十进制IP
    static const string defaultIP="0.0.0.0";
    static const int gnum=1024;//缓冲区大小
    
    enum{USAGE_ERR=1,SOCKET_ERR,BIND_ERR};//退出码

    typedef function<void(string,uint16_t,string)> func_t;

    class udpServer
    {
    public:
        udpServer(const func_t& callback,const uint16_t &port,const string& ip=defaultIP)
            :callback_(callback),port_(port),ip_(ip),sockfd_(-1)
        {}
        void initServer()
        {
            //****创建UDP网络通信端口****
            sockfd_=socket(AF_INET,SOCK_DGRAM,0);
            if(sockfd_==-1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success: " << " : " << sockfd_ << endl;

            //******绑定port,ip****
            //服务器要明确绑定port,不能随意改变,需要程序员显示指明
            struct sockaddr_in local;
            bzero(&local,sizeof(local));//对结构体数据清0

            //填充结构体

            local.sin_family=AF_INET;
            //大小端转换,给别人发消息,端口号和IP地址也要发给对方
            local.sin_port=htons(port_);//端口号转网络

            //1.string->uint32_t  2.htonl()---->两个工作统一交给inet_addr
            local.sin_addr.s_addr=inet_addr(ip_.c_str());//ip转网络
            // local.sin_addr.s_addr=htonl(INADDR_ANY);//任意地址绑定,服务器真正写法,上面与这个选一个就行

            int n=bind(sockfd_,(struct sockaddr*)&local,sizeof(local));
            if(n==-1)
            {
                cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
        }
        void start()
        {
            char buffer[gnum];
            for(;;)
            {
                //****读取数据****

                //服务器的本质就是一个死循环
                struct sockaddr_in peer;//做输入输出型参数
                socklen_t len=sizeof(peer);//必填
                //数据获取:IP地址+端口号+数据
                ssize_t s= recvfrom(sockfd_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                if(s>0)
                {
                    buffer[s]=0;
                    //1.网络->主机 2.uint32_t -> 点分十进制
                    //string clientip=peer.sin_addr.s_addr;需要转两步,不方便 
                    string clientip=inet_ntoa(peer.sin_addr);
                    uint16_t clientport=ntohs(peer.sin_port);
                    string message=buffer;

                    cout<<clientip<<"["<<clientport<<"]#"<<message<<endl;
                    //利用回调方法处理数据,实现解耦
                    callback_(clientip,clientport,message);
                }
            }
        }

        ~udpServer(){}
    private:
        uint16_t port_;//端口号
        string ip_;//IP地址
        int sockfd_;//文件描述符
        func_t callback_;//回调
    };
}

2.3 绑定IP和port问题

  • 一般情况下,服务器不会绑定某一个确定的IP,避免因绑定一个确定的
    IP而漏掉另一个ip发过来的数据,实际情况是,将ip设置为全0,任何提交到服务器的开放端口的数据都会被该服务器处理
  • 服务器不需要绑定一个固定IP(目的ip有多个,可能是本地环回、内网IP、公网IP),只要是访问服务器上的某个开放的端口,都会把数据拿过来处理,但这并不意味着客户端不需要绑定IP
  • 这个IP是目的IP,是已经收到了数据向上交付的时候,不需要绑定IP,只需要看端口号就行了

在这里插入图片描述
本地环回:客户端与服务端在一台主机上,进行通信的时候数据贯穿协议栈流动,但不会到达物理层(出不去),仅测试
在这里插入图片描述
内网IP:真正属于这个服务器的IP,同一个品牌的服务器可以用内网ip通信
公网IP:云服务器是虚拟的,不能直接bind公网IP
虚拟机或者真正的linux可以绑定
内网IP可以被绑定

2.4 源文件udpServer.cc

服务器端进行测试的时候不需要提供IP地址

#include "udpServer.hpp"

#include <memory>
using namespace std;
using namespace Server;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

void handlerMessage(string,uint16_t,string)
{
    //对message进行处理,完成server通信与业务逻辑解耦
}

int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=atoi(argv[1]);
    std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage,port));
    usvr->initServer();
    usvr->start();

    return 0;
}

三、客户端实现

3.1 接口认识

3.1.1 数据发送:sendto

#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);
sockfd:发送给哪个套接字,
buf:要发送的消息;len:消息的字节数;flags:0,有数据就发,没数据就阻塞
dest_addr:这个结构体也是由struct sockaddr_in强转,指明向谁发,填充服务器的IP和端口
addrlen:输入型参数

3.2 头文件udpClient.hpp

在这个文件中:构造函数负责初始化变量

initClient函数负责创建套接字但是不需要程序员手动绑定端口号,OS会帮我们绑
服务端明确绑定是因为需要客户端知道,并且不能随便改变;未来是多个客户端访问一个服务端,客户端端口号是多少不重要,保证唯一性就行

run()函数负责发送数据到服务端,需要用到sendto函数,而sendto函数在首次发送数据的时候,OS发现客户端还未绑定端口号,会令sendto函数自动绑定一个端口号

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
namespace Client
{
    using namespace std;

    // //默认的点分十进制IP
    // static const string defaultIP="0.0.0.0";
    // static const int gnum=1024;
    
    enum{USAGE_ERR=1,SOCKET_ERR,BIND_ERR};//退出码

    class udpClient
    {
    public:
        udpClient(const string& serverip,const uint16_t&serverport)
            :serverip_(serverip),serverport_(serverport),sockfd_(-1),quit_(false)
        {}
        void initClient()
        {
            //****1.创建UDP网络通信端口****
            sockfd_=socket(AF_INET,SOCK_DGRAM,0);
            if(sockfd_==-1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success: " << " : " << sockfd_ << endl;

            //****Client必须要bind,但是Client不需要显示bind(不需要自己写)******/
            //客户端端口是多少不重要,只要能保证唯一性就行,让OS自己去绑
        }

        void run()
        {
            /*********发送数据***********/
            struct sockaddr_in server;
            memset(&server,0,sizeof(server));
            server.sin_family=AF_INET;
            //主机序列转网络序列
            server.sin_addr.s_addr=inet_addr(serverip_.c_str());
            server.sin_port=htons(serverport_);

            string message;
            while (!quit_) 
            {
                cout<<"Please Enter# "<<endl;
                cin>>message;
                //首次向服务器发送数据的时候,OS识别到还未绑定,sendto自动绑定ip+port
                sendto(sockfd_,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
            }
            
        }

        ~udpClient()
        {}
    private:
        int sockfd_;
        string serverip_;
        uint16_t serverport_;
        bool quit_;
    };
}

3.3 源文件udpClient.cc

客户端进行测试的时候必须要提供IP地址和端口号

#include "udpClient.hpp"
#include <memory>
using namespace std;
using namespace Client;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" <<proc << " server_ip server_port\n\n";
}

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

    //客户端必须要知道发给谁
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    unique_ptr<udpClient> ucli(new udpClient(serverip,serverport));
    ucli->initClient();
    ucli->run();

    return 0;
}

四、结果展示

在这里插入图片描述

无论是客户端还是服务端的测试文件,所要传递的参数(ip、port)与头文件中struct sockaddr_in需要填充的ip、port没有关系

这篇博客只是讲解一下UDP下网络通信的逻辑,关于它的应用方面会在近期推出,敬请关注!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/510840.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大语言模型进化树重磅发布,感慨技术方向选择的残酷,文末有彩蛋

文 / 高扬&#xff08;微信公众号&#xff1a;量子论&#xff09; 今天说点有深度的内容。五一假期&#xff0c;学习了一篇论文《Harnessing the Power of LLMs in Practice: A Survey on ChatGPT and Beyond》。 这篇论文来自 Amazon 以及 Texas A&M University 研究团队&…

【2023 · CANN训练营第一季】应用开发深入讲解——第五章 端到端案例讲解

学习资源 样例介绍 使用DVPP加速预处理网络输入&#xff0c;并通过模型转换使能静态AIPP功能&#xff0c;使能AIPP功能后&#xff0c;YUV420SP_U8格式图片转化为RGB&#xff0c;然后减均值和归一化操作&#xff0c;并将该信息固化到转换后的离线模型中&#xff0c;对ResNet50…

传统IDC服务器迁移上云流程

上云是趋势&#xff0c;越来越多企业的IDC服务器选择迁移上云&#xff0c;迁移上云的方式有很多&#xff0c;阿里云提供服务器迁移中心SMC来帮助用户迁移上云。使用SMC服务器迁移中心&#xff0c;将您的源服务器方便快捷地迁移至阿里云&#xff0c;支持的迁移源类型包括IDC服务…

【Java EE 初阶】锁策略以及CAS问题

目录 1.常见的锁策略 1.乐观锁 vs 悲观锁 2.读写锁 3.重量级锁 vs 轻量级锁 4.自旋锁&#xff08;Spin Lock&#xff09; 5.公平锁 vs 非公平锁 6.可重入锁 vs 不可重入锁 7.Synchronized实现了哪些锁策略&#xff1f; 1.是乐观锁也是悲观锁 2.既是轻量级锁也是重量级…

K_A37_005 基于STM32等单片机驱动ADS1115 ADC模块 串口与OLED0.96双显示

K_A37_005 基于STM32等单片机驱动ADS1115 ADC模块 串口与OLED0.96双显示 所有资源导航一、资源说明二、基本参数参数引脚说明 三、驱动说明IIC时序对应程序: 四、部分代码说明1、接线引脚定义1.1、STC89C52RCADS1115 ADC模块1.2、STM32F103C8T6ADS1115 ADC模块 五、基础知识学习…

解决“未在本地计算机注册“OraOLEDB.Oracle.1”提供程序“问题

由于本地使用Oracle.ManagedDataAccess批量插入问题&#xff0c;连接数据库时报错 : ProviderOraOLEDB.Oracle;Data Sourceorcl;User IdQueueDp;PasswordQueueDp 此问题之前解决过没记录&#xff0c;又遇到了&#xff0c;忘了怎么解决&#xff0c;试了很多没效果 解决办法一、…

用好git stash,工作超nice

一、介绍 如果修改后的内容还不想commit&#xff0c;就可以用git stash命令。它会将工作区和暂存区中的修改(也就是还没commit的内容)都会被保存到堆栈里&#xff0c;并在之后恢复到任意指定的分支上。 二、应用场景 1、在分支a进行开发feature 1时&#xff0c;突然需要紧急…

2、Orangepi Zero2刷机和系统启动

目录 2.1 工具安装 2.2 刷机 2.3 登录系统 2.4 修改登陆密码 2.5 网络配置 2.6 SSH登陆开发板 就像买了电脑&#xff0c;出厂带有 windows 操作系统&#xff0c;才算是正在的电脑&#xff0c;开发板需要烧写对应的系统固件&#xff0c;才 能正常发挥作用 工具 Orangepi Zero2…

【异常解决】浏览器无法访问此网站ERR_UNSAFE_PORT/网页可能无法连接,或者它已永久性地移动到了新网址问题解决方案

浏览器无法访问此网站ERR_UNSAFE_PORT问题解决方案 一、问题描述二、问题原因三、解决方案3.1 方案1修改服务器访问端口号&#xff08;推荐&#xff09;3.2 方案2修改浏览器设置3.2.1 Chrome浏览器3.2.2 Firefox浏览器3.2.3 Edge浏览器 一、问题描述 访问某一个特定的网址之后…

Flume系列:Flume Channel使用

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;Kafka Channel 2&#xff1a;File Channel 3&#xff1a;Memory Channel Apache Hadoop生态-目录汇总-持续更新 系统环境&#xff1a;centos7 Java环境&#xff1a;Java8 1&#xff1a;Kafka Channel Kafka Chan…

vim键位图+vim基本操作命令表

前言&#xff1a;本章末vim的基本操作命令总结的比较全&#xff0c;建议收藏起来&#xff0c;方便后面使用时作字典查找。 目录 一、什么是vi(vim)&#xff1f; vim键盘 二、vim工作模式 1&#xff09;命令模式 2&#xff09;编辑模式 3&#xff09;末行模式 4&#xf…

「2024」预备研究生mem-等差数列基础

一、等差数列基础 二、课后练习题 思路&#xff0c;知道a1和d &#xff0c;就可以知道an,sn

Kafka 集成 SpringBoot, 快速入门

一、kafka的生产者和消费者 1. 生产者发送消息的流程 2. 消费者接收消息的流程 二、 java 代码实现 1. 添加依赖&#xff1a; <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka_2.12</artifactId></dependency>2. 实现…

回溯算法例题(剪枝策略)

目录 1.组合1.77. 组合2.216. 组合总和 III3.17. 电话号码的字母组合4.39. 组合总和5.40. 组合总和 II 2.分割1.131. 分割回文串2.*93. 复原 IP 地址 3.子集1.78. 子集2.90. 子集 II 4.排列1.46. 全排列2.47. 全排列 II 5.棋盘问题1.51. N 皇后2.37. 解数独 6.其他1.491. 递增子…

系统移植 5-10

1.进入linux内核源码目录下&#xff0c;打开Makefile文件&#xff0c;搜索vmlinux&#xff0c;找到cmd_link-vmlinux命令&#xff0c; 1179 cmd_link-vmlinux \ 1180 $(CONFIG_SHELL) $< "$(LD)" "…

不同应用场景瑞芯微RK3568主板方案定制

随着物联网和智能设备的迅猛发展&#xff0c;瑞芯微RK3568主板方案作为一种高性能的系统System-on-a-chip&#xff08;SoC&#xff09;&#xff0c;已经成为嵌入式系统、智能家居设备和工业自动化设备等应用场景的首选方案。定制瑞芯微RK3568主板方案可以满足不同应用场景的需求…

科技云报道:ChatGPT应用爆火,安全的大数据底座何处寻?

科技云报道原创。 毫无疑问&#xff0c;AIGC正在给人类社会带来一场深刻的变革。 而剥开其令人眼花缭乱的华丽外表&#xff0c;运行的核心离不开海量的数据支持。 ChatGPT的“入侵”已经引起了各行各业对内容抄袭的担忧&#xff0c;以及网络数据安全意识的提高。 虽然AI技术…

线程的原子性、可见性、有序性及线程安全知识整理

要想保证线程安全&#xff0c;必须同时满足原子性、可见性、有序性。 一、定义 1.1 原子性 一个操作或者多个操作&#xff0c;要么全部执行&#xff0c;并且执行的过程不会被打断&#xff0c; 要么就全部不执行&#xff08;一个操作是不可被分割的&#xff09;。 Java中实现…

JavaScript经典教程(七)-- JavaScript初级

190&#xff1a;JavaScript初级内容 - DOM查询、插入内容、赋予样式等 1、DOM操作 DOM&#xff1a;节点&#xff0c;也就是html中的元素&#xff1b; DOM操作&#xff1a;其实就是节点元素的方法&#xff1b; &#xff08;1&#xff09;innerHTML - 返回元素内容 同时也可以…

【JUC基础】05. Synchronized和ReentrantLock

1、前言 前面两篇中分别讲了Synchronized和ReentrantLock。两种方式都能实现同步锁&#xff0c;且也都能解决多线程的并发问题。那么这两个有什么区别呢&#xff1f; 这个也是一个高频的面经题。 2、相同点 2.1、都是可重入锁 什么是可重入锁&#xff1f; 可重入锁&#xff0…