TCP状态转换,select多路IO转接模型

TCP状态转换图

 主动关闭的Socket端会进入TIME_WAIT状态,并且持续2MSL时间长度,MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失。

为什么需要2MSL?

1.让四次挥手过程更加可靠,确保最后一个发送给对方的ACK到达,若主动关闭方没有收到对方的ACK应答,主动关闭方会再次发送FIN请求关闭,此时在2MS时间内被动关闭方仍可以发送ACK给主动关闭方。

2.为了保证在2MS时间内,不能启动相同的SOCKET-PAIR。

TIME-WAIT一定出现在主动请求关闭的一方,也就是说2MS是针对主动关闭一方来说的,由于TCP存在丢包重传,丢包重传若发给了已经断开连接之后相同的socket-pair(该连接是新建的, 与原来的socket-pair完全相同, 双方使用的是相同的IP和端口), 这样会对之后的连接造成困扰,严重可能引起程序异常。

端口复用

使用setsockopt函数

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
	setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(int));

函数详解:http://t.csdn.cn/KD90s

半关闭状态

如果一方close, 另一方没有close, 则认为是半关闭状态, 处于半关闭状态的时候, 可以接收数据, 但是不能发送数据。相当于把文件描述符的写缓冲区操作关闭了。

注意: 半关闭一定是出现在主动关闭的一方。

长连接:连接建立之后一直不断开。

短连接:连接收发数据完毕之后就关闭为短连接。

shutdown和close的区别

       shutdown能够把文件描述符上的读或者写操作关闭, 而close关闭文件描述   符只是将连接的引用计数的值减1, 当减到0就真正关闭文件描述符了。

心跳包

作用:用于监测长连接是否正常的字符串

心跳包使用时通信双方需要协商规则(协议), 如4个字节长度+数据部分

高并发服务器模型--select

多路IO技术:select,同时监听多个文件描述符,将监听的操作交给内核去处理

select函数:委托内核监控该文件描述符对应的读,写或者错误时间的发生。

