Linux网络--UDP套接字

news2024/11/24 3:41:56


文章目录

  • 预备知识
  • socket套接字
  • UDP网络编程

一、预备知识

        1.源IP地址和目的IP地址

IP地址标识计算机在网络中的唯一性。

在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址。
源IP地址 网络通信的发起者。
目的IP地址 网络通信的接受者。

        2.端口号

端口号:可以用来标识进程的唯一性。

网络通信的目的是让两台计算机上的两个进程在进行通信。

因为两台计算机之间进行数据的发送时,发送到计算机中的是进行网络通信的手段并不是目的,而真正进行网络通信的是两台计算机上面的某个应用之间的通信。

端口号(port)是传输层协议的内容.
端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
(全网唯一进程=IP地址+端口号)
一个端口号只能被一个进程占用。

 3.认识TCP/UDP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)和UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题.

 TCP协议

传输层协议
有连接
可靠传输
面向字节流

UDP协议

传输层协议
无连接
不可靠传输
面向数据报

4.网络字节序

内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>

//主机序列转网络序列
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);

//网络序列转主机序列
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

二、socket套接字

1.socket 常见API

#include <sys/types.h>
#include <sys/socket.h>

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,
 socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
 socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
 socklen_t addrlen);

2.sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同.
套接字的种类常见的可以分为三种:
  • 域间套接字:存在于本地间通信。
  • 网络套接字:跨主机进行网络通信同时也支持本地间通信。
  • 原始套接字:可以跨越传输层(TCP/UDP)访问底层数据。

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.
  • IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
  • socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数.

sockaddr_in结构

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址

 sockaddr结构in_addr结构

 in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;

将sockaddr看成是基类,将sockaddr_in和sockaddr_un看成是派生类。

地址转换函数
基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址
但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;
字符串转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把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.

三、UDP网络编程

1.服务端实现

class UdpServer
{
public:
    

private:
    // 一个服务器,一般必须需要ip地址和port(16位的整数)
    uint16_t _port;
    std::string _ip;
    int _sock;
};

 首先构造服务器时需要传入端口号和ip地址

一般来说端口号是指定的,一旦指定之后不能更改这里为8080,ip地址为服务器的地址。

UdpServer(const uint16_t &port,const std::string& ip=""):_port(port),_ip(ip),_sock(-1)
    {}

 通过调用ipconfig指令我们可以看到此时服务器的ip信息。

 为了能够使服务器接受所有的网络请求,将服务器的ip地址绑定为INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。

 初始化服务器

    void initServer()
    {
        //1.创建套接字
        _sock=socket(AF_INET,SOCK_DGRAM,0);

        if(_sock<0){
            std::cerr<<"socket err: "<<errno<<" : "<<strerror(errno)<<std::endl;
            exit(2);
        }

        //bind绑定
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        local.sin_family=AF_INET;
        local.sin_port=htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());

        if(bind(_sock,(struct sockaddr*)&local,sizeof(local))<0)
        {
            std::cerr<<"bind error: "<<errno<<" : "<<strerror(errno)<<std::endl;
            exit(2);
        }
    }

启动服务器

 void Start()
    {
        char buffer[SIZE];
        while(true){
            struct sockaddr_in peer;
            bzero(&peer,sizeof(peer));
            socklen_t len=sizeof(peer);

            ssize_t s=recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&(peer),&len);//阻塞读取
            if(s>0){
                buffer[s]=0;
                
                std::string cli_ip=inet_ntoa(peer.sin_addr);
                uint16_t cli_port=ntohs(peer.sin_port);

                std::cout<<cli_ip<<"["<<cli_port<<"]#"<<buffer<<std::endl;
            }

            sendto(_sock,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);
        }
    }

利用recvfrom读取网络中的数据。

 udp_server.cc

#include "udp_server.hpp"
#include <memory>
#include <cstdlib>

static void usage(std::string proc)//使用手册
{
    std::cout<<"\nUsage: "<<proc<<"port\n"<<std::endl;
}

int main(int argc,char*argv[])
{
    if(argc!=2){//命令行参数
        usage(argv[0]);
        exit(1);
    }

    uint16_t port=atoi(argv[1]);
    std::unique_ptr<UdpServer> svr(new UdpServer(port));//利用智能指针创建服务器对象
    svr->initServer();
    svr->Start();
    return 0;
}
此时启动服务器,利用netstat -nuap来查看进程

 2.客户端实现

class UdpClient
{
private:
        uint16_t _port;
        std::string _ip;
        int sock;
};

客户端可以有无数个但是服务端只有一个,所以对于客户端来说只需要知道服务端的ip和端口号。

