LINUX网络编程(UDP)

本文深入解析UDP协议特性,探讨其无连接、不可靠、面向数据报的特点及其在视频传输等实时应用的优势。详述socket编程中服务端与客户端的UDP编程步骤,包括套接字创建、绑定、数据收发及关闭过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

udp

用户数据报协议-无连接,不可靠,面向数据报
面向数据报:每条数据有长度标记,整条发,整条收,传输不够灵活,但是不会存在粘包问题
视使用场景:对数据实时要求高,输出视频——保证传输速度
优点:传输速度快 ,无粘包情况
缺点:不可靠

如果使用场景对文件的安全性要求比较高(文件传输),尽量使用tcp,保证数据的可靠;像音乐视频这种对安全性要求不是很高(视频传输),但是实时性,速度快的用udp

socket套接字编程:

网络编程涉及到对网卡的操作,而我们用户无法直接操作,因此操作系统就提供了一套接口来供我们操作——socket接口

网络编程中分了两个端:客户端程序 / 服务端程序
在网络编程中,客户端是主动的一方,并且客户端必须知道服务端的地址信息(ip+port)
并且服务端还必须得在这个制指定的地址上等着别人

udp编程步骤:

服务端
1.创建套接字 :在内核中创建socket结构体;向进程返回一个操作句柄;通过这个内核中的socket结构体与网卡建立联系
2.为套接字绑定地址信息:向内核中的socket结构体添加各种地址描述信息(IP地址和端口)为套接字绑定地址端口,就是为了声明,去网卡接受数据的时候,接收的是哪一部分数据局(因为数据上有可能会有很多的数据)若目的地址信息和我绑定的地址信息相同,则将数据交给我处理
3.接收数据:从socket结构体中的接受缓冲区中取出数据(每个数据中都包含源地址和目的地址,因此获取数据也就获取了对端是谁)
4.发送数据:把数据拷贝到内核中的socket结构体的发送缓冲区中
操作系统这时候会在合适的时候从发送缓冲区中取出数据,然后进行数据的层层封装,最终通过网卡发送出去
5.不通信了,关闭套接字,释放资源

客户端永远都是主动发送数据的一方,意味着客户端必须知道服务端的信息才可以在发送数据的时候,将数据能够层层封装完成(网络传输的数据都应该包括:源IP地址/目的IP地址/源端口/目的端口/协议)

客户端所知道的服务端地址,都是服务端告诉它的——服务端的地址通常都是永久不变的

客户端
1.创建套接字
2.为套接字绑定地址(不主动绑定) 通常客户端不推荐用户手动帮东地址信息
3.发送数据
4.接收数据‘
5.关闭套接字
**
服务端:
1.创建套接字socket()
2. 为套接字绑定地址 bind()
3. 接收数据 recvform
4. 发送数据sendto()
5.关闭套接字 close()//销毁内核中的数据

ip地址动态分配——DHCP
NAT服务——地址替换
IPV6(缺陷:不向下兼容)
IPV4

1、创建套接字
int socket(int domain,int type,int protocol)
domain:地址域——不同的网络地址结构   AF_INET—IPV4
type: 套接字类型
  SOCK_STREAM  流式套接字 (一种有序的,可靠的,双向的,基于连接的字节流传输),默认协议tcp  不支持udp
  SOCK_DGRAM  数据报套接字  (无连接的,不可靠的,有最大长度限制的传输 )默认协议udp  不支持tcp
protocol:协议类型 0:使用套接字默认协议(流式套接字默认是TCP/数据报套接字默认是UDP)
IPPR0TO_TCP=6  tcp协议
IPPROTO_UDP=17  udp协议
返回值:返回套接字的操作句柄——文件描述符  非负整数(套接字描述符)   失败:-1
socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);

2、为套接字绑定地址

int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
sockfd:创建套接字返回的操作句柄
addr:要绑定的地址信息
addrlen:地址信息长度  
返回值:成功:0  失败:-1
用户先定义sockaddr_in的IPV地址结构,强转之后传入binf之中
bind(sockaddr*){
if(sockaddr ->sin_family== AF_INET){
这是IPV4地址结构的解析
}else if(sockaddr->sin_family = =AF_INET6)
}

unit16_t htons(uint16_t hostshort)
将一个短整型(16位)数据从主机字节序转位网络字节序
in_addr_t inet_addr(const char *cp);
将点分十进制字符串的ip地址转换为网络字节序ip地址

3.接收数据

int  recvfrom( int sockfd,char *buf,int len, int flags, struct sockaddr *dest_addr,socklen_t *addr_len)
sockfd: 套接字操作句柄
buf:缓冲区首地址,用buf存储要接收的数据,从内核socket接收缓冲区中取出数据放入这个buf用户缓冲区中
len:想要接收的    长度
flags:发送标志,0-默认阻塞操作(若缓冲区中没有数据则一直等待) MSG_DONTWAIT-非阻塞
MSG_PEEK  接收数据后并不会从缓冲区删除
dest_addr:接收到的数据的源端地址——表示这个数据是谁发的,从哪来的,回复的时候就是对这个地址进行回复
addr_len:输入输出型参数,用于指定指定想要获取多长的地址信息,用于返回地址信息的实际长度
返回值:实际读取到的数据字节长度  失败返回:-1

