首页 > 系统相关 >2024-02-29-Linux高级网络编程(2-UDP编程)

2024-02-29-Linux高级网络编程(2-UDP编程)

时间:2024-02-29 11:01:50浏览次数:27  
标签:02 UDP addr int ip 编程 接字 include 字节

2. UDP编程

2.1 字节序概述

字节序是指多字节数据的存储顺序

分类:

  1. 小端格式:将低位字节数据存储在低地址(LSB)
  2. 大端格式:将高位字节数据存储在低地址
image-20240228170207279

2.1.1 如何判断当前系统的字节序

#include <stdio.h>
union un
{
    /* data */
    int a;
    char b;
};

int main(int argc, char const *argv[])
{
    union un myun;
    myun.a = 0x12345678;
    printf("a = %#x\n", myun.a);
    printf("b = %#x\n", myun.b);
    if (myun.b == 0x78)
    {
        printf("小端对齐\n");
    }
    else
    {
        printf("大端对齐\n");
    }

    return 0;
}

输出结果

a = 0x12345678
b = 0x78
小端对齐

2.1.2 字节序转换函数

特点

  1. 网络协议指定了通讯字节序大端
  2. 只有在多字节数据处理时才需要考虑字节序
  3. 运行在同一台计算机上的进程相互通信时,一般不用考虑字节序
  4. 异构计算机之间通讯,需要转换自己的字节序为网络字节序

在需要字节序转换的时候一般调用特定字节序转换函数

host -> network
    1. htonl
    #include <arpa/inet.h>
    uint32_t htonl(uint32_t hostint32)
    功能:将32位主机字节序数据转换成网络字节序数据
    参数:
    	hostint32: 待转换的32位主机字节序数据;
	返回值:
        成功:返回网络字节序的值
        
    2. htons
    #include <arpa/inet.h>
    uint16_t htons(uint16_t hostint16);
	功能:
    	将16位主机字节序数据转换成网络字节序数据;
	参数:	
    	uint16_t: unsigned short int
        hostint16: 待转换的16位主机字节序数据;
	返回值:
    	成功:
			返回网络字节序的值
networl -> host
    3. ntohl
    #include <arpa/inet.h>
    uint32_t ntohl(uint32_t netint32);
	功能:
 		将32位网络字节序数据转换成主机字节序数据
    参数:
    	uint32_t: unsigned int 
         netint32: 待转换的32位网络字节序数据;
	返回值:
        成功:
            返回主机字节序的值
                
    4. ntohs
       uint16_t ntohs(uint16_t netint16);
	功能:
        将16位网络字节序数据转换成主机字节序数据;
	参数:
        uint16_t: unsigned short   int
        netint16:待转换的16位网络字节序数据
        返回值:
        	成功:返回主机字节序的值 
#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    int a = 0x12345678;
    short b = 0x1234;
    // ubuntu中以小端存储,转换后变成大端存储,以字节进行操作
    printf("%#x\n", htonl(a));
    printf("%#x\n", htons(b));
    return 0;
}

输出结果

0x78563412
0x3412

2.1.3 地址转换函数 - inet_pton、inet_ntop

人为识别的ip地址是点分十进制的字符串形式,但是计算机网络中识别的ip地址是整型数据。

// 字符串ip地址转换型数据
#include <arpa/inet.p>
int inet_pton(int family,const char *strptr,void *addrptr)
功能:将点分十进制数串转换成 32 位无符号整数
参数:
	family:  协议族
	strptr:  点分十进制数串 
     addrptr: 32 位无符号整数的地址;
返回值:
	成功: 1;
	失败: 非1
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
    char ip_str[] = "192.168.3.103";
    unsigned int ip_int = 0;
    unsigned char *ip_p = NULL;

    // 将点分十进制ip地址转化为32位无符号整形数据
    inet_pton(AF_INET, ip_str, &ip_int);
    printf("ip int = %d\n", ip_int);
    
    ip_p = (char *)&ip_int;
    printf("in_uint = %d,%d,%d,%d\n", *ip_p, *(ip_p + 1), *(ip_p + 2), *(ip_p + 3));
    return 0;
}

输出结果

ip int = 1728293056
in_uint = 192,168,3,103
// 整型数据转字符串格式ip 地址
const char *inet_ntop(int family, const void *addrptr,char *strptr,size_t len)
功能: 将 32 位无符号整数转换成点分十进制数串;
参数:
	 family:协议族 :AF_INET // ipv4
	 addrptr:32 位无符号整数
	 strptr:点分十进制数串
     len :strptr 缓存区长度
    	len的宏定义 :
			#define INET_ADDRSTRLEN 16 // ipv4
			#define INET_ADDRSTRLEN 46 // ipv6
