• linux网络编程——UDP编程


    写在前边

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

    UDP协议简介

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

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

    图 3UDP 用户数据包模式

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

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

    sendto()

    函数结构

    1. #include
    2. #include
    3. ssize_t sendto ( socket s , const void * msg, int len, unsigned int flags, const
    4. struct sockaddr * to , int tolen ) ;

    描述

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

    参数

    - s

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

    - msg

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

    - len

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

    - flags

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

    - to

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

    - tolen

    sockaddr的结构长度。

    - 返回值

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

    recvfrom()

    函数结构

    1. #include
    2. #include
    3. ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
    4.                  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

    具体实现看代码:

    1. #include           /* See NOTES */
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. /*
    13. * 服务器端程序
    14. * socket
    15. * bind
    16. * sendto/recvfrom
    17. * close
    18. */
    19. #define PORT 8888
    20. struct Param {
    21.     int sockfd;
    22.     char* buff;
    23.     struct sockaddr* src_addr;
    24. };
    25. // 接收数据在子线程中处理
    26. void* Receive_data(void* Param1)
    27. {
    28.     struct Param Param2 = *(struct Param*)Param1;
    29.     while (1)
    30.     {
    31.         // 接收数据
    32.         int addr_len = sizeof(Param2.src_addr);
    33.         int iRecvLen = recvfrom(Param2.sockfd, Param2.buff, sizeof(Param2.buff), 0, Param2.src_addr, &addr_len);
    34.         if (iRecvLen > 0)
    35.         {
    36.             Param2.buff[iRecvLen] = '\0';
    37.             // inet_ntoa(tSocketClientAddr.sin_addr)是将IP地址转换为字符串的函数
    38.             printf("Get Msg From Client: %s: %s\n", inet_ntoa(((struct sockaddr_in*)Param2.src_addr)->sin_addr), Param2.buff);
    39.         }
    40.     }
    41.    
    42. }
    43. int main(int argc, char** argv)
    44. {
    45.     int isocketfd;
    46.     int Client_socketfd;
    47.     int ret;
    48.     struct sockaddr_in my_addr;
    49.     struct sockaddr_in Client_addr;
    50.     char send_buf[1024];
    51.     char buff[1000];
    52.     struct Param Param1;
    53.     pthread_t ntid;
    54.     // SOCK_DGRAM是UDP协议,AF_INET表示IPv4协议
    55.     isocketfd = socket(AF_INET, SOCK_DGRAM, 0);
    56.     if (-1 == isocketfd)
    57.     {
    58.         printf("create socket failed!\n");
    59.         return -1;
    60.     }
    61.     // 配置bind函数的地址信息
    62.     my_addr.sin_family      = AF_INET;  //指定协议族为IPV4版本的TCP/IP协议族
    63.     my_addr.sin_port        = htons(PORT);  //指定端口号(和客户端通信的端口号,两者必须一致)
    64.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //指定IP地址,这里设置为INADDR_ANY,表示可以接收任何来源的连接请求
    65.     ret = bind(isocketfd, (const struct sockaddr*)&my_addr,sizeof(struct sockaddr));
    66.     if (-1 == ret)
    67.     {
    68.         printf("bind socket failed!\n");
    69.         return -1;
    70.     }
    71.     Param1.sockfd = isocketfd;
    72.     Param1.buff = buff;
    73.     Param1.src_addr = (struct sockaddr*)&Client_addr;
    74.     ret = pthread_create(&ntid, NULL, Receive_data, &Param1);
    75.     if (ret)
    76.     {
    77.         printf("create pthread failed!\n");
    78.         return -1;
    79.     }
    80.     while (1)
    81.     {
    82.         // 发送数据
    83.         if (fgets(send_buf, sizeof(send_buf), stdin))
    84.         {
    85.             sendto(isocketfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&Client_addr, sizeof(Client_addr));
    86.         }
    87.        
    88.        
    89.     }
    90.     close(isocketfd);
    91.     return 0;
    92. }

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

    图 5服务器端测试结果

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

    client 程序

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

    图 6client

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

    1. #include           /* See NOTES */
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. /*
    13. * UDP客户端程序
    14. * socket
    15. * send
    16. * close
    17. */
    18. #define PORT 8888
    19. // 线程参数结构体
    20. struct Param {
    21.     int sockfd;
    22.     char* buff;
    23.     struct sockaddr* src_addr;
    24. };
    25. // 接收数据在子线程中处理
    26. void* Receive_data(void* Param1)
    27. {
    28.     struct Param Param2 = *(struct Param*)Param1;
    29.     while (1)
    30.     {
    31.         // 接收数据
    32.         int addr_len = sizeof(Param2.src_addr);
    33.         int iRecvLen = recvfrom(Param2.sockfd, Param2.buff, sizeof(Param2.buff), 0, Param2.src_addr, &addr_len);
    34.         if (iRecvLen > 0)
    35.         {
    36.             Param2.buff[iRecvLen] = '\0';
    37.             // inet_ntoa(tSocketClientAddr.sin_addr)是将IP地址转换为字符串的函数
    38.             printf("Get Msg From Client: %s: %s\n", inet_ntoa(((struct sockaddr_in*)Param2.src_addr)->sin_addr), Param2.buff);
    39.         }
    40.         else if (iRecvLen == -1)
    41.         {
    42.             perror("recvfrom failed");
    43.             break;
    44.         }
    45.     }
    46. }
    47. int main(int argc, char** argv)
    48. {
    49.     int isocketfd;
    50.     struct sockaddr_in client_addr;
    51.     char send_buf[1024];
    52.     struct Param Param1;
    53.     pthread_t ntid;
    54.     char reve_buff[1024];
    55.     memset(send_buf, 0, sizeof(send_buf));
    56.     if (argc < 2)
    57.     {
    58.         printf("Usage: %s ip_address\n", argv[0]);
    59.         return -1;
    60.     }
    61.     client_addr.sin_family = AF_INET;  //指定协议族为IPV4版本的TCP/IP协议族
    62.     client_addr.sin_port = htons(PORT);  //指定端口号
    63.     //指定IP地址,htons()函数是将一个本地字节序的short转为网络字节序的short
    64.     client_addr.sin_addr.s_addr = inet_addr(argv[1]);
    65.     isocketfd = socket(AF_INET, SOCK_DGRAM, 0);
    66.     if (-1 == isocketfd)
    67.     {
    68.         printf("create socket failed!\n");
    69.         return -1;
    70.     }
    71.     Param1.sockfd = isocketfd;
    72.     Param1.buff = reve_buff;
    73.     Param1.src_addr = (struct sockaddr*)&client_addr;
    74.     int ret = pthread_create(&ntid, NULL, Receive_data, &Param1);
    75.     if (ret)
    76.     {
    77.         printf("create pthread failed!\n");
    78.         return -1;
    79.     }
    80.     while (1)
    81.     {
    82.         if (fgets(send_buf, sizeof(send_buf), stdin))
    83.         {
    84.             sendto(isocketfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&client_addr, sizeof(client_addr));
    85.         }
    86.     }
    87.     return 0;
    88. }

    图 7客户端测试结果

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

    pthread_create()

    函数结构

    1. #include
    2. 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,失败:返回错误码。

  • 相关阅读:
    Linux:多线程概念 | Windows的线程 | 线程的优缺点 | 进程与线程 | 线程控制 | 线程创建 | 线程终止 | 线程等待 | 分离线程
    批处理文件(.bat)中,dir与tree命令的效果
    关于 SAP 电商云 Spartacus UI Navigation Service 执行的一些明细
    模型部署 — PaddleNLP 基于 Paddle Serving 快速使用(服务化部署 - Docker)— 图像识别 + 信息抽取(UIE-X)
    MySQL基本操作之数据库设计理论
    738. 单调递增的数字
    【Java】Java易丢失的基础知识一
    C# 异步编程Invoke、beginInvoke、endInvoke的用法和作用
    上手Python之列表
    刷脸支付对旅游行业的影响
  • 原文地址:https://blog.csdn.net/only_print/article/details/142168679