4.发送数据

i

nt  sendto( int sockfd,char *data,int data_len, int flag, struct sockaddr *dest_addr,socklen_t  addrlen)
socked:套接字操作句柄,发送数据就是将数据拷贝到内核的socket发送缓冲区中
data:要发送的数据首地址
len:要发送的数据长度
flags:选项参数 0-默认阻塞发送    MSG_DONTWAIT-设置为非阻塞
若发送数据的时候,socket发送缓冲区已经满了,则0默认阻塞等待;MSG_DONTWAIT就是立即报错返回了
daddr:目的段地址信息结构——表示数据要发送到哪里去
每一条数据都要描述源端信息(绑定的地址信息)和对端信息(当前赋予的信息)
addrlen:地址信息结构长度
返回值:成功返回实际发送的数据字节数  失败:-1

5.关闭socket

int close(int fd);

字节序转换:
在这里插入图片描述
assign: 从一个字符串中拷贝指定长度数据到string中
buf->assign(tmp,rlen)

udp下服务端编程代码:
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<netinet/in.h>
  6 #include<arpa/inet.h>
  7 #include<sys/socket.h>
  8 
  9 int main(int argc,char*argv[]){
 10     //argc表示参数个数,通过argv向程序传递端口参数
 11     if (argc!=3){
 12         printf("./udp_srv ip port em:./udp_srv 192.168.85.128 9000\n");
 13         return -1;
 14 
 15     }
 16     const char*ip_addr=argv[1];
 17     uint16_t port_addr=atoi(argv[2]);
 18     //socket(地址域,套接字类型,协议类型)
 19     int sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
 20     if(sockfd<0){
    perror("socket error");
 22         return -1;
 23     }
 24     //bind(套接字描述符,地址结构,地址长度)
 25     //struct sockaddr_in IPV4地址结构;
 26     //struct in_addr{uint 32_t s_addr};
 27     struct sockaddr_in addr;
 28      addr.sin_family=AF_INET;
 29     //htons-将两个字节的主机字节序整数转转换成网络字节序的整数
 30     addr.sin_port=htons(port_addr);
 31     //inet_addr 将一个点分十进制的字符串IP地址转换成网络字节序的整数IP地址
 32     addr.sin_addr.s_addr=inet_addr(ip_addr);
 33     socklen_t len=sizeof(struct sockaddr_in);
 34     int ret=bind(sockfd,(struct sockaddr*)&addr,len);
 35     if(ret<0){
 36         perror("bind error");
 37         return -1;
 38     }
 39     while(1){
 40 
 41         char buf[1024]={0};
 42         struct sockaddr_in cliaddr;
 43         socklen_t len=sizeof(struct sockaddr_in);
 44 
 45         //recvfrom(描述符,缓冲区,长度,参数,客户端地址信息,地址信息长度)
 46         //阻塞接收数据,将数据放入buf中,将发送的地址放入cliaddr中
 47         int ret=recvfrom(sockfd,buf,1023,0,(struct sockaddr*)&cliaddr,&len);
 48         if(ret<0){
 49             perror("recvform error");
 50             close(sockfd);//关闭套接字
 51             return -1;
 52     }
 53         printf("client say:%s\n",buf);
 54         printf("server say:");
 55         fflush(stdout);//让用户输入数据,发送给客户端
 56         memset(buf,0x00,1024);//清空buf中的数据
 57         scanf("%s",buf);
 58         //通过sockfd将buf中的数据发送到cliaddr客户端
 59         ret=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&cliaddr,len);
 60         if(ret <0){
 61             perror("sendto error");
 62             close(sockfd);//关闭套接字
 63             return -1;
 64         }
UDP下客户端编程代码:
  1/*封装一个udpsocket类,向外提供简单的接口实现套接字编程*/
   2#include<*iostream*>
  3 #include<*cstdio*>//stdio.h
  4 #include<*string*>//std::string
  5 #include<unistd.h>//close接口
  6 #include<stdlib.h>//atoi接口
  7 #include<netinet/in.h>//地址结构定义
  8 #include<arpa/inet.h>//字节序转换接口
  9 #include<sys/socket.h>//套接字接口

 10 
 11 class UdpSocket{
 12     public:
 13         UdpSocket():_sockfd(-1){
 14         }
 15         //1.创建套接子
 16         //socket(地址域,套接字类型,协议类型)
 17         //
 18         bool Socket(){
 19             _sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
 20             if(_sockfd<0){
 21                 perror("socket error");
 22                 return false;
 23             }
 24             return true;
 25         }
 26 
 27         //2.为套接字绑  定地址信息
 28         //bind(描述符,统一地址信息,地址信息长度)
 29         bool Bind(const std::string&ip,uint32_t port){
 30             struct sockaddr_in addr;
 31             addr.sin_family=AF_INET;//地址域,用于向bind接口表明这是一个IPV4    地址结构
 32             addr.sin_port=htons(port);//网络字节序的端口信息
 33             addr.sin_addr.s_addr=inet_addr(ip.c_str());//网络字节序的IP地址>    信息
 34             socklen_t len=sizeof(struct sockaddr_in);
 35             int ret=bind(_sockfd,(struct sockaddr*)&addr,len);
 36             if (ret<0){
 37                 perror("bind error");
 38                 return false;
 39             }
 40             return true;
 41         }
 42         //3.发送数据
 43 
 44 bool Send(const std::string &data,const std::string &ip,uint16_t port){
 45             //sendto(描述符,数据,长度,选项,对端地址,地址长度)
 46             struct sockaddr_in addr;
 47             addr.sin_family=AF_INET;
 48             addr.sin_port=htons(port);
 49             addr.sin_addr.s_addr=inet_addr(ip.c_str());
 50             int ret;
 51             socklen_t len=sizeof(struct sockaddr_in);
 52 ret=sendto(_sockfd,data.c_str(),data.size(),0,(struct sockaddr*)&addr,len);
 53             if(ret<0){
 54                 perror("sendto error");
 55                 return false;
 56             }
 57             return true;
 58         }
 59         //4.接收数据
 60         //
 61         //输入型参数使用const 引用
 62         //输出型参数使用指针
 63         //输入输出型参数使用引用
 64 bool Recv(std::string *buf,std::string *ip=NULL,uint16_t*port=NULL){
 65             struct sockaddr_in addr;
 66             socklen_t len=sizeof(struct sockaddr_in);
 67             int ret;
 68             char tmp[4096]={0};
 69             ret=recvfrom(_sockfd,tmp,4096,0,(struct sockaddr*)&addr,&len);
 70             if(ret<0){
 71                 perror("recvfrom error");
 72                 return -1;
 73             }
 74             buf->assign(tmp,ret);//给buf申请ret大小的空间,从tmp中拷贝ret长度的数据进去
 75             if(ip!=NULL){
 76                 *ip=inet_ntoa(addr.sin_addr);//将网络字节序整数ip地址转化为字符串地址
 77             }
 78             if(port!=NULL){
 79                 *port=ntohs(addr.sin_port);
 80             }
 81             return true;
 82 
 83             //recvfrom(描述符,缓冲区,数据长度,选项,对端地址,地址长度)
 84 
 85         }
86 
 87         //5.关闭套接字
 88         void Close(){
 89             close(_sockfd);
 90             _sockfd=-1;
 91             ;
 92         }
 93     private:
 94         //贯穿全文的套接字描述符
 95         int _sockfd;
 96 };
 97 //客户端要给服务端发送数据,那么就需要知道服务端的地址信息
 98 //因此通过程序运行参数传入服务器的地址信息
 99 #define CHECK_RET(q) if((q)==false){return -1;}
100 int main(int argc,char*argv[])
101 {
102     if(argc!=3){//argc在这里的作用是判断用户输入的变量是否是下面三个变量("./    udp_cli""192.168.85.128""9000")
103         printf("em: ./udp_cli 192.168.85.128 9000\n");
104         return -1;
105     }
106     std::string ip_addr=argv[1];
107     uint16_t port_addr=atoi(argv[2]);
108     UdpSocket sock;
109     CHECK_RET(sock.Socket());//创建套接子
110     //CHECK_RET(sock.Bind());//绑定地址信息
111     while(1){
112         //获取一个标准输入的数据,进行发送
113         std::cout<<"client say:";
114         fflush(stdout);
115         std::string buf;
116         std::cin>>buf;//获取标准输入的数据
117         sock.Send(buf,ip_addr,port_addr);
118         buf.clear();//清空buf缓冲区
119         sock.Recv(&buf);//因为本身客户端就知道服务端的地址,因此不需要再获取120         std::cout<<"server say:"<<buf<<std::endl;
121     }
 122     sock.Close();
123     return 0;
124 }
125 
126 
                          

客户端建议最好不要手动绑定地址信息,因为绑定了有可能绑定失败(端口被占用),因此,最好把绑定的过程交给操作系统完成,因为操作系统会选择一个当前合适的地址信息进行绑定,这样做可以将失败的可能性降至最低
但是服务器必须绑定地址,因为服务器是被动的一端,必须告诉别人需要将数据发送到哪个地址哪个端口,因此自己就必须接收这个地址这个端口的数据

每个程序绑定的都是自己的网卡地址信息
客户端发送的对端地址信息一定是服务端绑定的地址信息
服务端绑定的地址信息,一定是自己主机上的网卡IP地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值