返回值:
    成功:返回字符串首地址
    失败:返回NULL
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
    unsigned char ip_int[] = {192, 168, 3, 103};
    char ip_str[16] = "";
    inet_ntop(AF_INET, &ip_int, ip_str, 16);
    printf("ip_s =%s\n", ip_str);
    return 0;
}

输出结果

ip_s =192.168.3.103

inet_addr()inet_ntoa()

这两个函数只能用在ipv4地址的转换,且用的比较多

#include<sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);
功能:将点分十进制ip地址转化为整形数据;
参数:
	cp:点分十进制的IP地址
返回值:
	成功:整形数据;


char *inet_ntoa(struct in_addr in);
功能:将整形数据转化为点分十进制的ip地址;
参数:
	in:保存ip地址的结构体;
返回值:
	成功: 点分十进制的IP地址

2.2 UDP介绍、编程流程

2.2.1 UDP介绍

UDP 协议
面向无连接的用户数据报协议,在传输数据前不需要先建立连接:目地主机的运输层收到UDP 报文后,不需要给出任何确认

UDP 特点

  1. 相比 TCP 速度稍快些
  2. 简单的请求/应答应用程序可以使用 UDP
  3. 对于海量数据传输不应该使用 UDP
  4. 广播和多播应用必须使用 UDP

UDP 应用
DNS(域名解析)、NFS(网络文件系统)、RTP(流媒体)等

2.2.2 网络编程接口 socket

网络通信要解决的是不同主机进程间的通信

  1. 首要问题是网络间进程标识问题
  2. 多重协议的识别问题

​ 20 世纪 80 年代初,加州大学 Berkeley 分校在 BSD(一个 UNIX OS 版本)系统内实现了 TCP/IP 协议,其网络程序编程开发接口为 socket

​ 随着 UNIX 以及类 UNIX 操作系统的广泛应用, socket 成为最流行的网络程序开发接口。

socket作用:提供不同主机之间的通信

socket特点

  1. socket 也称“套接字”
  2. 是一种文件描述符,代表了一个通信管道的一个端点
  3. 类似对文件的操作一样,可以使用 read、write、close 等函数对 socket套接字进行网络数据的收取和发送等操作
  4. 得到 socket 套接字(描述符)的方法调用 socket()

socket分类

  1. SOCK_STREAM,流式套接字,用于TCP
  2. SOCK_DGRAM,数据报套接字,用于UDP
  3. SOCK_RAW,原始套接字,对于其他层次的协议操作时需要使用这个类型
  4. ....

2.2.3 UDP编程C/S架构

服务器:

1. 创建套接字 socket();
2. 将服务器的ip地址、端口号与套接字进行绑定 bind();
3. 接受数据 recvfrom();
4. 发送数据 sendto()

客户端

1. 创建套接字 socket();
2. 发送数据 sendto();
3. 接收数据 recvfrom();
4. 关闭套接字 close();

2.3 UDP编程-创建套接字

2.3.1 创建套接字socket

#include <sys/socket.h>
int socket(int family int type,int protocol);
功能:创建一个用于网络通信的socket套接字;
参数:
    family: 协议族(AF_INET,AF_INET6,PF_PACKET等);
	type: 套接字类(SOCK_STREAM,SOCK_DGRAM,SOCK_RAW等);
	protocol:协议类别(0,IPPROTO_TCP,IPPROTO_UDP等)
    
返回值:
	套接字
特点:
	创建套接字时,系统不会分配端口;
	创建的套接字默认属性是主动的,即主动发起服务的请求;
	当作为服务器时,往往需要修改为被动的

2.3.2 案例

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

int main(int argc, char const *argv[])
{
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        perror("FAIL TO SOCKET");
        exit(1);
    }
    printf("socketfd = %d\n", sockfd);
    return 0;
}

输出结果

socketfd = 3

2.4 UDP编程-发送、绑定、接收数据

2.4.1 IPV4套接字地址结构

在网络编程中经常使用的结构体sockaddr_in

#include <netinet/in.h>
struct in_addr{
    in_addr_t s_addr;//4字节
}

struct sockaddr_in{
    sa_family_t sin_family;//协议族,2字节
    in_port_t sin_port;//端口号,2字节
    struct in_addr sin_addr;//ip地址,4字节
    char sin_zero[8]//填充,8字节
}

通用结构体sockaddr

