Linux 网络编程——UDP编程

来源:互联网 发布:mysql create database 编辑:程序博客网 时间:2024/09/21 06:17

概述

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


UDP 有如下的特点:

1)邮件系统服务模式的抽象(可通过邮件模型来进行对比)

2)每个分组都携带完整的目的地址

3)发送数据之前不需要建立链接

4)不对数据包的顺序进行检查,不能保证分组的先后顺序

5)不进行分组出错的恢复和重传

6)不保证数据传输的可靠性



在网络质量令人十分不满意的环境下,UDP 协议数据包丢失会比较严重。但是由于 UDP 的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用 UDP 较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。比如我们聊天用的 ICQ 和 QQ 就是使用的 UDP 协议。


UDP 编程的 C/S 架构



UDP 客户端程序

对比于写信模型,客户端相当于寄信人,要想成功给人寄信,信封上必须写上对方的地址。

ssize_t sendto(   int sockfd,

const void *buf,

size_t nbytes,

int flags,

const struct sockaddr *to,        

socklen_t addrlen );

功能

向 to 结构体指针中指定的 ip,发送 UDP 数据,可以发送 0 长度的 UDP 数据包

参数

sockfd:套接字

buf:发送数据缓冲区

nbytes:发送数据缓冲区的大小

flags:一般为 0

to:指向目的主机地址结构体的指针

addrlen:to 所指向内容的长度

返回值

成功:发送数据的长度

失败: -1


这里通过 Windows 的网络调试助手和虚拟机中的 ubuntu 客户端程序进行通信,网络调试助手下载请点此处。


Windows 的网络调试助手作为服务器,接收客户端的请求,调试助手配置如下:



对于 UDP客户端编程流程, 有点类似于写信过程找个邮政工作人员( socket() ->信封上写上地址同时里面装上信件内容并且投递(sendto() )-> ……还可以继续写信,或者,接收对方的回信(recvfrom() )……->打完收工(close() )。


虚拟机中 ubuntu 的 UDP 客户端程序:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8.   
  9. int main(int argc, char *argv[])  
  10. {  
  11.     unsigned short port = 8080; //服务器端口  
  12.     char *server_ip = "10.221.20.10";   //服务器ip地址  
  13.       
  14.     if( argc > 1 )   // main函数传参,服务器ip地址  
  15.     {     
  16.         server_ip = argv[1];  
  17.     }  
  18.       
  19.     if( argc > 2 )   // main函数传参,服务器端口  
  20.     {  
  21.         port = atoi(argv[2]);  
  22.     }  
  23.   
  24.     int sockfd;  
  25.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);   //创建UDP套接字  
  26.     if(sockfd < 0)  
  27.     {  
  28.         perror("socket");  
  29.         exit(-1);  
  30.     }  
  31.       
  32.     // 套接字地址  
  33.     struct sockaddr_in dest_addr;  
  34.     bzero(&dest_addr, sizeof(dest_addr));   // 清空内容  
  35.     dest_addr.sin_family = AF_INET;     // ipv4  
  36.     dest_addr.sin_port   = htons(port); // 端口转换  
  37.     inet_pton(AF_INET, server_ip, &dest_addr.sin_addr); // ip地址转换  
  38.   
  39.     printf("send data to UDP server %s:%d!\n", server_ip, port);  
  40.       
  41.     while(1)  
  42.     {  
  43.         char send_buf[512] = "";  
  44.         fgets(send_buf, sizeof(send_buf), stdin);//获取输入  
  45.         send_buf[strlen(send_buf)-1] = '\0';  
  46.         //发送数据  
  47.         int len = sendto(sockfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));  
  48.         printf("len = %d\n", len);  
  49.     }  
  50.       
  51.     close(sockfd);  
  52.     return 0;  
  53. }  

运行结果如下:


UDP 客户端注意点

1)本地IP、本地端口(我是谁)

2)目的IP、目的端口(发给谁)

3)在客户端的代码中,我们只设置了目的IP、目的端口

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


UDP 服务器程序

UDP网络程序想要收取数据需什么条件?

1)确定的 ip 地址

2)确定的端口(port)

这正如,我要收到别人寄过来的信,我必须告诉别人我的地址(ip),同时告诉别人我我的公寓信箱号(端口)。


接收端使用 bind() 函数,来完成地址结构与 socket 套接字的绑定,这样 ip、port 就固定了,发送端在 sendto 函数中指定接收端的 ip、port,就可以发送数据了。


需要头文件:#include <sys/socket.h>

int bind(    int sockfd,

const struct sockaddr *myaddr,

socklen_t addrlen );

功能

将本地协议地址与 sockfd 绑定,这样 ip、port 就固定了

参数

sockfd:socket 套接字

myaddr: 指向特定协议的地址结构指针

addrlen:该地址结构的长度

返回值

成功:返回 0

失败:-1


使用实例如下:

[cpp] view plain copy
  1. // 本地网络地址  
  2. struct sockaddr_in my_addr;  
  3. bzero(&my_addr, sizeof(my_addr));   // 清空结构体内容  
  4. my_addr.sin_family = AF_INET;   // ipv4  
  5. my_addr.sin_port   = htons(port);   // 端口转换  
  6. my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定网卡所有ip地址,INADDR_ANY为通配地址,值为0  
  7.   
  8. printf("Binding server to port %d\n", port);  
  9. int err_log;  
  10. err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); // 绑定  
  11. if(err_log != 0)  
  12. {  
  13.     perror("bind");  
  14.     close(sockfd);        
  15.     exit(-1);  
  16. }  


绑定端口有些需要注意的问题,请看《绑定( bind )端口需要注意的问题》。


ssize_t recvfrom( int sockfd, 

void *buf, 

size_t nbytes,

int flags,

struct sockaddr *from, 

socklen_t *addrlen );

功能

接收 UDP 数据,并将源地址信息保存在 from 指向的结构中,默认的情况下,如果没有接收到数据,这个函数会阻塞,直到有数据到来。

参数

sockfd:套接字

buf接收数据缓冲区

nbytes:接收数据缓冲区的大小

flags套接字标志(常为 0)

from源地址结构体指针,用来保存数据的来源

addrlen:from 所指内容的长度

返回值

成功:接收到的长度

失败: -1


对于 UDP 服务器编程流程, 有点类似于收信过程:找个邮政工作人员( socket() ) -> 确定信箱的位置:地址+信箱号(bind() )-> 等待对方的来信( recvfrom() )-> ……还可以回信(write() ),或者,继续等待对方的来信……


ubuntu 中的服务器程序如下:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8. int main(int argc, char *argv[])  
  9. {  
  10.     unsigned short port = 8000;     // 本地端口  
  11.     if(argc > 1)  
  12.     {  
  13.         port = atoi(argv[1]);  
  14.     }  
  15.   
  16.     int sockfd;  
  17.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);    // 创建套接字  
  18.     if(sockfd < 0)  
  19.     {  
  20.         perror("socket");  
  21.         exit(-1);  
  22.     }  
  23.       
  24.     // 本地网络地址  
  25.     struct sockaddr_in my_addr;  
  26.     bzero(&my_addr, sizeof(my_addr));   // 清空结构体内容  
  27.     my_addr.sin_family = AF_INET;   // ipv4  
  28.     my_addr.sin_port   = htons(port);   // 端口转换  
  29.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定网卡所有ip地址,INADDR_ANY为通配地址,值为0  
  30.       
  31.     printf("Binding server to port %d\n", port);  
  32.     int err_log;  
  33.     err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); // 绑定  
  34.     if(err_log != 0)  
  35.     {  
  36.         perror("bind");  
  37.         close(sockfd);        
  38.         exit(-1);  
  39.     }  
  40.       
  41.     printf("receive data...\n");  
  42.     while(1)  
  43.     {  
  44.         int recv_len;  
  45.         char recv_buf[512] = "";  
  46.         struct sockaddr_in client_addr;  
  47.         char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16  
  48.         socklen_t cliaddr_len = sizeof(client_addr);   
  49.           
  50.         // 接受数据  
  51.         recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);  
  52.         inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
  53.         printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));  
  54.         printf("data(%d):%s\n",recv_len,recv_buf);  
  55.     }  
  56.       
  57.     close(sockfd);  
  58.     return 0;  
  59. }  


Windows 的网络调试助手作为客户端,给 ubuntu 中的服务器发送数据,调试助手配置如下:


运行结果如下:



示例代码下载请点此处。


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 孕中期吃了荠菜怎么办 孕妇吃了荠荠菜怎么办 刚怀孕吃了荠菜怎么办 孕妇误吃了荠菜怎么办 手刮了芋头很痒怎么办 手弄了芋头很痒怎么办 削完芋头皮手痒怎么办 洗完芋头后手痒怎么办 芋头的汁非常痒怎么办 孕妇吃了木耳菜怎么办 宝宝体检本丢了怎么办 家具长霉长虫了怎么办 喝了发霉的水怎么办 饭店刚开业生意不好怎么办 牙不能咬硬东西怎么办 甲鱼头不伸出来怎么办 咸鸭蛋腌的太咸怎么办 煮熟的鸭蛋不咸怎么办 羊腰子上的肥油怎么办 高漫sai没有压感怎么办 sai上面没有笔压怎么办 pr视频导不出来怎么办 脏辫头发长长了怎么办 脏辫发根长出来怎么办 白鞋前面踢破了怎么办 皮鞋破了一点皮怎么办 白色皮鞋破皮了怎么办 买的鞋鞋底太滑怎么办 鞋底磨平了很滑怎么办 包体马桶盖松了怎么办 箭牌马桶盖松了怎么办 送丝软管堵了怎么办 钢笔干了不出水怎么办 凌美钢笔不出水怎么办 新钢笔写不出水怎么办 新买的钢笔太粗怎么办 门锁螺丝拧花了怎么办 手机螺丝拧花了怎么办 电脑螺丝滑丝了怎么办 打了羽毛球手痛怎么办 小孩屁股打红了怎么办