int select(int nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

nfds:监控的文件描述符的范围(最大文件描述符+1)

readfds:读集合, 是一个传入传出参数

              传入: 指的是告诉内核哪些文件描述符需要监控

              传出: 指的是内核告诉应用程序哪些文件描述符发生了变化

writefds: 写文件描述符集合(传入传出参数)

       execptfds: 异常文件描述符集合(传入传出参数)

       timeout: NULL--表示永久阻塞, 直到有事件发生

                      0 --表示不阻塞, 立刻返回, 不管是否有监控的事件发生

                      >0--到指定事件或者有事件发生了就返回

返回值: 成功返回发生变化的文件描述符的个数

              失败返回-1, 并设置errno值.

void FD_CLR(int fd, fd_set *set);
//将fd从set集合中清除.
int FD_ISSET(int fd, fd_set *set);
//判断fd是否在集合中
//如果fd在set集合中, 返回1, 否则返回0.
void FD_SET(int fd, fd_set *set);
//将fd设置到set集合中.
void FD_ZERO(fd_set *set);
//初始化set集合

 IO多路复用技术select函数的使用

//IO多路复用技术select函数的使用 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>

int main()
{
	int i;
	int n;
	int lfd;
	int cfd;
	int ret;
	int nready;
	int maxfd;//最大的文件描述符
	char buf[FD_SETSIZE];
	socklen_t len;
	int maxi;  //有效的文件描述符最大值
	int connfd[FD_SETSIZE]; //有效的文件描述符数组
	fd_set tmpfds, rdfds; //要监控的文件描述符集
	struct sockaddr_in svraddr, cliaddr;

	//创建socket
	lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}

	//允许端口复用
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));

	//绑定bind
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	svraddr.sin_port = htons(8888);
	ret = bind(lfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in));
	if(ret<0)
	{
		perror("bind error");
		return -1;
	}

	//监听listen
	ret = listen(lfd, 5);
	if(ret<0)
	{
		perror("listen error");
		return -1;
	}

	//文件描述符集初始化
	FD_ZERO(&tmpfds);
	FD_ZERO(&rdfds);

	//将lfd加入到监控的读集合中
	FD_SET(lfd, &rdfds);

	//初始化有效的文件描述符集, 为-1表示可用, 该数组不保存lfd
	for(i=0; i<FD_SETSIZE; i++)
	{
		connfd[i] = -1;
	}

	maxfd = lfd;
	len = sizeof(struct sockaddr_in);

	//将监听文件描述符lfd加入到select监控中
	while(1)
	{
		//select为阻塞函数,若没有变化的文件描述符,就一直阻塞,若有事件发生则解除阻塞,函数返回
		//select的第二个参数tmpfds为输入输出参数,调用select完毕后这个集合中保留的是发生变化的文件描述符
		tmpfds = rdfds;
		nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
		if(nready>0)
		{
			//发生变化的文件描述符有两类, 一类是监听的, 一类是用于数据通信的
			//监听文件描述符有变化, 有新的连接到来, 则accept新的连接
			if(FD_ISSET(lfd, &tmpfds))	
			{
				cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);			
				if(cfd<0)
				{
					if(errno==ECONNABORTED || errno==EINTR)
					{
						continue;
					}
					break;
				}

				//先找位置, 然后将新的连接的文件描述符保存到connfd数组中
				for(i=0; i<FD_SETSIZE; i++)
				{
					if(connfd[i]==-1)
					{
						connfd[i] = cfd;
						break;
					}
				}
				//若连接总数达到了最大值,则关闭该连接
				if(i==FD_SETSIZE)
				{	
					close(cfd);
					printf("too many clients, i==[%d]\n", i);
					//exit(1);
					continue;
				}

				//确保connfd中maxi保存的是最后一个文件描述符的下标
				if(i>maxi)
				{
					maxi = i;
				}

				//打印客户端的IP和PORT
				char sIP[16];
				memset(sIP, 0x00, sizeof(sIP));
				printf("receive from client--->IP[%s],PORT:[%d]\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, sIP, sizeof(sIP)), htons(cliaddr.sin_port));

				//将新的文件 描述符加入到select监控的文件描述符集合中
				FD_SET(cfd, &rdfds);
				if(maxfd<cfd)
				{
					maxfd = cfd;
				}

				//若没有变化的文件描述符,则无需执行后续代码
				if(--nready<=0)
				{
					continue;
				}	
			}

			//下面是通信的文件描述符有变化的情况
			//只需循环connfd数组中有效的文件描述符即可, 这样可以减少循环的次数
			for(i=0; i<=maxi; i++)
			{
				int sockfd = connfd[i];
				//数组内的文件描述符如果被释放有可能变成-1
				if(sockfd==-1)
				{
					continue;
				}

				if(FD_ISSET(sockfd, &tmpfds))
				{
					memset(buf, 0x00, sizeof(buf));
					n = read(sockfd, buf, sizeof(buf));
					if(n<0)
					{
						perror("read over");
						close(sockfd);
						FD_CLR(sockfd, &rdfds);
						connfd[i] = -1; //将connfd[i]置为-1,表示该位置可用
					}
					else if(n==0)
					{
						printf("client is closed\n");	
						close(sockfd);
						FD_CLR(sockfd, &rdfds);
						connfd[i] = -1; //将connfd[i]置为-1,表示该位置可用
					}
					else
					{
						printf("[%d]:[%s]\n", n, buf);
						write(sockfd, buf, n);
					}

					if(--nready<=0)
					{
						break;  //注意这里是break,而不是continue, 应该是从最外层的while继续循环
					}
				}	
			}
		}	
	}

	//关闭监听文件描述符
	close(lfd);

	return 0;
}

select优点:

       1 一个进程可以支持多个客户端

       2 select支持跨平台

select缺点:

       1 代码编写困难

       2 会涉及到用户区到内核区的来回拷贝

       3 当客户端多个连接, 但少数活跃的情况, select效率较低

例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下

       4 最大支持1024个客户端连接

select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由FD_SETSIZE=1024限制的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值