struct sockaddr{
    sa_family_t sa_family; // 2字节
    char sa_data[14]; // 14字节
}

为了使不同格式地址能被传入套接字函数,地址须要强制转换成通用套接字地址结构,原因是因为不同场合所使用的结构体不一样。但是调用的函数却是同一个,所以定义一个通用结构体,当在指定场合使用时,在根据要求传入指定的结构体即可。

2.4.2 发送数据 - sendto函数

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);.
功能: 向 to 结构体指针中指定的 ip,发送 UDP 数据;
参数:
	sockfd: 套接字
	buf: 发送数据缓冲区
	nbytes: 发送数据缓冲区的大小
	flags: 一般为 0阻塞,MSG_DONT_WAIT非阻塞
	to: 指向目的主机地址结构体的指针;
	addrlen: dest_addr的长度;
注意:
    通过 to 和 addren 确定目的地址;
    可以发送0长度的 UDP 数据包;
返回值:
    成功:返回实际发送的字节数
    失败:返回-1

2.4.3 绑定函数 - bind函数

UDP网络程序想要收取数据需什么条件?
确定的ip地址,确定的port
怎样完成上面的条件呢?
接收端使用bind函数,来完成地址结构与socket套接字的绑定,这样ip、port就固定了发送端在sendto函数中指定接收端的ip、port,就可以发送数据了 。

#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
功能:将套接字与网络信息结构体绑定;
参数:
	sockfd:文件描述符,socket的返回值;
	addr:网络信息结构体
			通用结构体(一般不用)struct sockaddr
		 网络信息结构体 sockaddr_in
        	#include <netinet/in.h>
             struct sockaddr_in
	addrlen: addr的长度
返回值:
	成功:0
	失败:-1

2.4.4 接收数据

#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:文件描述符,socket的返回值
	buf: 保存接收的数据
	len: buf的长度
	flags: 标志位
		  0 阻塞
		  MSG_DONTWAIT 非阻塞
    src_addr: 源的网络信息结构体(自动填充,定义变量传参即可)
    addrlen: src_addr的长度
返回值:
	成功:0
	失败:-1        

2.5 UDP编程-Client、Server

image-20240229104052395

2.5.1 UDP客户端注意点

1、本地IP、本地端口(我是谁)
2、目的IP、目的端口(发给谁)
3、在客户端的代码中,只设置了目的IP、目的端口

​ 客户端的本地ip、本地port是我们调用sendto的时候linux系统底层自动给客户端分配的;分配端口的方式为随机分配,即每次运行系统给的port不一样

2.5.2 UDP服务器注意点

  1. 服务器之所以要 bind 是因为它的本地 port 需要是固定,而不是随机的
  2. 服务器也可以主动地给客户端发送数据
  3. 客户端也可以用 bind,这样客户端的本地端口就是固定的了,但一般不这样做

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define BUF_SIZE 1024
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 12345

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    socklen_t server_addr_len;
    char buffer[BUF_SIZE];

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(1);
    }

    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr_len = sizeof(server_addr);

    while (1) {
        printf("Enter a message to send (or 'q' to quit): ");
        fgets(buffer, BUF_SIZE, stdin);

        // 发送数据
        if (sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&server_addr, server_addr_len) == -1) {
            perror("sendto");
            exit(1);
        }

        // 退出条件
        if (buffer[0] == 'q' && (buffer[1] == '\n' || buffer[1] == '\0')) {
            break;
        }

        // 接收回复
        int recv_len = recvfrom(sockfd, buffer, BUF_SIZE, 0, NULL, NULL);
        if (recv_len == -1) {
            perror("recvfrom");
            exit(1);
        }

        // 打印回复
        buffer[recv_len] = '\0';
        printf("Received reply from server: %s\n", buffer);
    }

    // 关闭套接字
    close(sockfd);

    return 0;
}

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define BUF_SIZE 1024
#define PORT 12345

int main() {
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len;
    char buffer[BUF_SIZE];

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(1);
    }

    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    // 绑定套接字到服务器地址
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }

    printf("UDP server is running and listening on port %d...\n", PORT);

    while (1) {
        // 接收数据
        client_addr_len = sizeof(client_addr);
        int recv_len = recvfrom(sockfd, buffer, BUF_SIZE, 0, (struct sockaddr *)&client_addr, &client_addr_len);
        if (recv_len == -1) {
            perror("recvfrom");
            exit(1);
        }

        // 打印接收到的数据
        buffer[recv_len] = '\0';
        printf("Received message from client: %s\n", buffer);

        // 发送回复
        if (sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&client_addr, client_addr_len) == -1) {
            perror("sendto");
            exit(1);
        }
        printf("Sent reply to client: %s\n", buffer);
    }

    // 关闭套接字
    close(sockfd);

    return 0;
}