初始客户端

void initClient()
    {
        // 1.创建套接字
        _sock = socket(AF_INET, SOCK_DGRAM, 0);

        if (_sock < 0)
        {
            std::cerr << "socket err: " << errno << " : " << strerror(errno) << std::endl;
            exit(2);
        }

        std::cout << "socket success: " << std::endl;
        // 客户端不需要显示的绑定OS会随机分配
    }

对于客户端的初始化不需要创建绑定ip和端口号,因为没有用户会对客户端进行网络请求,所以OS会随机分配
客户端启动

 void run()
    {
        //填充服务器的ip
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_port = htons(_port);
        server.sin_addr.s_addr = inet_addr(_ip.c_str());

        char buffer[1024];
        std::string message;
        while (true)
        {
            std::cout << "请输入你的信息# ";
            std::getline(std::cin, message);

            //将数据发送到网络中
            sendto(_sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof server);

            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            ssize_t s = recvfrom(_sock, buffer, sizeof buffer, 0, (struct sockaddr *)&temp, &len);
            if (s > 0)
            {
                buffer[s] = 0;
                std::cout << "server echo# " << buffer << std::endl;
            }
        }
    }

利用sendto向网络中发送数据

 udp_client.cc

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

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
              << std::endl;
}

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

    std::string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    std::unique_ptr<UdpClient> ucli(new UdpClient(serverport,serverip));
    ucli->initClient();
    ucli->run();
    return 0;
}

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

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

相关文章

element ui 上传控件携带参数到后端

1.携带固定参数&#xff1a; 2.携带不固定参数&#xff1a; <el-row> <el-col :span"24"> <el-upload :multiple"false" :show-file-list"false" :on-success"f_h…

矿井人员视频行为分析算法 opencv

矿井人员视频行为分析算法通过opencvpython网络模型技术&#xff0c;矿井人员视频行为分析算法实时监测人员的作业行为&#xff0c;并与安全标准进行比对&#xff0c;可以及时发现不符合安全要求的行为&#xff0c;预防事故的发生。OpenCV的全称是Open Source Computer Vision …

json-server Node.js 服务,前端模拟后端提供json接口服务

json-server Node.js 服务,前端模拟后端提供json接口服务 背景&#xff1a; 前后端分离的项目&#xff0c;如果前端写页面的话&#xff0c;必须的后端提供接口文件&#xff0c;作为前端等待时间太久&#xff0c;不便于开发进行&#xff0c;如果前端写的过程中自己搭建一个简要的…

vue3+taro+Nutui 开发小程序(一)

前言&#xff1a;最近在调研开发小程序&#xff0c;发现现在taro框架逐渐成熟&#xff0c;能完美地使用vue3来进行开发&#xff0c;调研中发现京东的Nutui也不错所以准备写一个由0到1的vue3taroNutui的小程序。 这篇我们首先搭建一个框架&#xff1a; vscode插件准备环节&…

【C++详解】——异常

目录 C语言传统的处理错误的方式 C异常概念 异常的使用 异常的抛出和捕获 异常的重新抛出 异常安全 异常规范 自定义异常体系 C标准库的异常体系 异常的优缺点 C语言传统的处理错误的方式 传统的错误处理机制 终止程序&#xff0c;如assert。缺陷&#xff1a;用户难…

国产化测试工具的特色有哪些?

在软件开发和系统运维过程中&#xff0c;测试工具的选择和应用对于确保软件质量和系统稳定性至关重要。随着我国信息技术的快速发展&#xff0c;国产化测试工具以其独特的特色在市场上崭露头角。那国产化测试工具的特色有哪些&#xff1f; 一、技术创新&#xff1a; 适应多样化…

在英特尔 CPU 上微调 Stable Diffusion 模型

扩散模型能够根据文本提示生成逼真的图像&#xff0c;这种能力促进了生成式人工智能的普及。人们已经开始把这些模型用在包括数据合成及内容创建在内的多个应用领域。Hugging Face Hub 包含超过 5 千个预训练的文生图 模型。这些模型与 Diffusers 库 结合使用&#xff0c;使得构…

【Qt】安装Qt 5.7.1 MSVC2013 64bit版本的说明

【Qt】安装Qt 5.7.1 MSVC2013 64bit版本的说明 1、背景2、安装Qt 5.7.13、运行Qt Creator 1、背景 刚开始Qt是C库&#xff0c;后来Qt发展就越来越强大了。后来Qt 发展成为一套跨平台C图形用户界面应用程序开发框架。 注意它不但可以开发GUI程序&#xff0c;而且也可用于开发非…

