工业设计网站设计,中国做的电脑系统下载网站,网站快速优化排名排名,软文广告经典案例300大全基础
IP 地址可以在网络环境中唯一标识一台主机。
端口号可以在主机中唯一标识一个进程。
所以在网络环境中唯一标识一个进程可以使用 IP 地址与端口号 Port 。
字节序
TCP/IP协议规定#xff0c;网络数据流应采用大端字节序。
大端#xff1a;低地址存高位#xff0c…基础
IP 地址可以在网络环境中唯一标识一台主机。
端口号可以在主机中唯一标识一个进程。
所以在网络环境中唯一标识一个进程可以使用 IP 地址与端口号 Port 。
字节序
TCP/IP协议规定网络数据流应采用大端字节序。
大端低地址存高位高地址存低位
小端低地址存低位高地址存高位x86采用小端存储。
网络字节序就是在网络中进行传输的字节序列采用的是大端法。
主机字节序就是本地计算机中存储数据采用的字节序列采用的是小端法。
相关 API 函数
#include arpa/inet.h
uint32_t htonl(uint32_t hostlong);//h host n network l long s short
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);#include arpa/inet.h
//点分十进制字符串转换为网络字节序
int inet_pton(int af, const char *src, void *dst);
//网络字节序转换为点分十进制字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h //包含了netinet/in.h后者包含了sys/socket.htypedef uint32_t in_addr_t;
struct in_addr
{in_addr_t s_addr;
};//将cp所指C字符串转换成一个32位的网络字节序二进制值并通过inp指针来存储成功返回1失败返回0
int inet_aton(const char *cp, struct in_addr *inp);//将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串,由该函数的返回值所指向的
//字符串驻留在静态内存中,这意味着该函数是不可重入的
char *inet_ntoa(struct in_addr in);//inet_addr函数转换网络主机地址如192.168.1.10)为网络字节序二进制值如果参数char
//*cp无效函数返回-1(INADDR_NONE),这个函数在处理地址为255.255.255.255时也返回
//1,255.255.255.255是一个有效的地址不过inet_addr无法处理;
//返回值为32位的网络字节序二进制
in_addr_t inet_addr(const char *cp);//ok
in_addr_t inet_network(const char *cp);
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);#include sys/socket.h
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);常用结构体
man 7 ip 可以查看相应的结构体也可以使用命令sudo grep -rn struct sockaddr_in { /usr 进行搜索。 struct sockaddr
{sa_family_t sa_family; /* address family, AF_xxx */char sa_data[14]; /* 14 bytes of protocol address */
};struct sockaddr_in
{sa_family_t sin_family; /* address family: AF_INET */in_port_t sin_port; /* port in network byte order */struct in_addr sin_addr; /* internet address */
};/* Internet address. */
struct in_addr
{uint32_t s_addr; /* address in network byte order */
};IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中IPv4 地址用 sockaddr_in 结构体表示IPv6 地址使用 sockaddr_in6 结构体表示。UNIX Domain Socket 的地址格式定义在 sys/un.h 中使用 sockaddr_un 结构体表示。所有的地址类型分别定义为常数 AF_INET 、AF_INET6 、AF_UNIX。
struct sockaddr_in addr;
addr.sin_family AF_INET/AF_INET6/AF_UNIX;
addr.sin_port htons/ntohs;
addr.sin_addr.s_addr htonl/ntohl/inet_pton/inet_ntop网络编程相关函数
socket 函数
创建套接字:
#include sys/types.h /* See NOTES */
#include sys/socket.h//创建套接字函数
int socket(int domain, int type, int protocol);domain:AF_INET/AF_INET6/AF_UNIX
type:SOCK_STREAM/SOCK_DGRAM 前者默认是TCP后者默认是UDP
protocol:传0表示使用默认协议
//函数返回值
成功返回指向新创建的socket的文件描述符失败返回-1设置errnobind 函数
因为服务器程序所监听的网络地址与端口号是固定不变的所以需要使用bind函数进行绑定。bind 函数将 sockfd 与 addr 绑定在一起使 sockfd 这个用于网络通讯的文件描述符监听 addr 所描述的地址和端口号。
绑定 IP 与端口号:
#include sys/types.h /* See NOTES */
#include sys/socket.hint bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//绑定服务的端口号与IP地址
sockfd:上面socket创建的套接字
addr所要绑定的ip地址与端口号
addrlen:前面addr结构体的长度
//函数返回值
成功返回指向新创建的socket的文件描述符失败返回-1设置errnolisten 函数
用来指定监听上限数值允许同时多少个客户端与服务器建立连接指定最大同时发起连接数。
#include sys/types.h /* See NOTES */
#include sys/socket.hint listen(int sockfd, int backlog);sockfd:socket创建的文件描述符
backlog:排队建立3次握手队列和刚刚建立3次握手队列的连接数和。可以使用命令进行最大发起连接数限定值的查看
cat /proc/sys/net/ipv4/tcp_max_syn_backlog 1accept 函数
接收连接请求的函数阻塞等待客户端发起连接。
如果客户端还没有来得及连接此时 accept 函数会处于阻塞状态。
#include sys/types.h /* See NOTES */
#include sys/socket.hint accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd:socket创建的文件描述符
addr传出参数返回连接客户端地址信息包含IP地址与端口号
addrlen:传入传出参数值-结果传入sizeof(addr)大小函数返回时返回真正接收到地址结构
体的大小。
//函数返回值
成功返回一个新的socket文件描述符用于和客户端通信失败返回-1设置errnoconnect 函数
客户端调用该函数连接到服务器上。
发起连接
#include sys/types.h /* See NOTES */
#include sys/socket.hint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfd是客户端自己使用socket得到的文件描述符。
addr:传入参数指定服务器端地址信息包含IP地址与端口号
addrlen:传入参数传入sizeof(addr)大小
返回值成功返回0失败返回-1设置errno客户端需要调用 connect 连接服务器connect 和 bind 的采纳数形式一致区别在于 bind 的参数是自己的地址而 connect 的参数是对方的地址。
close 函数
关闭套接字创建的文件描述符。
#include unistd.hint close(int fd);客户端其实也是需要 bind 端口号与 IP 地址如果没有显示绑定的话操作系统会自动分配一个 IP 地址与端口号。但是服务器是不能不使用 bind 函数让操作系统随机分配 IP 地址与端口号这样的话客户端就不知道服务器的 IP 地址与端口号就不知道怎么连接到服务器上了也不知道连接到那个服务器上。
本地随机的有效数字类型的 IPINADDR_ANY。
INADDR_ANY解析转换过来就是 0.0.0.0泛指本机的意思表示本机的所有IP因为有些电脑不止一块网卡如果某个应用程序只监听某个端口那么其他端口过来的数据就接收不了。
网络编程代码
逻辑示例图 端口复用
让同一个端口可以进行重复使用不至于等待 2MSL的时间
#include sys/types.h
#include sys/socket.hint getsockopt(int sockfd, int level, int optname, void* optval, socklen_t* optlen);int setsockopt(int sockfd, int level, int optname,const void* optval, socklen_t optlen);int opt 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));int opt 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, opt, sizeof(opt));服务器端源码
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include unistd.h
#include ctype.h#define SERV_IP 127.0.0.1
#define SERV_PORT 6666int main()
{int sfd, cfd;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;char buf[BUFSIZ], clie_IP[BUFSIZ];int nByte, idx;sfd socket(AF_INET, SOCK_STREAM, 0);int opt 1;setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));//允许端口复用memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_port htons(SERV_PORT);serv_addr.sin_addr.s_addr htonl(INADDR_ANY);bind(sfd, (struct sockaddr*)serv_addr, sizeof(serv_addr));listen(sfd, 128);clie_addr_len sizeof(clie_addr);cfd accept(sfd, (struct sockaddr*)clie_addr, clie_addr_len);printf(client IP: %s, port: %d\n,inet_ntop(AF_INET, clie_addr.sin_addr.s_addr, clie_IP,sizeof(clie_IP)),ntohs(clie_addr.sin_port));while (1){nByte read(cfd, buf, sizeof(buf));for (idx 0; idx nByte; idx){buf[idx] toupper(buf[idx]);}write(cfd, buf, nByte);}close(sfd);close(cfd);return 0;
}客户端源码
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include unistd.h
#include ctype.h
#include string.h#define SERV_IP 127.0.0.1
#define SERV_PORT 6666int main()
{int cfd;struct sockaddr_in serv_addr;char buf[BUFSIZ];int nByte;cfd socket(AF_INET, SOCK_STREAM, 0);memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_port htons(SERV_PORT);serv_addr.sin_addr.s_addr htonl(INADDR_ANY);/* inet_pton(cfd, SERV_IP, serv_addr.sin_addr.s_addr); */connect(cfd, (struct sockaddr*)serv_addr, sizeof(serv_addr));while (1){fgets(buf, sizeof(buf), stdin);//hello world ---- hello world\n\0write(cfd, buf, strlen(buf));nByte read(cfd, buf, sizeof(buf));write(STDOUT_FILENO, buf, nByte);}close(cfd);return 0;
}read 返回值
1、大于0实际读到的字节数并且buf1024 如果read读到的数据的长度等于buf返回的就是1024 如果read读到的数据长度小于buf那就是小于1024的数值。 2、返回值为0数据读完读到文件、管道、socket末尾 —对端关闭
3、返回值为-1表明出现异常 errno EINTR 说明被信号中断 所以需要重启或者退出; errno EAGAINEWOULDBLOCK非阻塞方式读并且没有数据; 其他值的出现表示出现错误使用 perror 打印然后 exit 退出 readn/writen 函数的封装
因为以太网帧一次只能传送 1500 字节的数据所以使用 read 函数一次最多只能读到 1500 字节就返回退出。
ssize_t readn(int fd, void* vptr, size_t n)
{size_t nleft;//usigned int剩余未读取的字节数size_t nread;//int 实际读到的字节数char* ptr;nleft n;//n未读取字节数ptr vptr;while (nleft 0){if ((nread read(fd, ptr, nleft)) 0){if (errno EINTR){nread 0;}else{return -1;}}else if (0 nread){break;}nleft - nread;ptr nread;}return (n - nleft);
}ssize_t writen(int fd, const void* vptr, size_t n)
{size_t nleft;size_t nwritten;const char* ptr;nleft n;ptr vptr;while (nleft 0){if ((nwritten write(fd, ptr, nleft)) 0){if (nwritten 0 errno EINTR){nwritten 0;}else{return -1;}}nleft - nwritten;ptr nwritten;}return n;
}IO多路复用
概念与原理图
多进程与多线程并发服务器不经常使用这种作为大型服务器开发的原因是所有的监听与访问请求都由服务器操作。
可以使用多路IO转接服务器也叫多任务IO服务器思想不再由应用程序自己监视客户端连接取而代之由内核替应用程序监视文件。 select
接口解析
#include sys/select.h
#include sys/time.h
#include sys/types.h
#include unistd.hint select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,struct timeval* timeout);nfds:监控的文件描述符集里最大文件描述符 1因为此参数会告诉内核检测前多少个文件描述符的状态。
readfs / writes / exceptfds : 监控有读数据 / 写数据 / 异常发生到达文件描述符集合三个都是传入传出参数。
timeout : 定时阻塞监控时间3种情况
1、NULL永远等下去
2、设置timeval等待固定时间
3、设置timeval里时间均为0检查描述字后立即返回轮询。
fd_set本质是个位图。
struct timeval
{long tv_sec; /* seconds */long tv_usec; /* microseconds */
};
返回值
成功所监听的所有的监听集合中满足条件的总数。
失败返回 -1.void FD_ZERO(fd_set *set);//将set清空为0
void FD_SET(int fd, fd_set *set);//将fd设置到set集合中
void FD_CLR(int fd, fd_set *set);//将fd从set中清除出去
int FD_ISSET(int fd, fd_set *set);//判断fd是否在集合中优缺点
1、文件描述符上限1024同时监听的文件描述符1024个历史原因不好修改除非重新编译Linux内核。
2、当监听的文件描述符个数比较稀疏的时候比如3 600 1023循环判断比较麻烦所以需要自定义数据结构数组。
3、监听集合与满足监听条件的集合是同一个需要将原有集合保存。
代码实现C语言
server 端
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include sys/select.h
#include sys/time.h
#include stdlib.h
#include strings.h
#include string.h
#include unistd.h
#include ctype.h#define SERV_PORT 8888int main()
{int listenfd, connfd, sockfd;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;int ret, maxfd, maxi, i, j, nready, nByte;fd_set rset, allset;int client[FD_SETSIZE];char buf[BUFSIZ], str[BUFSIZ];listenfd socket(AF_INET, SOCK_STREAM, 0);if (-1 listenfd){perror(socket error);exit(-1);}int opt 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));bzero(serv_addr, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_port htons(SERV_PORT);serv_addr.sin_addr.s_addr htonl(INADDR_ANY);ret bind(listenfd, (struct sockaddr*)serv_addr,sizeof(serv_addr)); if (-1 ret){perror(bind error);exit(-1);}ret listen(listenfd, 128);if (-1 ret){perror(listen error);exit(-1);}maxfd listenfd;maxi -1;for (i 0; i FD_SETSIZE; i){client[i] -1;}FD_ZERO(allset);FD_SET(listenfd, allset);while (1){rset allset;nready select(maxfd 1, rset, NULL, NULL, NULL);if (nready 0){perror(select error);exit(-1);}if (FD_ISSET(listenfd, rset)){clie_addr_len sizeof(clie_addr);connfd accept(listenfd, (struct sockaddr*)clie_addr,clie_addr_len);if (-1 connfd){perror(accept error);exit(-1);}printf(receive from %s from port %d\n,inet_ntop(AF_INET, clie_addr.sin_addr, str,sizeof(str)),ntohs(clie_addr.sin_port));for (i 0; i FD_SETSIZE; i){if (client[i] 0){client[i] connfd;break;}}if (i FD_SETSIZE){fputs(too many clients\n, stderr);exit(1);}FD_SET(connfd, allset);if (connfd maxfd){maxfd connfd;}if (i maxi){maxi i;}if (--nready 0){continue;}}for (i 0; i maxi; i){if ((sockfd client[i]) 0){continue;}if (FD_ISSET(sockfd, rset)){if ((nByte read(sockfd, buf, sizeof(buf))) 0){close(sockfd);FD_CLR(sockfd, allset);client[i] -1;}else if (nByte 0){for (j 0; j nByte; j){buf[j] toupper(buf[j]);}write(sockfd, buf, nByte);write(STDOUT_FILENO, buf, nByte);}if (--nready 0){break;}}}}close(listenfd); close(connfd);return 0;
}client 端
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include unistd.h
#include ctype.h
#include string.h
#include stdlib.h#define SERV_IP 127.0.0.1
#define SERV_PORT 8888int main()
{int cfd;struct sockaddr_in serv_addr;char buf[BUFSIZ];int nByte;cfd socket(AF_INET, SOCK_STREAM, 0);if (-1 cfd){perror(socket error);exit(-1);}memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_port htons(SERV_PORT);serv_addr.sin_addr.s_addr htonl(INADDR_ANY);/* inet_pton(cfd, SERV_IP, serv_addr.sin_addr.s_addr); */connect(cfd, (struct sockaddr*)serv_addr, sizeof(serv_addr));while (1){fgets(buf, sizeof(buf), stdin);//hello world ---- hello world\n\0write(cfd, buf, strlen(buf));nByte read(cfd, buf, sizeof(buf));write(STDOUT_FILENO, buf, nByte);}close(cfd);return 0;
}poll
接口解析
#include poll.hint poll(struct pollfd *fds, nfds_t nfds, int timeout);struct pollfd
{int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};fds:文件描述符数组。
eventsPOLLIN/POLLOUT/POLLERR
nfds监控数组中有多少文件描述符需要被监控。
timeout 毫秒级等待-1:阻塞等#define INFTIM -1 Linux中没有定义此宏0:立即返回不阻塞进程0:等待指定毫秒数如当前系统时间精度不够毫秒向上取值。
函数返回值满足监听条件的文件描述符的数目。优缺点
优点
1、突破文件描述符1024的上限 2、监听与返回的集合分离 3、搜索范围变小已经知道是哪几个数组
缺点
1、监听1000个文件描述符但是只有3个满足条件这样也需要全部遍历效率依旧低。 2、cat /proc/sys/fs/file-max 查看一个进程可以打开的文件描述符的上限数。 3、sudo vi /etc/security/limits.conf。在文件尾部写入以下配置soft 软限制hard 硬限制。
soft nofile 65536
hard nofile 100000代码实现C语言
server 端
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include unistd.h
#include ctype.h
#include string.h
#include stdlib.h
#include poll.h
#include errno.h#define SERV_PORT 8888
#define OPEN_MAX 1024int main()
{int i, j, n, maxi;int nready, ret;int listenfd, connfd, sockfd;char buf[BUFSIZ], str[INET_ADDRSTRLEN];struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;struct pollfd client[OPEN_MAX];listenfd socket(AF_INET, SOCK_STREAM, 0);if (-1 listenfd){perror(socket error);exit(-1);}int opt 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_port htons(SERV_PORT);//本地字节序port与ip都要转换为网络字节序serv_addr.sin_addr.s_addr htonl(INADDR_ANY);//因为要在网络上传输ret bind(listenfd, (struct sockaddr*)serv_addr,sizeof(serv_addr));if (-1 ret){perror(bind error);exit(-1);}ret listen(listenfd, 128);if (-1 ret){perror(listen error);exit(-1);}client[0].fd listenfd;client[0].events POLLIN;for (i 1; i OPEN_MAX; i){client[i].fd -1;//将数组初始化为-1}maxi 0;while (1){nready poll(client, maxi 1, -1);if (nready 0){perror(poll error);exit(-1);}if (client[0].revents POLLIN){clie_addr_len sizeof(clie_addr);connfd accept(listenfd, (struct sockaddr*)clie_addr,clie_addr_len);//立即连接此时不会阻塞等if (-1 connfd){perror(accept error);exit(-1);}printf(received from %s at port %d\n,inet_ntop(AF_INET, clie_addr.sin_addr.s_addr, str,sizeof(str)),ntohs(clie_addr.sin_port));for (i 1; i OPEN_MAX; i){if (client[i].fd 0)//因为初始化为-1,所以在此作为判断条件{client[i].fd connfd;break;//直接跳出免得继续判断,浪费时间}}if (i OPEN_MAX)//select监听的文件描述符有上限最大只能监听1024个{fputs(too many clients\n, stderr);exit(1);}client[i].events POLLIN;if (i maxi){maxi i;//因为文件描述符有新增导致自定义数组有变化所以需要重新修改maxi的值}if (--nready 0)//意思不明确{continue;}}for (i 1; i maxi; i){if ((sockfd client[i].fd) 0){continue;}if (client[i].revents POLLIN){if ((n read(sockfd, buf, sizeof(buf))) 0){if (errno ECONNRESET){printf(client[%d] abort connect\n, i);close(sockfd);client[i].fd -1;}else{perror(read n 0 error);}}else if (n 0){for (j 0; j n; j){buf[j] toupper(buf[j]);}write(sockfd, buf, n);write(STDOUT_FILENO, buf, n);}else{close(sockfd);client[i].fd -1;}if (--nready 0){break;}}}}close(listenfd);close(connfd);return 0;
}client 端
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include unistd.h
#include ctype.h
#include string.h
#include stdlib.h#define SERV_IP 127.0.0.1
#define SERV_PORT 8888int main()
{int cfd;struct sockaddr_in serv_addr;char buf[BUFSIZ];int nByte;cfd socket(AF_INET, SOCK_STREAM, 0);if (-1 cfd){perror(socket error);exit(-1);}memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_port htons(SERV_PORT);serv_addr.sin_addr.s_addr htonl(INADDR_ANY);/* inet_pton(cfd, SERV_IP, serv_addr.sin_addr.s_addr); */connect(cfd, (struct sockaddr*)serv_addr, sizeof(serv_addr));while (1){fgets(buf, sizeof(buf), stdin);//hello world ---- hello world\n\0write(cfd, buf, strlen(buf));nByte read(cfd, buf, sizeof(buf));write(STDOUT_FILENO, buf, nByte);}close(cfd);return 0;
}epoll
接口解析
是Linux下IO多路复用接口select/poll的增强版本能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要侦听的文件描述符集合另一个原因是获取事件的时候它无须遍历整个被侦听的描述符集只要遍历哪些被内核IO事件唤醒而加入Ready队列的描述符集合就行了。
#include sys/epoll.hint epoll_create(int size);size参数size用来告知内核监听的文件描述符的个数与内存大小有关。//控制某个epoll监控的文件描述符上的事件注册、修改、删除
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);epfdepoll_create函数返回的值
opEPOLL_CTL_ADD / EPOLL_CTL_MOD / EPOLL_CTL_DEL
fd将哪个文件描述符以op的方式加在以epfd建立的树上
event告诉内核需要监听的事情。struct epoll_event
{uint32_t events;epoll_data_t data;
};typedef union epoll_data
{void* ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;//等待所监控文件描述符上有事件的产生
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);events用来存内核得到事件的集合这里是个传出参数
maxevents告知内核这个events有多大这个maxevents的值不能大于创建epoll_create时的size
timeout是超时时间-1阻塞0立即返回非阻塞0指定毫秒
返回值成功返回有多少文件描述符就绪时间到时返回0出错返回 - 1优缺点
优点
1、文件描述符数目没有上限通过 epoll_ctl() 来注册一个文件描述符内核中使用红黑树的数据结构来 管理所有需要监控的文件描述符。
2、基于事件就绪通知方式一旦被监听的某个文件描述符就绪内核会采用类似于 callback 的回调机制迅速激活这个文件描述符这样随着文件描述符数量的增加也不会影响判定就绪的性能。
3、维护就绪队列当文件描述符就绪就会被放到内核中的一个就绪队列中这样调用 epoll_wait 获取就绪文件描述符的时候只要取队列中的元素即可操作的时间复杂度恒为 O(1) 。
图解 类型区别
水平触发(level-triggered)
只要文件描述符关联的读内核缓冲区非空有数据可以读取就一直发出可读信号进行通知当文件描述符关联的内核写缓冲区不满有空间可以写入就一直发出可写信号进行通知LT模式支持阻塞和非阻塞两种方式。
epoll默认的模式是LT。
边缘触发(edge-triggered)
当文件描述符关联的读内核缓冲区由空转化为非空的时候则发出可读信号进行通知当文件描述符关联的内核写缓冲区由满转化为不满的时候则发出可写信号进行通知。
两种类型区别
两者的区别在哪里呢
水平触发是只要读缓冲区有数据就会一直触发可读信号而边缘触发仅仅在空变为非空的时候通知一次。
LT(level triggered) 是缺省的工作方式并且同时支持 block 和 no-block socket.
在这种做法中内核告诉你一个文件描述符是否就绪了然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作内核还是会继续通知你的所以这种模式编程出错误可能性要小一点。传统的 select/poll 都是这种模型的代表 当设置了边缘触发以后以可读事件为例对“有数据到来”这事件为触发。 select/poll/epoll 除了应用于 fd 外像管道、文件也是可以的。
代码实现C语言
server端
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include sys/select.h
#include sys/time.h
#include sys/epoll.h
#include stdlib.h
#include strings.h
#include unistd.h
#include ctype.h#define SERV_PORT 8888
#define OPEN_MAX 5000int main()
{int listenfd, connfd, sockfd, epfd;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;int ret, i, j, nready, nByte;char buf[BUFSIZ], str[BUFSIZ];struct epoll_event evt, ep[OPEN_MAX];listenfd socket(AF_INET, SOCK_STREAM, 0);if (-1 listenfd){perror(socket error);exit(-1);}int opt 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));bzero(serv_addr, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_port htons(SERV_PORT);serv_addr.sin_addr.s_addr htonl(INADDR_ANY);ret bind(listenfd, (struct sockaddr*)serv_addr,sizeof(serv_addr));if (-1 ret){perror(bind error);exit(-1);}ret listen(listenfd, 128);if (-1 ret){perror(listen error);exit(-1);}epfd epoll_create(OPEN_MAX);if (-1 epfd){perror(epoll_create error);exit(-1);}evt.events EPOLLIN;evt.data.fd listenfd;ret epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, evt);if (-1 ret){perror(epoll_ctl error);exit(-1);}while (1){nready epoll_wait(epfd, ep, OPEN_MAX, -1);if (nready 0){perror(select error);exit(-1);}for (i 0; i nready; i){if (!(ep[i].events EPOLLIN)){continue;}if (ep[i].data.fd listenfd)//如果是连接事件{clie_addr_len sizeof(clie_addr);connfd accept(listenfd, (struct sockaddr*)clie_addr,clie_addr_len);if (-1 connfd){perror(accept error);exit(-1);}printf(receive from %s from port %d\n,inet_ntop(AF_INET, clie_addr.sin_addr, str,sizeof(str)),ntohs(clie_addr.sin_port));evt.events EPOLLIN;evt.data.fd connfd;epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, evt);}else //不是连接建立事件而是读写事件(信息传递事件){sockfd ep[i].data.fd;nByte read(sockfd, buf, sizeof(buf));if (nByte 0){ret epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);if (-1 ret){perror(epoll_ctl error);}close(sockfd);printf(client[%d] closed connection\n, sockfd);}else if (nByte 0){perror(epoll_ctl error);ret epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);if (-1 ret){perror(epoll_ctl error);}close(sockfd);}else{for (j 0; j nByte; j){buf[j] toupper(buf[j]);}write(sockfd, buf, nByte);write(STDOUT_FILENO, buf, nByte);}}}}close(listenfd);close(connfd);return 0;
}client端
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include unistd.h
#include ctype.h
#include string.h
#include stdlib.h#define SERV_IP 127.0.0.1
#define SERV_PORT 8888int main()
{int cfd;struct sockaddr_in serv_addr;char buf[BUFSIZ];int nByte;cfd socket(AF_INET, SOCK_STREAM, 0);if (-1 cfd){perror(socket error);exit(-1);}memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_port htons(SERV_PORT);serv_addr.sin_addr.s_addr htonl(INADDR_ANY);/* inet_pton(cfd, SERV_IP, serv_addr.sin_addr.s_addr); */connect(cfd, (struct sockaddr*)serv_addr, sizeof(serv_addr));while (1){fgets(buf, sizeof(buf), stdin);//hello world ---- hello world\n\0write(cfd, buf, strlen(buf));nByte read(cfd, buf, sizeof(buf));write(STDOUT_FILENO, buf, nByte);}close(cfd);return 0;
}