linux网络编程——UDP编程

写在前边

本文是B站up主韦东山4_8-3.UDP编程示例_哔哩哔哩_bilibili视频的笔记,其中有些部分博主也没有理解,希望各位辩证的看。

UDP协议简介

UDP 是一个简单的面向数据报的运输层协议,在网络中用于处理数据包,是一种无连接的协议。UDP 不提供可靠性的传输,它只是把应用程序传给 IP 层的数据报发送出去,但是并不能保证它们能到达目的地。由于 UDP 在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。

对于UDP网络编程步骤,这里借用韦山东老师的图:

图 3UDP 用户数据包模式

UDP相对于TCP编程来说简单了很多,因为UDP没有TCP那些可靠连接的东西,所以编程相对来说也简单了一些。

这里对于函数,只有发送和接受函数和之前有点区别:

sendto()

函数结构

cpp 复制代码
#include <sys/types.h>

#include <sys/socket.h>

ssize_t sendto ( socket s , const void * msg, int len, unsigned int flags, const

struct sockaddr * to , int tolen ) ;

描述

sendto() 用来将数据由指定的socket传给对方主机。

参数

  • s

用于通信的通信描述符,对于服务器,就是指accept函数返回的通信描述符

  • msg

指向一片应用缓存,用于存放要发送的数据,存放数据一般使用结构体变量。

  • len

存放发送数据的缓存的大小。

  • flags

一般设置为0,此时是阻塞发送的,阻塞发送是指发送数据不成功会一直阻塞,直到被某信号中断或发送成功为止,不过发送数据一般不阻塞。

  • to

存放指定欲传送的网络地址,结构sockaddr请参考bind()。

  • tolen

sockaddr的结构长度。

  • 返回值

成功:返回发送的字节数,失败:返回-1

recvfrom()

函数结构

cpp 复制代码
#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);

描述

它是一个系统调用,用于从套接字接收数据。该函数通常与无连接的数据报服务(如 UDP)一起使用,但也可以与其他类型的套接字使用。与简单的 recv() 函数不同,recvfrom() 可以返回数据来源的地址信息。

参数

  • sockfd

一个已打开的套接字的描述符

  • buf

指明一个缓冲区,该缓冲区用来存放recvfrom函数接收到的数据

  • len

指明buf的长度。

  • flags

传0 表示使用默认协议。

  • src_addr

一个指针,指向一个 sockaddr 结构,用于保存发送数据的源地址,结构sockaddr请参考bind()。

  • addrlen

src_addr的结构长度。当 recvfrom() 返回时,该值会被修改为实际地址的长度(以字节为单位)。

  • 返回值

成功:成功执行时,返回接收到的字节数。,失败:返回-1。

剩下的函数请参考TCP中的函数:linux网络编程------TCP编程-CSDN博客

现在分别实现server 程序和client 程序。

server程序

在这个函数中参照server图进行编程,将图中所有函数挨个实现即可。

图 4server

具体实现看代码:

cpp 复制代码
#include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <netinet/in.h>

#include <sys/wait.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <pthread.h>







/*

* 服务器端程序

* socket

* bind

* sendto/recvfrom

* close

*/

#define PORT 8888



struct Param {

    int sockfd;

    char* buff;

    struct sockaddr* src_addr;

};



// 接收数据在子线程中处理

void* Receive_data(void* Param1)

{



    struct Param Param2 = *(struct Param*)Param1;



    while (1)

    {

        // 接收数据

        int addr_len = sizeof(Param2.src_addr);

        int iRecvLen = recvfrom(Param2.sockfd, Param2.buff, sizeof(Param2.buff), 0, Param2.src_addr, &addr_len);

        if (iRecvLen > 0)

        {

            Param2.buff[iRecvLen] = '\0';

            // inet_ntoa(tSocketClientAddr.sin_addr)是将IP地址转换为字符串的函数

            printf("Get Msg From Client: %s: %s\n", inet_ntoa(((struct sockaddr_in*)Param2.src_addr)->sin_addr), Param2.buff);

        }

    }

   

}

int main(int argc, char** argv)

{

    int isocketfd;

    int Client_socketfd;

    int ret;

    struct sockaddr_in my_addr;

    struct sockaddr_in Client_addr;

    char send_buf[1024];

    char buff[1000];

    struct Param Param1;

    pthread_t ntid;

    // SOCK_DGRAM是UDP协议,AF_INET表示IPv4协议

    isocketfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (-1 == isocketfd)

    {

        printf("create socket failed!\n");

        return -1;

    }

    // 配置bind函数的地址信息

    my_addr.sin_family      = AF_INET;  //指定协议族为IPV4版本的TCP/IP协议族

    my_addr.sin_port        = htons(PORT);  //指定端口号(和客户端通信的端口号,两者必须一致)

    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //指定IP地址,这里设置为INADDR_ANY,表示可以接收任何来源的连接请求

    ret = bind(isocketfd, (const struct sockaddr*)&my_addr,sizeof(struct sockaddr));

    if (-1 == ret)

    {

        printf("bind socket failed!\n");

        return -1;

    }



    Param1.sockfd = isocketfd;

    Param1.buff = buff;

    Param1.src_addr = (struct sockaddr*)&Client_addr;

    ret = pthread_create(&ntid, NULL, Receive_data, &Param1);

    if (ret)

    {

        printf("create pthread failed!\n");

        return -1;

    }

    while (1)

    {

        // 发送数据

        if (fgets(send_buf, sizeof(send_buf), stdin))

        {

            sendto(isocketfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&Client_addr, sizeof(Client_addr));

        }

       

       

    }

    close(isocketfd);

    return 0;

}

