【网络】udp_socket编程

news2024/11/16 21:57:21

目录

1.认识端口号

1.1 理解端口号和进程ID

1.2 理解源端口号和目的端口号

2.认识TCP协议

3.认识UDP协议

4.网络字节序

5.socket编程接口

5.1socket常见API

5.2sockaddr结构

sockaddr结构

sockaddr_in 结构

in_addr结构

6.简单的UDP网络程序

6.1创建套接字

6.2 绑定网络信息 指明IP+prot

6.3 udpServe代码

6.3.1 udpServe通用服务器

6.3.2 实现简单通信的服务器

6.4 udpClient代码

6.5本地测试


在上一讲中我们知道了网络传输的基本流程,本节我们要更加深刻的理解一下两台主机之间交互的本质。

我们在网络通信的时候,只要让两台主机能够通信就可以了吗??

实际上,在进行通信的时候不仅仅要考虑两台主机间相互交互数据!!本质上将,进行数据交互的时候是用户和用户在进行交互用户的身份,通常是用程序体现的!!程序一定是在运行中 --> 进程!!

因此主机间通信的本质是:在各自的主机上的两个进程在互相交互数据!!也就是进程间通信

IP地址可以完成主机和主机的通信,而主机上各自的通信进程才是发送和接受数据的一方。

因此我们用IP--确保主机的唯一性,再加端口号(prot)来确保该主机上的进程的唯一性.

IP:PORT = 标识互联网中唯一的一个进程!!

因此我们把IP:PORT叫做socket(套接字),因此网络通信的本质:也就是进程间通信!!

我们通常把本地的进程间通信称作SystemV进程间通信。

网络间的进程间通信用的是process,Socket.

1.认识端口号

我们刚说过端口号PROT是用来确保主机上进程的唯一性:他要告诉主机以后把消息交给哪一个进程。

端口号(port)是传输层协议的内容:

  • 端口号是一个2字节16位的整数
  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
  • IP地址+端口号 能够标识网络上的某一台主机的某一个进程
  • 一个端口号只能被一个进程占用

1.1 理解端口号和进程ID

我们之前在学习进程的时候,学习到过pid是用来标识进程的,那么此处的端口号也是唯一表示一个进程的。那么这两者又有什么关系呢?

端口号是个数字,标定进程唯一性,更加是一种证明,证明这个进程要进行网络通信,没有端口号,这个进程可能只是本地进程。因此端口号和进程ID的差别就是这样。

因此一个进程可以绑定多个端口号,但是一个端口号只能绑定一个进程!!

1.2 理解源端口号和目的端口号

源端口号和目的端口号是传输层协议(tcp和udp)的数据段中有两个端口号,就是在描述“数据是谁发的,要发给谁”。

 

2.认识TCP协议

此处我们先对TCP(Transmission Control Protocolc 传输控制协议)有一个直观的认识,后面我们再详细讨论TCP的一些细节

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

有连接:类似于打电话,两者要建立连接

可靠传输:仍然是打电话,我说的话你都听到了,你都收到了。

3.认识UDP协议

UDP(User Datagram Protocol 用户数据报协议)

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

无连接:类似于发邮箱,不管你在不在,我都可以给你发邮件。

不可靠传输:仍然是发邮件,我发送给你,你看不看是你的事情。

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

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

5.socket编程接口

5.1socket常见API

// 创建 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);

我们发现很多函数都有一个sockaddr结构,那么这是一个什么结构呢?

5.2sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6,以及UNIX Domain Socket(域间套接字 -- 不跨网络),然后各种网络协议的地址格式并不相同。

因此我们只要拿到sockaddr结构我们强转成sockaddr类型,读取前16位,如果是AF_INET则是跨网络通信,如果是AF_UNIX则是域间通信。因此我们这里都是网络通信,因此前16位都是AF_INET.

  • 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结构

sockaddr_in 结构

 

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

in_addr结构

 

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