【数据架构】Data Fabric 架构是实现数据管理和集成现代化的关键

D&A 领导者应该了解数据编织架构的关键支柱&#xff0c;以实现机器支持的数据集成。 在日益多样化、分布式和复杂的环境中&#xff0c;数据管理敏捷性已成为组织的任务关键优先事项。为了减少人为错误和总体成本&#xff0c;数据和分析 (D&A) 领导者需要超越传统的数据…

LangChain+LLM大模型问答能力搭建与思考

1. 背景 最近&#xff0c;大模型&#xff08;LLMs&#xff0c;Large Language Models&#xff09;可谓是NLP领域&#xff0c;甚至整个科技领域最火热的技术了。凑巧的是&#xff0c;我本人恰好就是NLP算法工程师&#xff0c;面临着被LLMs浪潮淘汰的窘境&#xff0c;决定在焦虑…

【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7

【趟坑记录】d3.zoom()的正确使用姿势 d3.v7 文章目录 【趟坑记录】d3.zoom()的正确使用姿势 d3.v7问题重现原因分析解决方案放缩平移写法特殊修改transform函数的写法 总结 在开发一个D3应用的时候遇到了一个 zoom相关的问题&#xff0c;记录解决思路与方案 问题重现 最近在…

nodejs+vue+elementui学习交流和学习笔记分享系统

Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台。 前端技术&#xff1a;nodejsvueelementui,视图层其实质就是vue页面&#xff0c;通过编写vue页面从而展示在浏览器中&#xff0c;编写完成的vue页面要能够和控制器类进行交互&#xff0c;从而使得用户在点击网页进…

【Hello mysql】 mysql的事务

Mysql专栏&#xff1a;Mysql 本篇博客简介&#xff1a;介绍mysql的事务 mysql的事务 事务的概念事务功能测试事务的隔离级别如何理解隔离性&#xff08;粗浅理解&#xff09;隔离级别查看和设置隔离级别四种隔离级别详解读 -- 未提交读 - 提交可重复读串行化一致性的理解 总结 …

前端学习——ajax (Day4)

同步代码和异步代码 回调函数地狱和 Promise 链式调用 回调函数地狱 Promise - 链式调用 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge&quo…

python识别极验4滑块验证码实战

闲得无聊&#xff0c;趁着休息研究了一下极验4滑块验证码的安全性&#xff0c;是否有机器识别、自动化拖拽的可能性。首先看一下效果 如何识别验证码 1、下载图片 下载图片可以参考博客《采集极验4滑块验证码图片数据》 2、标记图片 3、标记滑动距离 实现代码 __author__ &…

算法与数据结构(二十一)前缀和数组差分数组

前缀和技巧适用于快速、频繁地计算一个索引区间内的元素之和。 1. 一维数组中的前缀和 先看一道例题&#xff0c;力扣第 303 题「区域和检索 - 数组不可变」&#xff0c;让你计算数组区间内元素的和&#xff0c;这是一道标准的前缀和问题&#xff1a; 题目要求你实现这样一个…

多线程(JavaEE初阶系列2)

目录 前言&#xff1a; 1.什么是线程 2.为什么要有线程 3.进程与线程的区别与联系 4.Java的线程和操作系统线程的关系 5.多线程编程示例 6.创建线程 6.1继承Thread类 6.2实现Runnable接口 6.3继承Thread&#xff0c;使用匿名内部类 6.4实现Runnable接口&#xff0c;使…

nodejs+vue+elementui学生选课系统awwm9

前端技术&#xff1a;nodejsvueelementui,视图层其实质就是vue页面&#xff0c;通过编写vue页面从而展示在浏览器中&#xff0c;编写完成的vue页面要能够和控制器类进行交互&#xff0c;从而使得用户在点击网页进行操作时能够正常。 可以设置中间件来响应 HTTP 请求。 (3) 教…

详细总结Webpack5的配置和使用

打包工具 使用框架&#xff08;React、Vue&#xff09;&#xff0c;ES6 模块化语法&#xff0c;Less/Sass 等 CSS预处理器等语法进行开发的代码要想在浏览器运行必须经过编译成浏览器能识别的 JS、CSS 等语法&#xff0c;才能运行。 所以需要打包工具帮我们做完这些事。除此之…

微信小程序学习笔记(三)——视图与逻辑

页面导航 什么是页面导航 页面导航指的是页面之间的相互跳转。例如&#xff0c;浏览器中实现页面导航的方式有如下两种&#xff1a; <a> 链接location.href 小程序中实现页面导航的两种方式 声明式导航、 在页面上声明一个 <navigator> 导航组件通过点击 <…