UDP概述
UDP 是 User Datagram Protocol 的简称, 中文名是用户数据报协议,是一个简单的面向数据报的传输层协议,在网络中用于处理数据包,是一种无连接的协议。UDP 不提供可靠性的传输,它只是把应用程序传给 IP 层的数据报发送出去,但是并不能保证它们能到达目的地。由于 UDP 在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
UDP编程C/S结构
udp编程中的常用函数
1、sendto()函数的使用
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void * buf, size_t len , int flags, const struct sockaddr * dest_addr , socklen_t addrlen);
(1)功能:向指定的数据接收端发送指定的数据。
(2)返回值:成功,返回实际发送的字节数。失败,返回-1.
(3)参数:
----sockfd:发送操作使用的套接字文件描述符
-----buf:要发送的数据在内存中的首地址
-----len:要发送的数据的长度
------flags:发送标志,一般为0
------dest_addr:数据接收端的地址(包含IP地址和端口号)的结构体指针
-------addrlen:数据接收端地址结构体的大小
2、recvfrom()函数的使用
#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);
(1)功能:接收数据发送端到达的数据
(2)返回值:成功,返回实际接收到的字节数。失败返回-1
(3)参数:
-----sockfd:接收操作使用的套接字文件描述符
-----buf:接收到的数据存放在内存中的位置
------len:指buf缓冲区的大小,即期望接收的最大数据的长度
-----flags:接收标志,一般为0
-----src_addr:指向的结构体将被数据发送端的地址(含IP地址和端口号)所填充
-----addrlen:所指向的存储位置,调用前应填入src_addr和addrlen的结构体大小,调用后则将被填入发送端的地址的实际大小
备注:若不需要发送端的IP地址和端口号,可以将src_addr和addrlen都设置为NULL
i/o模型和多路复用
在linux/unix下主要有4种I/O模型:
- 阻塞I/O:最常用
- 非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询
- I/O 多路复用:允许同时多个I./0进行控制
- 信号驱动I/O:一种异步通信模型
阻塞I/O模式
阻塞I/O模式是最普遍使用的I/O模式,大部分程序使用的都是阻塞模式的i/o
缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式,很多读写函数在调用过程中会发生阻塞;
许多函数在调用过程中都会发生阻塞:
- 读操作中的read,recv,recvfrom
- 写操作中的:write,send【sendto不阻塞】
- 其他操作中的:accept,connect
阻塞基本原理
读阻塞
以read为例:
- 进程调用read函数从套接字上读取数据,当套接字的接受缓冲区中还没有数据可读,函数read将会发生阻塞
- 他会一直阻塞下去,等待套接字的接受缓冲区中有数据可读,
- 经过一段时间后,缓冲区内接受到数据,于是内核边去唤醒该进程,通过read访问这些数据
- 如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去
写阻塞
- 在写操作是发生阻塞的情况要比读操作少,主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。
- 这时,写操作不进行热河的拷贝工作,将发生阻塞
- 一旦发送缓冲区有足够的空间,内核将唤醒进程,将数据从用户缓冲区拷贝到相应的发送数据缓冲区。
- UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议汇总不存在发送缓冲区满的情况,在udp套接字上的执行操作永远也不会阻塞
非阻塞模式I/O
- 当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核"当我请求的I/O操作不能马上完成,你想让我们的进程进行休眠等待时,不要这么做,请返回一个错误给我"。
- 当一个应用程序是用来非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符数据可读(polling)
- 应用程序不停地polling 内核来检查是否I/O操作已经就绪,这将是一个季度浪费CPU资源的操作
- 这种模式使用不普遍
非阻塞模式的实现
fcnl()函数
int fcnl(nt fd, int cmd,long arg);
int flag;
falg=fcntl(sockfd, F_GETEL 0);
flag |=0_NONBLOCK;
fcnl(sockefd,F_SETEL,flag);
int b_on=1;
ioct1(sock_fd,FIONBIO,&b_on);
多路复用I/O
-
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的。
-
若采用非阻塞模式,对多个输入进行轮询,但又台浪费CPU时间;
-
若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂。
-
比较好的方法就是使用I/O复用,其基本思想是:
- 先构造一张描述符的表,然后调用一个函数,当这些文件描述符中的一个或者多个已经准备好进行I/O时函数才返回
- 函数返回时告诉进程那个描述符已经就绪,可以进行I/O操作。
基本常识:
linux每个进程默认最多可以打开1024个文件,最多有1024个文件描述符
文件描述符的特点:
- 非负整数
- 从最小可用的数字分配
- 每个进程启动时默认打开0,1,2三个文件描述符
多路复用针对布置套接字fd,也针对普通的文件描述fd
select函数
使用select函数可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
非阻塞方式:non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生,则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。
设置文件描述符
select可以同时监视多个文件描述符(套接字)。
此时需要先将文件描述符集中到一起。集中时也要按照监视项(接收,传输,异常)进行区分,即按照上述3种监视项分成三类。
使用fd_set数组变量执行此项操作,该数组是存有0和1的位数组。
数组是从下标0开始,最左端的位表示文件描述符0。如果该位值为1,则表示该文件描述符是监视对象。
“是否应当通过文件描述符的数字直接将值注册到fd_set变量?”
当然不是!操作fd_set的值由如下宏来完成:
FD_ZERO(fd_set* fdset):=将fd_set变量的所有位初始化为0。FD_SET(int fd, fd_set* fdset):在参数fd_set指向的变量中注册文件描述符fd的信息。
FD_CLR(int fd, fd_set* fdset):参数fd_set指向的变量中清除文件描述符fd的信息。
FD_ISSET(int fd, fd_set* fdset):若参数fd_set指向的变量中包含文件描述符fd的信息,则返回真。
设置监视范围及超时
select函数:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset,
const struct timeval* timeout);
select函数共有5个参数,其中参数和返回值:
maxfd:监视对象文件描述符数量。
readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值。
writeset: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值。
exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值。
timeout:调用select后,为防止陷入无限阻塞状态,传递超时信息。
返回值:错误返回-1,超时返回0。当关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数。
select函数用来验证3种监视项的变化情况。根据监视项声明3个fd_set变量,分别向其注册文件描述符信息,并把变量的地址传递到函数的第二到第四个参数。但是,在调用select函数前需要决定2件事:
“文件描述符的监视范围是?”
“如何设定select函数的超时时间?”
第一,文件描述符的监视范围与第一个参数有关。
实际上,select函数要求通过第一个参数传递监视对象文件描述符的数量。因此,需要得到注册在fd_set变量中的文件描述符数。但每次新建文件描述符时,其值都会增1,故只需将最大的文件描述符值加1再传递到select函数即可。(加1是因为文件描述符的值从0开始)
第二,超时时间与最后一个参数有关。
其中timeval结构体如下:
struct timeval
{
long tv_sec;
long tv_usec;
};
本来select函数只有在监视文件描述符发生变化时才返回,未发生变化会进入阻塞状态。指定超时时间就是为了防止这种情况发生。
将上述结构体填入时间值,然后将结构体地址值传给select函数的最后一个参数,此时,即使文件描述符中未发生变化,只要过了指定时间,也可以从函数返回。不过这种情况下,select函数返回0。 不想设置超时最后一个参数只需要传递NULL。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)