6.简单的UDP网络程序

上面的准备工作结束,我们可以实现一个简单的英译汉的功能

6.1创建套接字

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

返回值:如果成功,返回一个新的文件描述符。失败返回-1。

6.2 绑定网络信息 指明IP+prot

 

6.3 udpServe代码

6.3.1 udpServe通用服务器

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

static void Usage(const std::string proc)
{
    std::cout<<"Usage:\n\t"<<proc<<"port [ip]" <<std::endl;
}


//udp服务器
class UdpServer
{
public:
    UdpServer(int port,std::string ip="")
    :port_((uint16_t)port)
    ,ip_(ip)
    ,sockfd_(-1)
    {}

    ~UdpServer(){}

public:
    void init()
    {
        //1.创建socket套接字
        sockfd_ = socket(AF_INET,SOCK_DGRAM,0);//就是打开了一个文件
        // 无连接的  不可靠的
        if(sockfd_ < 0)
        {
            logMessage(FATAL,"socket:%s:%d",strerror(errno),sockfd_);
            exit(1);
        }
        logMessage(DEBUG,"socket create success : %d",sockfd_);

        // 2.绑定网络信息 指明IP+prot
        // 2.1 先填充基本信息到struct sockaddr_in
        struct sockaddr_in local;
        bzero(&local,sizeof(local));//memset
        local.sin_family = AF_INET;//协议家族 前2个字节 16位 域的概念
        //填充服务器的端口号信息,是会发给对方的port_一定会到网络中
        local.sin_port = htons(port_);
        // 服务器都必须具有IP地址,
        //"aaa.zzz.yy.xx" 字符串风格的点分十进制->4字节IP->uint32_t ip
        // INADDR_ANY(0) 程序员不关心会bind到哪一个ip,任意地址bind,强烈推荐的做法
        // inet_addr:指定填充确定的ip,特殊用途,测试时使用;除了做转换,还会自动给我做序列转换
        local.sin_addr.s_addr = ip_.empty()?htonl(INADDR_ANY):inet_addr(ip_.c_str());
        // 2.2 bind网络信息
        if(bind(sockfd_,(const struct sockaddr*)&local,sizeof(local)) == -1)
        {
            logMessage(FATAL,"bind:%s:%d",strerror(errno),sockfd_);
            exit(2);
        }
        logMessage(DEBUG,"socket bind success : %d",sockfd_);
        // done 
    }

    void start()
    {
        //服务器设计的时候都是死循环
        //将来读取到的数据都放在这里
        char inbuffer[1024];
        //将来发送的数据都放在这里
        char outbuffer[1024];


        while(true)
        {
            // struct sockaddr_in peer;//远端 输出型参数
            // socklen_t len = sizeof(peer);//输入输出型参数
            // //demo 2
            // //后面的两个参数是输出型参数,谁给你发的消息 和客户端多长
            // ssize_t s = recvfrom(sockfd_,inbuffer,sizeof(inbuffer)-1,0,
            //     (struct sockaddr*)&peer,&len);
            
            logMessage(NOTICE,"serve 提供 service中...");
            sleep(1);
        }
    }
private:
    //服务器端口号信息
    uint16_t port_;
    //服务器必须得有ip地址
    std::string ip_;
    int sockfd_;//套接字信息
};
// ./udpSever port [ip]
int main(int argc,char* argv[])
{
    if(argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(3);
    }  
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if(argc == 3)
    {
        ip = argv[2];
    }
    UdpServer svr(port,ip);
    svr.init();
    svr.start();

    return 0;
}

我们在用指令也可以查看当前的服务信息

netstat -lnup

  • 第一列为服务类型(Proto):当前为udp
  • 第二列表示有没有收到消息(Recv-Q):0表示没有
  • 第三列表示有没有发送消息(Send-Q):0表示没有
  • 第四列表示本地绑定的ip地址:为全0表示任意地址绑定,绑定的端口号是8080
  • Foreign Address :表示允许远端的任何主机任何端口给我发消息