这里使用了多线程将发送和接收数据分开,实现发送和接收数据互不干涉,其中pthread_create()函数是创建一个线程,具体函数分析见pthread_create()章节,这里将接收数据放入了创建的子线程中,主函数中实现发送函数。

图 5服务器端测试结果

这里获得的数据,其中192.168.147.132的IP是客户端的数据。

client 程序

在客户端的程序中,基本思路还是和之前服务器的程序相同,都是使用多线程将接收数据放入了创建的子线程中,还是依照韦老师的图:

图 6client

依次实现如上函数即可,具体实现如下代码:

cpp 复制代码
#include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <netinet/in.h>

#include <sys/wait.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <pthread.h>



/*

* UDP客户端程序

* socket

* send

* close

*/



#define PORT 8888



// 线程参数结构体

struct Param {

    int sockfd;

    char* buff;

    struct sockaddr* src_addr;

};



// 接收数据在子线程中处理

void* Receive_data(void* Param1)

{



    struct Param Param2 = *(struct Param*)Param1;



    while (1)

    {

        // 接收数据

        int addr_len = sizeof(Param2.src_addr);

        int iRecvLen = recvfrom(Param2.sockfd, Param2.buff, sizeof(Param2.buff), 0, Param2.src_addr, &addr_len);

        if (iRecvLen > 0)

        {

            Param2.buff[iRecvLen] = '\0';

            // inet_ntoa(tSocketClientAddr.sin_addr)是将IP地址转换为字符串的函数

            printf("Get Msg From Client: %s: %s\n", inet_ntoa(((struct sockaddr_in*)Param2.src_addr)->sin_addr), Param2.buff);

        }

        else if (iRecvLen == -1)

        {

            perror("recvfrom failed");

            break;

        }

    }



}





int main(int argc, char** argv)

{

    int isocketfd;

    struct sockaddr_in client_addr;

    char send_buf[1024];

    struct Param Param1;

    pthread_t ntid;

    char reve_buff[1024];

    memset(send_buf, 0, sizeof(send_buf));

    if (argc < 2)

    {

        printf("Usage: %s ip_address\n", argv[0]);

        return -1;

    }



    client_addr.sin_family = AF_INET;  //指定协议族为IPV4版本的TCP/IP协议族

    client_addr.sin_port = htons(PORT);  //指定端口号

    //指定IP地址,htons()函数是将一个本地字节序的short转为网络字节序的short

    client_addr.sin_addr.s_addr = inet_addr(argv[1]);

    isocketfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (-1 == isocketfd)

    {

        printf("create socket failed!\n");

        return -1;

    }

    Param1.sockfd = isocketfd;

    Param1.buff = reve_buff;

    Param1.src_addr = (struct sockaddr*)&client_addr;

    int ret = pthread_create(&ntid, NULL, Receive_data, &Param1);

    if (ret)

    {

        printf("create pthread failed!\n");

        return -1;

    }

    while (1)

    {

        if (fgets(send_buf, sizeof(send_buf), stdin))

        {

            sendto(isocketfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&client_addr, sizeof(client_addr));

        }

    }

    return 0;

}

图 7客户端测试结果

连接成功后即可发送和接收数据。

pthread_create()

函数结构

cpp 复制代码
#include <pthread.h>

int pthread_create(pthread_t* restrict tidp,const pthread_attr_t* restrict_attr,void* (*start_rtn)(void*),void *restrict arg);

描述

用来创建线程,并向线程函数传递参数。

参数

  • tidp

事先创建好的pthread_t类型的参数。成功时tidp指向的内存单元被设置为新创建线程的线程ID。

  • attr

用于定制各种不同的线程属性。通常直接设为NULL。

  • start_rtn

新创建线程从此函数开始运行。无参数时arg设为NULL即可。

  • arg

start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。

  • 返回值

成功:返回0,失败:返回错误码。

相关推荐
Aczone2817 分钟前
硬件(五) 存储、ARM 架构与指令系统
arm开发·嵌入式硬件·架构
Mr. Cao code38 分钟前
Docker:颠覆传统虚拟化的轻量级革命
linux·运维·ubuntu·docker·容器
Dontla44 分钟前
Docker多共享网络配置策略(Docker多网络、Docker networks、Docker Compose网络、Docker网络、Docker共享网络)
网络·docker·容器
抓饼先生1 小时前
Linux control group笔记
linux·笔记·bash
【ql君】qlexcel1 小时前
MCU上电到运行的全过程
单片机·嵌入式硬件·mcu·启动过程
LUCIAZZZ1 小时前
HTTPS优化简单总结
网络·网络协议·计算机网络·http·https·操作系统
搞一搞汽车电子1 小时前
S32K3平台eMIOS 应用说明
开发语言·驱动开发·笔记·单片机·嵌入式硬件·汽车
挺6的还1 小时前
25.线程概念和控制(二)
linux
wanhengidc1 小时前
云手机运行流畅,秒开不卡顿
运维·网络·科技·游戏·智能手机
pQAQqa2 小时前
FreeRTOS项目(2)摇杆按键检测
stm32·单片机·嵌入式硬件·freertos