Linux:网络编程-UDP通信

目录

[1.UDP通信(User Datagram Protocol用户数据报协议)](#1.UDP通信(User Datagram Protocol用户数据报协议))

[1.1 UDP协议概述](#1.1 UDP协议概述)

1.2套接字

[1.3. UDP 通信流程](#1.3. UDP 通信流程)

1.4函数接口

2.UDP通信应用示例

2.1基于UDP协议的文件传输程序

2.2基于UDP协议的双向聊天程序


1.UDP通信(User Datagram Protocol用户数据报协议)

1.1 UDP协议概述

UDP协议:用户数据报协议(数据报:一包一包地传递 问题:易丢包,对方有没有收到,这个地址对不对,都无反馈)

特点:①UDP资源开销比较小

②UDP传输机制简单

③UDP传输不安全、不可靠
图示: 发送数据:逐层加包头接收数据:逐层拆包头

1.2套接字

套的是协议和接口,把不同的协议都套到这一组函数接口上完成功能。这是Linux系统下提供的一种为协议去解决通信问题的一套函数接口,任何协议都能套(套接字就是网络通信的"端点",

就像电话机是通话的端点一样。有了它不同设备之间才能"打电话"(通信),而IP地址是"电话号码",端口号是"分机号")

1.3. UDP 通信流程

服务端(收端)步骤:

socket():创建套接字

bind():绑定 IP 和端口

recvfrom():接收客户端数据

sendto():给客户端回发数据

close():关闭套接字

客户端(发端)步骤:

socket():创建套接字

(bind():绑定 IP 和端口。可以绑定也可以不绑定,系统会帮你随机绑定一个端口)

sendto():给服务端发数据

recvfrom():接收服务端回复

close():关闭套接字

1.4函数接口

socket

Socket 是 "网络文件描述符",打开网络设备后获得它,就能通过它收发数据。

  • 标识一个网络连接:IP+端口(IP 定位主机,端口定位主机上的应用程序)
  • 端口范围:1~65535
    原型:int socket(int domain,int type,int protocol);

头文件:#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

功能:创建一个用来进行套接字通信的终端节点

参数:

domain:AF_INET IPv4协议族(PF_INET= 网络程序)

type:SOCK_DGRAM 数据报套接字

protocol:0 UDP通信

返回值:

成功返回新的文件描述符

失败返回-1

示例:

sendto

原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,

const struct sockaddr *dest_addr,socklen_t addrlen);

头文件:#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

功能:向另一个套接字发送信息

参数:

sockfd:使用socket创建获得的套接字

buf:发送内容的空间的首地址

len:发数据的长度

flags:发送的属性,默认为0

dest_addr:目的IP的空间的首地址

addrlen:目的IP的大小

返回值:

成功返回发送的字节数

失败返回-1

示例:

htons

原型:

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:net网络

l:long 长整型 IPV6用它(因为IPV6更长)

s:short 短整型 IPV4用它

htons:将本地字节序转换为网络字节序

inet_addr

原型:in_addr_t inet_addr(const char *cp);

功能:

将字符串IP地址转换 为32位的IP地址

参数:

cp:字符串IP地址

返回值:

32位结构体IP地址

bind

原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:

将地址与套接字完成绑定

参数:

sockfd:套接字文件描述符fd

addr:要绑定的地址信息(IPv4 用 struct sockaddr_in)

cpp 复制代码
struct sockaddr_in
{
    u_short sin_family; // 地址族(AF_INET)
    u_short sin_port;   // 端口(要转网络字节序)
    struct in_addr sin_addr; // IP地址
};

addrlen:地址长度

返回值:

成功返回0

失败返回-1

注意:

1.只能绑定自己的IP地址

2.端口号不能重复绑定

recvfrom

原型: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地址空间的首地址

addrlen:想要接收的地址的大小,接收完毕后返回实际接收到地址的大小

返回值:

成功返回接收字节数

失败返回-1

注意:

1.只能绑定自己的IP地址

2.端口号不能重复绑定

测试------编写程序实现UDP发端,从终端接收字符串发送给接收端

接收软件:

wltszs4.3.29.exe

前提:

1.关闭防火墙/杀毒软件

2.关闭windows Defencer

3.关闭windows 防火墙

代码:

结果:
注意:

①主机中用小端存储,网络中用大端存储(因为接收数据更容易管理)

②UDP包头字段

UDP包头占8个字节,包含:

源端口(2个字节)

目的端口(2个字节)

长度(2个字节)

校验值(2个字节)

2.UDP通信应用示例

2.1基于UDP协议的文件传输程序

发端代码:send.c

cpp 复制代码
#发端:send.c
#include "head.h"

int main(void)
{
    int sockfd = 0;
    int fd = 0;
    char filename[256] = {0};
    char tmpbuff[1300] = {0};
    ssize_t nret = 0;
    struct sockaddr_in recvaddr;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("fail to socket");
        return -1;
    }

    printf("请输入要发送文件:\n");
    gets(filename);

    recvaddr.sin_family = AF_INET;
    recvaddr.sin_port = htons(50000);
    recvaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
    nret = sendto(sockfd, filename, strlen(filename), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
    if (-1 == nret)
    {
        perror("fail to sendto");
        return -1;
    }

    fd = open(filename, O_RDONLY);
    if (-1 == fd)
    {
        perror("fail to open");
        return -1;
    }

    while (1)
    {
        nret = read(fd, tmpbuff, sizeof(tmpbuff));
        if (nret <= 0)
        {
            break;
        }

        nret = sendto(sockfd, tmpbuff, nret, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
        if (-1 == nret)
        {
            perror("fail to sendto");
            return -1;
        }
        usleep(10000);
    }
    
    close(fd);

    sprintf(tmpbuff, "____QUIT____");
    nret = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
    if (-1 == nret)
    {
        perror("fail to sendto");
        return -1;
    }

    close(sockfd);

    return 0;
}

收端代码:recv.c

cpp 复制代码
#收端:recv.c
#include "head.h"

int main(void)
{
    int fd = 0;
    int ret = 0;
    int sockfd = 0;
    struct sockaddr_in recvaddr;
    ssize_t nret = 0;
    char tmpbuff[1300] = {0};

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("fail to socket");
        return -1;
    }

    recvaddr.sin_family = AF_INET;
    recvaddr.sin_port = htons(50000);
    recvaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
    ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
    if (-1 == ret)
    {
        perror("fail to bind");
        return -1;
    }

    nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
    if (-1 == nret)
    {
        perror("fail to recvfrom");
        return -1;
    }

    fd = open(tmpbuff, O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (-1 == fd)
    {
        perror("fail to open");
        return -1;
    }

    while (1)
    {
        memset(tmpbuff, 0, sizeof(tmpbuff));
        nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
        if (-1 == nret)
        {
            perror("fail to recvfrom");
            return -1;
        }

        if (0 == strcmp(tmpbuff, "____QUIT____"))
        {
            break;
        }

        write(fd, tmpbuff, nret);
    }

    close(fd);
    close(sockfd);

    printf("接收成功!\n");

    return 0;
}

makefile:

bash 复制代码
all:send recv

send:send.c 
	gcc $^ -o $@
recv:recv.c
	gcc $^ -o $@

.PHONY:
clean:
	rm send recv

注意:

①测试流程是,将需要发送的文件存于发送端可执行文件所在的目录下,程序会先发送你输入的文件名,再发文件数据;收端先收到文件名,再在收端可执行程序所在的文件夹下建立以该文件名命名的文件,再打卡此文件,将接收的数据存入,再关闭文件。所以,使用时不要将将发端可执行程序与收端可执行程序放于同一个文件夹下,否则会清理需要发送的文件

②两个代码的IP需要手动更改,使用ifconfig命令查看IP地址填入

两个程序的端口为50000,可以更改也可以不更改;

2.2基于UDP协议的双向聊天程序

clientA 代码:

cpp 复制代码
#clientA:
#include "head.h"

pthread_t tid_send;
pthread_t tid_recv;
int sockfd = 0;

void *sendfun(void *arg)
{
    ssize_t nret = 0;
    char tmpbuff[4096] = {0};
    struct sockaddr_in recvaddr;

    recvaddr.sin_family = AF_INET;
    recvaddr.sin_port = htons(50000);
    recvaddr.sin_addr.s_addr = inet_addr("192.168.0.151");

    while (1)
    {
        gets(tmpbuff);
        nret = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
        if (-1 == nret)
        {
            perror("fail to sendto");
            return NULL;
        }

        if (0 == strcmp(tmpbuff, ".quit"))
        {
            break;
        }
    }
    pthread_cancel(tid_recv);

    return NULL;
}

void *recvfun(void *arg)
{
    ssize_t nret = 0;
    char tmpbuff[4096] = {0};

    while (1)
    {
        memset(tmpbuff, 0, sizeof(tmpbuff));
        nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
        if (-1 == nret)
        {
            perror("fail to recvfrom");
            return NULL;
        }
        
        if (0 == strcmp(tmpbuff, ".quit"))
        {
            break;
        }

        printf("RECV:%s\n", tmpbuff);
    }
    pthread_cancel(tid_send);

    return NULL;
}

int main(void)
{
    int ret = 0;
    struct sockaddr_in sendaddr;

    //socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("fail to socket");
        return -1;
    }

    //bind
    sendaddr.sin_family = AF_INET;
    sendaddr.sin_port = htons(40000);
    sendaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
    ret = bind(sockfd, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
    if (-1 == ret)
    {
        perror("fail to bind");
        return -1;
    }

    pthread_create(&tid_send, NULL, sendfun, NULL);
    pthread_create(&tid_recv, NULL, recvfun, NULL);

    pthread_join(tid_send, NULL);
    pthread_join(tid_recv, NULL);

    return 0;
}

clintB代码:

cpp 复制代码
#clientB代码
#include "head.h"

pthread_t tid_send;
pthread_t tid_recv;
int sockfd = 0;

void *sendfun(void *arg)
{
    ssize_t nret = 0;
    char tmpbuff[4096] = {0};
    struct sockaddr_in recvaddr;

    recvaddr.sin_family = AF_INET;
    recvaddr.sin_port = htons(40000);
    recvaddr.sin_addr.s_addr = inet_addr("192.168.0.151");

    while (1)
    {
        gets(tmpbuff);
        nret = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
        if (-1 == nret)
        {
            perror("fail to sendto");
            return NULL;
        }

        if (0 == strcmp(tmpbuff, ".quit"))
        {
            break;
        }
    }
    pthread_cancel(tid_recv);

    return NULL;
}

void *recvfun(void *arg)
{
    ssize_t nret = 0;
    char tmpbuff[4096] = {0};

    while (1)
    {
        memset(tmpbuff, 0, sizeof(tmpbuff));
        nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
        if (-1 == nret)
        {
            perror("fail to recvfrom");
            return NULL;
        }
        
        if (0 == strcmp(tmpbuff, ".quit"))
        {
            break;
        }

        printf("RECV:%s\n", tmpbuff);
    }
    pthread_cancel(tid_send);

    return NULL;
}

int main(void)
{
    int ret = 0;
    struct sockaddr_in sendaddr;

    //socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("fail to socket");
        return -1;
    }

    //bind
    sendaddr.sin_family = AF_INET;
    sendaddr.sin_port = htons(50000);
    sendaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
    ret = bind(sockfd, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
    if (-1 == ret)
    {
        perror("fail to bind");
        return -1;
    }

    pthread_create(&tid_send, NULL, sendfun, NULL);
    pthread_create(&tid_recv, NULL, recvfun, NULL);

    pthread_join(tid_send, NULL);
    pthread_join(tid_recv, NULL);

    return 0;
}

makefile代码

bash 复制代码
all:clientA clientB

clientA:clientA.c 
	gcc $^ -o $@ -lpthread

clientB:clientB.c 
	gcc $^ -o $@ -lpthread 

.PHONY:
clean:
	rm clientA clientB

注意:①两个代码的IP需要手动更改,使用ifconfig命令查看IP地址填入

②发端代码要使用bind绑定自己的IP,接收端的IP和端口号要传给发端的sendto函数

相关推荐
susu10830189111 小时前
ubuntu重做系统后无法apt update
linux·运维·ubuntu
努力搬砖的鱼1 小时前
校园网运维-生成树协议实战
运维·网络
蜕变的小白1 小时前
Linux系统编程:揭秘网络通信 IP与端口号的奥秘
linux·网络·网络协议·tcp/ip
津津有味道1 小时前
易语言数据报UDP通讯接收RFID网络读卡器获取卡号驱动设备播报TTS语音
网络·udp·rfid·读卡器·易语言·数据报
爱倒腾的老唐2 小时前
02、STM32——嵌入式芯片
linux·stm32·嵌入式硬件
战族狼魂2 小时前
基于SpringBoot+Vue的基因调控网络推断系统
网络·vue.js·spring boot
AryShaw2 小时前
macOS 上搭建 RK3568 交叉编译环境
linux·macos
芒果披萨2 小时前
Linux文件类基础命令行1
linux·运维·服务器
学嵌入式的小杨同学2 小时前
STM32 进阶封神之路(八):外部中断 EXTI 实战 —— 按键检测从轮询到中断(库函数 + 寄存器双版本)
linux·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构