Linux select api
linux select api 4.14.0.rc4
https://www.kernel.org/doc/html/latest/media/uapi/v4l/func-select.html?highlight=select
select Synchronous I/O multiplexing
select系统调用原型
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
Description:
使用select()函数,应用程序可以暂停执行,直到驱动程序捕获数据或准备接受数据进行输出。
当streaming I/O 已协商时,此功能等待直到缓冲区已被填充或显示,并可以使用VIDIOC_DQBUF ioctl出队。当缓冲区已经在驱动程序的传出队列中时,该函数立即返回。
On success select() returns the total number of bits set in struct fd_set(). When the function timed out it returns a value of zero. On failure it returns -1 and the errno variable is set appropriately. When the application did not call ioctl VIDIOC_QBUF, VIDIOC_DQBUF or ioctl VIDIOC_STREAMON, VIDIOC_STREAMOFF yet the select() function succeeds, setting the bit of the file descriptor in readfds or writefds, but subsequent VIDIOC_DQBUF calls will fail. [1]
当使用read()函数并且驱动程序尚未捕获时,select()函数开始捕获。当失败时,select()返回成功,并且随后的read()调用也将尝试开始捕获,将返回相应的错误代码。当驱动程序连续捕获(与例如静止图像相反)并且数据已经可用时,select()函数立即返回。
当使用write()函数已经协商时,select()函数只是等待直到驱动程序准备好进行非阻塞的write()调用。
实现read()或write()函数或流I / O的所有驱动程序也必须支持select()函数。
有关详细信息,请参阅select()手册页。
[1] The Linux kernel implements select() like the poll() function, but select() cannot return a POLLERR.
Return Value:
EBADF
One or more of the file descriptor sets specified a file descriptor that is not open.
EBUSY
The driver does not support multiple read or write streams and the device is already in use.
EFAULT
The readfds, writefds, exceptfds or timeout pointer references an inaccessible memory area.
EINTR
The call was interrupted by a signal.
EINVAL
The nfds argument is less than zero or greater than FD_SETSIZE.
https://www.kernel.org/doc/html/latest/media/uapi/v4l/func-poll.html?highlight=poll
#include <sys/poll.h>
int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
https://www.cnblogs.com/luoxn28/p/6220372.html
1、基本知识
epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
2、epoll接口
epoll操作过程需要三个接口,分别如下:
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
(1) int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
(3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
3、工作模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
socket api 参数
一、套接字数据结构
1、通用套接字地址
struct sockaddr
{
sa_family_t sa_family; //通信类型,对于IPV4为AF_INET
char sa_data[14]; //用来保存IP地址和端口信息,一般不用
}
2、IPV4套接字地址
struct sockaddr_in
{
unsigned short sin_len;//IPV4地址长度
sa_family_tsin_family;//通信类型
unsigned short int sin_port;//端口号
struct in_addr sin_addr;//IP地址
unsigned char sin_zero[8];//补充字段
}
其中struct in_addr为:
struct in_addr
{
uint32_t s_addr;//32位IP地址,网络字节顺序
}
3、hostent
struct hostent
{
char* h_name;//主机的正式名
char** h_aliases;//主机的别名
int h_addrtype;//主机的地址类型,IPV4为AF_INET
int h_length;//地址长度,IPV4为32
char** h_addr_list;//主机的IP地址列表
}
#define h_addr h_addr_list[0]//主机的第一个IP地址
POSIX中的数据类型
数据类型说明头文件
int8_t带符号的8位整数<sys/types.h>
uint8_t无符号的8位整数<sys/types.h>
int16_t带符号的16位整数<sys/types.h>
uint16_t无符号的16位整数<sys/types.h>
int32_t带符号的32位整数<sys/types.h>
uint32_t无符号的32位整数<sys/types.h>
sa_family_t套接字地址结构的地址族<sys/socket.h>
socklen_t套接字地址结构的长度,一般为uint32_t<sys/socket.h>
int_port_tTCP或者UDP端口号,一般为uint16_t<netinet/in.h>
in_addr_tIPV4地址,一般为uint32_t<netinet/in.h>
二、基础函数
1、主机字节序和网络字节序
#include<netinet/in.h>
uint32_t htonl(uint32_t hostlong)
uint16_t htons(uint16_t hostsho以上rt)
以上两个函数返回网络字节序
uint32_t ntohl(uint32_t netlong)
uint16_t ntohs(uint16_t netshort)
以上两个函数返回主机字节序
2、字节操作函数
#include<string.h>
void memset(void* dest,int c,size_t len)
void memcpy(void* dest,const void* src,size_t nbytes)
int memcmp(const void* ptr1,const void* ptr2,size_t nbytes)
3、整数与IP地址转换
TCP/IP中的IP地址是以''.''隔开的十进制的数,而套接字中用的是32位的网络字节序的二进制数值。
#include<arpa/inet.h>
int inet_aton(const char* straddr,struct in_addr* addrptr)
若成功则返回1,否则返回0
参数:straddr为点分十进制字符串,结果保存在addrptr所指的内存中
char* inet_ntoa(struct in_addr inaddr)
若成功则返回点分十进制数串的指针,否则返回NULL
参数:inaddr为32位网络字节的整数,返回点分十进制数串的指针
in_addr_t inet_addr(const char* straddr)
若成功则返回32位的二进制网络字节序的地址,否则返回INADDR_NONE(表示一个不存在的IP地址,其实就是255.255.255.255,就是-1)
参数:straddr为点分十进制字符串,返回为32位的二进制网络字节序的地址
4、域名与IP地址的转换
#include<netdb.h>
struct hostent* gethostbyname(const char* hostname)
struct hostent* gethostbyaddr(const char* addr,size_t len,int family)
若成功返回hostent结构指针,否则返回空指针,同时设置h_errno,可以调用hstrerror()函数获取h_errno的值
h_errno的取值:
HOST_NOT_FOUND找不到主机
TRY_AGAIN出错重试
NO_RECOVERY不可修复性错误
NO_DATA指定的名字有效,但是没有记录
5、其他常用函数:
若IP地址设为INADDR_ANY则表示本机IP,这时可以在浏览器中输入
http://localhost:端口号/
作为客户端向服务器发出链接请求
一般出错信息可以用perror来输出
void perror(const char* s)
该函数除了会输出字符串s,还会输出错误原因
三、TCP编程
头文件:#include<sys/socket.h>
服务器流程:socket()→bind()→listen()→accept()→recv()<═>send()→close()
客户端流程:socket()→connect()→send()<═>recv()→close()
1、创建套接字
int socket(int family,int type,int ptotocol)
若成功则返回套接字描述符,否则返回-1
参数:family取值:PF_INET(等价于AF_INET)、PF_INET6(等价于AF_INET6)
(PF代表Protocl Family协议族,AF代表Address Family地址族)
type取值:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW
ptotocol一般取0
2、绑定端口
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen)
若成功返回0,否则返回-1
若出错,则可以在errno中捕获:
EBADF:参数sockfd不合法
EACCEDD:权限不足
ENOTSOCK:参数sockfd是一个文件描述符,而不是socket
3、等待监听
int listen(int sockfd,int backlog)
若成功则返回0,否则返回-1
参数:sockfd表示要监听的套接字,backlog表示最多同时处理的请求数(若为IPV4则最多为128),若超过这个个数,则客户端会受到ECONNREFUSED错误
若出错,则可以在errno中捕获:
EBADF:参数sockfd不合法
EACCEDD:权限不足
EOPNOTSUPP:指定的socket不支持listen
4、接受链接
int accept(int sockfd,struct sockaddr* addr,socketlen_t* addrlen)
若成功则返回新的套接字,否则返回-1
参数:sockfd表示处于监听的套接字,addr表示客户端的信息
accept接受一个链接时会返回一个新的套接字,以后的数据传输就用这个新的套接字处理若出错,则可以用errno捕获:
EBADF:参数sockfd不合法
EFAULT:参数addr指针指向无法存取的空间
ENOTSOCK:参数sockfd是一个文件描述符,而不是socket
EOPNOTSUPP:制定的socket不是SOCK_STREAM
EPERM:防火墙拒绝这个链接
ENOBUFS:系统缓冲内存不足
ENOMEM:核心内存不足
5、请求链接
int connect(int sockfd,const struct sockaddr* serv_addr,int addrlen)
若成功返回0,否则返回-1
参数:sockfd表示已经建立好的套接字,serv_addr保存服务器信息
若出错,可以在errno中捕获:
EBADF:参数sockfd不合法
EFAULT:参数addr指针指向无法存取的空间
ENOTSOCK:参数sockfd是一个文件描述符,而不是socket
EISCONN:参数sockfd的套接字已经处于链接状态
ECONNREFUSED:链接请求被拒绝
ETIMEDOUT:超时
ENETUNREACH:无法传送数据包到指定主机
EAFNOSUPPORT:sockaddr结构的sa_family不正确
EALREADY:socket不能阻断,但是以前的链接操作还未完成
6、数据发送和接收
int send(int sockfd,const void* buf,int len,unsigned int flags)
int recv(int sockfd,void* buf,int len,unsigned int flags)
若成功则返回已经发送或者接收的字节数,否则返回-1
flags一般置0
若出错,可以在errno中捕获:
EBADF:参数sockfd不合法
EFAULT:参数addr指针指向无法存取的空间
ENOTSOCK:参数sockfd是一个文件描述符,而不是socket
EINTR:进程被信号中断
EAGAIN:此动作会中断进程,但参数sockfd的套接字不可中断
ENOBUFS:系统的缓冲内存不足
ENOMEM:核心内存不足
EINVAL:函数参数不正确
7、write和read函数
#include<unistd.h>
ssize_t write(int fd,const void* buf,size_t count)
ssize_t read(int fd,void* buf,size_t count)
若成功返回写入和读取的字节数,否则返回-1
注意:write等价于send,read等价于recv
8、关闭套接字
#include<unistd.h>
int close(int fd)
若成功返回0,否则返回-1