image-20240229105148051

标签:02,UDP,addr,int,ip,编程,接字,include,字节
From: https://www.cnblogs.com/hasaki-yasuo/p/18042960

相关文章

  • 快速改善代码质量的20 条编程规范
    命名大到项目名,模块名,包名,对外暴露的接口,笑道类名,函数名,变量名,参数名。只要做开发,我们就逃不过起名字这一关,命名的好坏,对于代码的可读性来说非常中国要。甚至起到决定性的作用。除此之外,命名能力也体现了一个程序员的而基本的素养。这也是我们把命名放到第一位的原因。 取一个......
  • 界面控件DevExpress WinForms 2024产品路线图预览(一)
    DevExpressWinForm拥有180+组件和UI库,能为WindowsForms平台创建具有影响力的业务解决方案。DevExpressWinForm能完美构建流畅、美观且易于使用的应用程序,无论是Office风格的界面,还是分析处理大批量的业务数据,它都能轻松胜任!本文将介绍2024年DevExpressWinForms第一个主要更新......
  • 【STL和泛型编程】3. set、map分析(及typename起源)
    前置知识:红黑树原理 【数据结构】7.平衡搜索树(AVL树和红黑树),红黑树的平衡性有利于search和insert红黑树的迭代器begin()左侧end()右侧迭代顺序56781011121315不能使用迭代器修改Key的值,例如将6改成50会破坏红黑树的性质1.RB-tree在g++编译......
  • 20240228打卡
    早上python程序设计课,主要对py的安装配置以及基础的语法进行了学习,并通过实际例子体会其鲜明的特点然后工程数学课,主要学习了最优化模型,并顺带复习了以前线性代数的相关知识,老师让我们要会用matlab,但好贵,学生认证后还要29美元qwq。掏钱是不可能滴,先用python试试看吧下午虽然没课......
  • 20240228日记
    今天难得的没加班,为了新住所的事情,特意去山姆超市买了碗,为了续费省30,拿了一盒100+的蓝莓。这就是新型的为了省钱而花钱吧。今天的工作也不算顺利,但是现在只想摆烂了。对于工作的怨气,很大程度是因为对于领导的胡乱指挥。但是我今天仔细思考了下为什么会有这样的信息偏差;为什么我目......
  • 题解 gym102900J 【Octasection】
    代码:#include<iostream>#include<algorithm>#include<stack>#include<vector>#include<cstdio>usingnamespacestd;typedefstructRectangle_tag{ intx1; intx2; inty1; inty2; Rectangle_tag(intx1_,intx2_,int......
  • 【专题】2024物联网平台产业研究报告及案例集报告PDF合集分享(附原数据表)
    原文链接:https://tecdat.cn/?p=35235原文出处:拓端数据部落公众号前三季度,我国软件业务收入达87610亿元,同比增长13.5%。统计范围涵盖软件及信息技术服务、集成电路设计、基础软件、工业软件、信息安全、工业互联网平台和数据服务等。软件业务收入由软件产品、信息技术服务、信息......
  • 2023下学期whk期末游记
    stohztmax0orz!!!!stoYBaggioorz!!!!stoguanyforz!!!!stoyabntoorz!!!!sto_XOF_orz!!!!Day-Inf期末unr开始了\汗,第一天就考地理,寄!Day-998244353unr成绩全出了,一共86+100+97.5+90+79+86+86=624.5,满分720,有点悬啊。。。小四门一般,语文炸了QAQ。Day-1本学期最后一个周六......
  • CSP2023 游寄
    你不会T1你会什么题啊,你一题不会你打个锤子啊——老KDay-2最后一场模拟赛,T3被卡常挂了50,T4没读懂题直接爆0。3场模拟赛一崩到底,喜提倒数,寄。Day-1上午动员大会,连线到了lk,真好。然后pty讲了些小技巧,模拟赛中经常因为奇怪错误挂分的我被反复鞭尸。下午和几个巨......
  • 2024年2月28号题解
    P4994终于结束的起点解题思路通过加法同余原理可以知道f[i]%m==0,那么f[i-1]%m=1,所有把f[i+1]%m=1转换成了f[i-1]%m=1的问题那么只需要填好斐波那契数列再判断就可以了,又因为斐波那契可能会超出int的范围那么我们对每一项斐波那契再取模就可以了代码......