注意:云服务器不能显示的写端口号,因为云服务器不允许你绑定云服务器公网IP

因此我们一旦实现绑定云服务器ip会报错。我们直接不写让他任意绑定即可

 

6.3.2 实现简单通信的服务器

我们完成服务器编写的基本框架后,我们想要实现一个可以将客户端传入的字符进行大小写转换的服务。具体服务如下:

 void start()
    {
        //服务器设计的时候都是死循环
        //将来读取到的数据都放在这里
        char inbuffer[1024];
        //将来发送的数据都放在这里
        char outbuffer[1024];


        while(true)
        {
            struct sockaddr_in peer;//远端 输出型参数
            socklen_t len = sizeof(peer);//输入输出型参数
            //demo 2
            //后面的两个参数是输出型参数,谁给你发的消息 和客户端多长
            ssize_t s = recvfrom(sockfd_,inbuffer,sizeof(inbuffer)-1,0,
                (struct sockaddr*)&peer,&len);
            if(s>0)
            {
                inbuffer[s] = 0;//当做字符串
            }
            else if(s == -1)
            {
                logMessage(WARINING,"recvfrom:%s:%d",strerror(errno),sockfd_);
                continue;//服务器不能退出
            }
            //走到这里一定成功了 除了读取到对方的数据 还要读取对方的网络地址[ip:port]
            std::string peerIp = inet_ntoa(peer.sin_addr);//拿到了对方的ip
            uint32_t peerPort = ntohs(peer.sin_port);//拿到了对方的port
            //打印出来客户端给服务器发送过来的消息
            logMessage(NOTICE,"[%s:%d]]# %s",peerIp.c_str(),peerPort,inbuffer);
            // logMessage(NOTICE,"serve 提供 service中...");
            // sleep(1);
        }
    }

6.4 udpClient代码

我们服务器写完之后,现在来编写客户端的代码

客户端在编写的时候不需要bind,为什么?

所谓的"不需要"指的是:不需要用户自己bind端口信息 因为OS会自动给你绑定,如果我们非要自己bind 可以 但是不推荐!!!

所有的客户端软件<- ->服务器 通信的时候 必须得有client[ip:port] <-->serve[ip:port]。为什么?

因为client很多,不能给客户bind指定的port,因为port可能被别的client使用了. 你的client就无法启动了

那么Server凭什么要bind呢?

server提供的服务,必须被所有人都知道!而且server不能随便改变!

#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include <sys/socket.h>
#include <assert.h>
static void Usage(std::string name)
{
    std::cout << "Usage:\n\t" << name << " server_ip server_port" << std::endl;
}

// udpClient server_ip server_port
//  如果一个客户端要连接server必须知道server对应的ip和port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 1.根据命令行 设置要访问的服务器IP
    std::string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    // 2.创建客户端
    //  2.1 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sockfd > 0);

    // 2.2 client 不需要bind
    // 所谓的"不需要" 指的是:不需要用户自己bind端口信息 因为OS会自动给你绑定
    // 如果我们非要自己bind 可以 但是不推荐
    // 所有的客户端软件<- ->服务器 通信的时候 必须得有client[ip:port] <-->serve[ip:port]
    // 为什么?client很多,不能给客户bind指定的port,因为port可能被别的client使用了
    // 你的client就无法启动了
    // 2.2 填写服务器信息
    struct sockaddr_in server;
    bzero(&server, sizeof server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // 3.通信过程
    std::string buffer;
    while (true)
    {
        std::cout << "Please Enter# ";

        std::getline(std::cin, buffer);
        // 发送消息给sever
        // 首次调用sendto函数的时候 我们的client
        sendto(sockfd, buffer.c_str(), buffer.size(), 0,
               (const struct sockaddr *)&server, sizeof(server));
    }

    return 0;
}

6.5本地测试

