主动方发生在FIN_WAIT_2状态,这个状态时,主动方不可以在应用层发送数据了,但是应用层还可以接收数据,这个状态称为半关闭
#include
int shutdown(int sockfd, int how);
sockfd: 需要关闭的socket的描述符
how: 允许为shutdown操作选择以下几种方式:
SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
该套接字不再接收数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。
SHUT_RDWR(2): 关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
如果对方异常断开,本机检测不到,一直等待,浪费资源
需要设置tcp的保持连接,作用就是每隔一定的时间间隔发送探测分节,如果连续发送多个探测分节对方还未回,就将次连接断开
int keepAlive = 1;
setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
心跳包: 最小粒度
乒乓包: 携带比较多的数据的心跳包
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
注意: 程序中设置某个端口重新使用,在这之前的其他网络程序将不能使用这个端口
阻塞等待 消耗资源
非阻塞忙轮询 消耗cpu
多路IO
多路IO转接(多路IO复用): 内核监听多个文件描述符的属性(读写缓冲区)变化
如果某个文件描述符的读缓冲区变化了,这个时候就是可以读了,将这个事件告知应用层
windwos 使用select select跨平台
poll 少用
epoll linux
#include
/* According to earlier standards */
#include
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能: 监听多个文件描述符的属性变化(读,写,异常)
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
参数:
nfds : 最大文件描述符+1
readfds : 需要监听的读的文件描述符存放集合
writefds :需要监听的写的文件描述符存放集合 NULL
exceptfds : 需要监听的异常的文件描述符存放集合 NULL
timeout: 多长时间监听一次 固定的时间,限时等待 NULL 永久监听
struct timeval {
long tv_sec; /* seconds */ 秒
long tv_usec; /* microseconds */微妙
};
返回值: 返回的是变化的文件描述符的个数
注意: 变化的文件描述符会存在监听的集合中,未变化的文件描述符会从集合中删除
select文件描述符请求剩余,之后在遍历,备份。
实现原理:
优缺点:
优点: 跨平台
缺点:
文件描述符1024的限制 由于 FD_SETSIZE的限制
只是返回变化的文件描述符的个数,具体哪个那个变化需要遍历
每次都需要将需要监听的文件描述集合由应用层符拷贝到内核
大量并发,少了活跃,select效率低
假设现在 4-1023个文件描述符需要监听,但是5-1000这些文件描述符关闭了?
假设现在 4-1023个文件描述符需要监听,但是只有 5,1002 发来消息- 无解
优点: 相对于select没有最大1024文件描述符限制
请求和返回是分离
poll API
#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能: 监听多个文件描述符的属性变化
参数:
fds : 监听的数组的首元素地址
nfds: 数组有效元素的最大下标+1
timeout : 超时时间 -1是永久监听 >=0 限时等待
数组元素:
struct pollfd
struct pollfd {
int fd; /* file descriptor */ 需要监听的文件描述符
short events; /* requested events */需要监听文件描述符什么事件 EPOLLIN 读事件 EPOLLOUT写事件
short revents; /* returned events */ 返回监听到的事件 EPOLLIN 读事件 EPOLLOUT写事
};
优点:
没有文件描述符1024的限制
请求和返回是分离的
缺点和select一样:
每次都需要将需要监听的文件描述符从应用层拷贝到内核
每次都需要将数组中的元素遍历一遍才知道那个变化了
大量并发,少量活跃效率低
实现原理:
epollAPI
a> 创建红黑树
#include
int epoll_create(int size);
参数:
size : 监听的文件描述符的上限, 2.6版本之后写1即可,
返回: 返回树的句柄
b> 上树 下树 修改节点
epoll_ctl
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd : 树的句柄
op : EPOLL_CTL_ADD 上树 EPOLL_CTL_DEL 下树 EPOLL_CTL_MOD 修改
fd : 上树,下树的文件描述符(lfd,cfd)
event : 上树的节点
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */ 需要监听的事件
epoll_data_t data; /* User data variable */ 需要监听的文件描述符
};
将cfd上树
int epfd = epoll_create(1);
struct epoll_event ev;
ev. data.fd = cfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD,cfd, &ev);
c> 监听
#include
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能: 监听树上文件描述符的变化
epfd : 数的句柄
events : 接收变化的节点的数组的首地址
maxevents : 数组元素的个数
timeout : -1 永久监听 大于等于0 限时等待
返回值: 返回的是变化的文件描述符个数
半关闭,主动方关闭了,应用层不能发送数据了,但是能接受数据,这种状态为半关闭状态
对方如果断开连接,一直等待,浪费资源,探测分节就是心跳包用来检测tcp的连接
设置端口复用,可以将端口重复使用
之前的阻塞等待,像是一个事件创建一个还要去遍历,非阻塞忙轮询,就像雇佣了一个人来回查看是否有新的事件,多路io端口复用是内核检测文件描述符的变化,再告知应用层
select(Windows),epoll(Linux),poll都是检测的,
要知道select的函数如何写,并且需要了解如何去构造,我们需要自己构造文件描述符的集合,并且需要遍历,
select实现原理:需要把文件描述符的集合拷贝内核空间,再拷贝回去应用空间,而且需要遍历每一位,读文件还需要遍历,文件描述符太少,只是返回文件描述符的个数,具体变化还需要处理,
poll没有文件描述符1024限制,监听文件描述的变化,EPOLLIN读事件,EPOLLOUT写事件。
poll优点就是修改了文件描述符的限制1024,其他缺点和select一样
epoll创建一个红黑树,将要监听的文件描述符上树,监听,监听到的文件描述符变化,发给数组接收,创建一个红黑树epoll_create,返回树的句柄,epoll_ctl(句柄,epoll_ctl_add(上树),epoll_ctl_dev(下树),文件描述符,上树的节点)上树,下树,监听epoll_wait(句柄,事件,数组元素个数,-1)返回文件描述变化个数