其中127.0.0.1 是本地换回的端口号,也就是本主机

 

当然如果大家有多台主机就可以跨网络传输通信啦!

这是我朋友链接我的主机给我发送的消息(跨主机网络通信)

 

注意:如果是云服务器的话,大家一定要手动开端口号,否则是不能使用指定端口号的

(本篇完)

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

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

相关文章

【Docker】三 镜像容器常用命令

这里写目录标题1 配置镜像加速器2 Docker镜像常用命令2.1 搜索镜像2.2 下载镜像[重要]2.3 列出镜像[重要]2.3 删除本地镜像[重要]2.4 保存镜像2.5 加载镜像2.6 构建镜像[重要]3 容器常用命令3.1 新建并启动容器[重要]3.2 列出容器[重要]3.3 停止容器[重要]3.4 强制停止容器[重要…

你可以不用Git,但不能不会Git(三)基础(下)

目录 一.将文件添加至忽略列 二.日志记录操作 三.比较文件差异 四.还原文件 一.将文件添加至忽略列 一般我们总会有些文件无需纳入Git的管理&#xff0c;也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件&#xff0c;比如日志文件&#xff0c;或者编译过程中…

重学 Java 设计模式-结构型模式-适配器模式

重学 Java 设计模式-结构型模式-适配器模式 内容摘自&#xff1a;添加链接描述 适配器模式介绍 图片来自&#xff1a;https://refactoringguru.cn/design-patterns/adapter(opens new window) 适配器模式的主要作用就是把原本不兼容的接口&#xff0c;通过适配修改做到统一。…

canva绘制(二次、三次)贝塞尔曲线并且图片在曲线上运动

下图为实现效果&#xff08;图片在三次贝塞尔曲线中运动&#xff09; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"…

Spring之注解开发

目录 一&#xff1a;Bean基本注解开发 二&#xff1a;Bean依赖注入注解开发 三&#xff1a;非自定义Bean注解开发 四&#xff1a;Bean配置类的注解开发 五&#xff1a;Spring配置其他注解 六&#xff1a;Spring注解的解析原理 一&#xff1a;Bean基本注解开发 Spring除了…

【区块链】区块链技术学习总结

文章目录一、区块链技术简介1.1 区块链概念1.2 区块链应用1.2.1 区块链1.0技术&#xff1a;比特币1.2.2 区块链2.0技术&#xff1a;以太坊1.2.3 区块链3.0技术&#xff1a;价值互联网二、区块链1.0技术比特币2.1 比特币2.1.1 比特币概念2.1.2 比特币性质2.1.3 比特币解决的问题…

Springboot扩展点之BeanDefinitionRegistryPostProcessor

前言通过这篇文章来大家分享一下&#xff0c;另外一个Springboot的扩展点BeanDefinitionRegistryPostProcessor&#xff0c;一般称这类扩展点为容器级后置处理器&#xff0c;另外一类是Bean级的后置处理器&#xff1b;容器级的后置处理器会在Spring容器初始化后、刷新前这个时间…

第二章 chrony服务器

文章目录第二章 chrony服务器1.1安装与配置1.2同步网络时间服务器1.3 配置时间服务器1.4 chronyc 命令1.5常见时区课后练习第一题&#xff1a;第一台机器从阿里云同步时间&#xff0c;第二台机器从第一台机器同步时间第二题&#xff1a;第一台服务器使用系统时间作为第二台服务…

jetson nano GPIO控制说明

文章目录一.GPIO介绍二.安装GPIO库python库C库三.几种常用的通信协议UARTPWMI2CI2SSPI四.控制函数说明python&#xff08;[参考](https://pypi.org/project/Jetson.GPIO/)&#xff09;C五.例程一.GPIO介绍 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输…

干货 | Web自动化测试中显式等待与隐式等待该怎么用?

在实际工作中等待机制可以保证代码的稳定性&#xff0c;保证代码不会受网速、电脑性能等条件的约束。等待就是当运行代码时&#xff0c;如果页面的渲染速度跟不上代码的运行速度&#xff0c;就需要人为的去限制代码执行的速度。在做 Web 自动化时&#xff0c;一般要等待页面元素…

高压放大器在电子束增材制造聚焦消像散控制技术研究的应用

实验名称&#xff1a;高压放大器在电子束增材制造聚焦消像散控制技术研究的应用 研究方向&#xff1a;增材制造 实验目的&#xff1a; 电子束选区熔化技术&#xff0c;即电子束3D打印技术&#xff0c;属于金属增材制造的分支。该技术以电子束为热源&#xff0c;在计算机控制下以…

华为防火墙配置笔记

防火墙&#xff08;Firewall&#xff09;也称防护墙&#xff0c;是由Check Point创立者Gil Shwed于1993年发明并引入国际互联网&#xff08;US5606668&#xff08;A&#xff09;1993-12-15&#xff09;防火墙是位于内部网和外部网之间的屏障&#xff0c;它按照系统管理员预先定…

实战工作十年的Code Review方法论与实践总结

作为卓越工程文化的一部分&#xff0c;Code Review其实一直在进行中&#xff0c;只是各团队根据自身情况张驰有度&#xff0c;松紧可能也不一&#xff0c;这里简单梳理一下CR的方法和团队实践。 一、为什么要CR 提前发现缺陷 在CodeReview阶段发现的逻辑错误、业务理解偏差、…

CleanMyMac2023Mac下载排行最好的清理工具

CleanMyMac是Mac清理工具&#xff0c;具有很多功能。例如‬&#xff0c;删除大量不可见的缓存文件&#xff0c;可以批量删除未使用的DMG、不完整的下载以及其余的旧包。不过由于MAC系统不像windows那样会产生缓存或系统垃圾&#xff0c; 使用Win电脑很多人会下载各类系统优化软…

MQ面试题总结

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java面试题…

【流行框架】Zookeeper

&#x1f31f;个人博客&#xff1a;www.hellocode.top&#x1f31f; &#x1f31f;Java知识导航&#xff1a;Java-Navigate&#x1f31f; ⭐想获得更好的阅读体验请前往Java-Navigate &#x1f525;本文专栏&#xff1a;《流行框架》 &#x1f31e;如没有JavaWEB基础&#xff0…

Linux文件目录结构详解

Linux文件目录结构 Linux文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录“/”&#xff0c;然后在此目录下再创建其他的目录 Linux系统下一切硬件皆文件 具体的目录结构 /bin ->存放最经常使用的指令/sbin ->存放系统管理员使用的系统管理…

从Redis、HTTP协议,看Nett协议设计,我发现了个惊天大秘密

1. 协议的作用 TCP/IP 中消息传输基于流的方式&#xff0c;没有边界 协议的目的就是划定消息的边界&#xff0c;制定通信双方要共同遵守的通信规则 2. Redis 协议 如果我们要向 Redis 服务器发送一条 set name Nyima 的指令&#xff0c;需要遵守如下协议 // 该指令一共有3…

第一章 R语言介绍

1.为何使用R 与起源于贝尔实验室的S语言类似&#xff0c;R也是一种为统计计算和绘图而生的语言和环境&#xff0c;它是一套开源的数据分析解决方案&#xff0c;由一个庞大且活跃的全球性研究型社区维护。但是&#xff0c;市面上也有许多其他流行的统计和制图软件&#xff0c;如…

NLP自然语言处理NLTK常用英文功能汇总

自然语言处理 (NLP) 是一门研究如何让计算机程序理解人类语言的学科。NLTK (Natural Language Toolkit) 是一个 Python 包,可以用于 NLP 的应用开发。 很多数据都是非结构化的,而且包含可以被人类读懂的文本。在用编程方式分析这些数据之前,我们需要对它们进行预处理。在本…