AndreaDO

导航

网络编程3 端口复用-多路IO转接select

网络编程3 端口复用-多路IO转接

TCP状态转换图

端口复用

防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用,则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用,绑定会失败,提示ADDR已经在使用中。

解决端口复用的问题: bind error: Address already in use, 发生这种情况是在服务端主动关闭连接以后, 接着立刻启动就会报这种错误.

setsockopt函数

setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); 

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));
	函数说明可参看<<UNIX环境高级编程>>

lfd:是套接字的文件描述符,通常是一个整数,用于标识这个特定的套接字。
SOL_SOCKET:是选项级别,表示这个选项与套接字层(而非特定的协议层,如TCP或UDP)相关。
SO_REUSEADDR:是选项名,表示允许套接字在关闭后立即重新使用其地址。在TCP中,这特别有用,因为它允许服务器在重新启动后立即绑定到同一个端口,而不需要等待之前的连接完全超时。
&opt:是指向一个整数的指针,该整数包含要设置的选项值。在这里,opt 被设置为1,表示启用端口复用。
sizeof(int):表示 opt 的大小,确保 setsockopt 知道要设置多少字节的数据。

由于错误是bind函数报出来的, 该函数调用要放在bind之前, socket之后调用.

半关闭

如果一方close,另外一方没有close,则认为是半关闭状态,处于半关闭状态的时候,可以接受数据,但是不可以发送数据,相当于把文件描述符的写缓冲区操作关闭了。
注意:半关闭一定是出现在主动关闭的一方。

shutdown函数
长连接和端连接的概念:
连接建立之后一直不关闭为长连接;
连接收发数据完毕之后就关闭为短连接;

shutdown和close的区别:
shutdown能够把文件描述符上的读或者写操作关闭, 而close关闭文件描述符只是将连接的引用计数的值减1, 当减到0就真正关闭文件描述符了.
如: 调用dup函数或者dup2函数可以复制一个文件描述符, close其中一个并不影响另一个文件描述符, 而shutdown就不同了, 一旦shutdown了其中一个文件描述符, 对所有的文件描述符都有影响 .

心跳包

长连接:连接建立好之后,一直保持连接不关闭。
短连接:连接使用结束后就立即关闭。
心跳包用于在长连接中,检测与对面的网络连接是否正常。

  • 方法1
    keepAlive = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
    由于不能实时的检测网络情况, 一般不用这种方法。

  • 方法2: 在应用程序中自己定义心跳包, 使用灵活, 能实时把控.

多路IO技术

select的好处:

首先,select模式能够监视多个文件描述符的状态变化,使得服务器能够同时处理多个客户端的连接请求和数据传输。这使得服务器能够高效地利用系统资源,提高并发处理能力。

其次,select模式通过非阻塞I/O操作,使得服务器在等待某个操作完成时,可以继续处理其他任务,从而提高了系统的响应速度和吞吐量。

此外,select模式还提供了灵活性和可扩展性。服务器可以根据需要动态地添加或删除监视的文件描述符,以适应不同的业务场景和负载变化。

坏处:
然而,需要注意的是,当处理大量文件描述符时,select模式可能会存在性能瓶颈。因为select在监视文件描述符时,采用的是轮询的方式,即遍历所有被监视的文件描述符来查找就绪者。当文件描述符数量很大时,这种遍历会导致效率降低。因此,在处理大规模并发连接时,可能需要考虑使用更高效的I/O多路复用技术,如epoll或kqueue等。

多路IO技术: select, 同时监听多个文件描述符, 将监控的操作交给内核去处理
数据类型fd_set: 文件描述符集合,fd_set 实际上是一个位集(bitset),其中每一位代表一个文件描述符。

**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集合.

select多路IO转接模型

select代码的编写

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include "wrap.h"

int main()
{
 
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);
	int cfd;
	 
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));

	 
	struct sockaddr_in serv;
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr *)&serv, sizeof(serv));

	 
	Listen(lfd, 128);
	//	定义fd_set类型的变量
	fd_set readfds;
	fd_set tmpfds;
// 清空readfds和tmpfds
	FD_ZERO(&readfds); //初始化set集合.
FD_ZERO(&tmpfds);
// 将lfd加入readfds中,委托内核监控
FD_SET(lfd,&readfds); //将fd设置到set集合中.
int maxfd = lfd;
int nready;
int sockfd;
char buf[1024];
int n,i;
while(1)
{
	tmpfds = readfds;
	nready = select(maxfd+1,&tmpfds,NULL,NULL,NULL);
	if(nready<0)
	{
		if(errno == EINTR)//信号被中断
		{
			continue;
		}
		break;
	}
	// 有客户端请求过来
	// 功能描述: 判断fd是否在集合中
// 返回值: 如果fd在set集合中, 返回1, 否则返回0
	if(FD_ISSET(lfd,&tmpfds))
	{
			// 接受新的客户端连接请求
			cfd = Accept(lfd,NULL,NULL);
		   // 将cfd加入到readfds
			FD_SET(cfd,&readfds);

			// 修改内核的监控范围
			if(maxfd<cfd)
			{
				maxfd = cfd;
			}
			if (--nready==0)
			{
				continue;
			}
	}

	// 数据发来的情况
	for( i = lfd +1;i<=maxfd;i++)
	{	
		sockfd=i;
		// 判断sockfd是否有变化

		if(FD_ISSET(sockfd,&tmpfds))
		{
			memset(buf,0,sizeof(buf));
			n = Read(sockfd,buf,sizeof(buf));
			if(n<=0)
			{//关闭连接
				close(sockfd);
			// 将socket从readfds中删除
			FD_CLR(sockfd,&readfds);//将fd从set集合中清除.
			}
			else
			{
				printf("n==[%d],buff==[%s]\n",n,buf);
				int k;
				for(k=0;k<sizeof(buf);k++)
				{
					buf[k]=toupper(buf[k]);
				}
				Write(sockfd,buf, n);
			}
			if(--nready==0)
			{
				break;
			}
		}
	}
}
	close(lfd);	return 0;
}

posted on 2024-03-15 20:46  AndreaDO  阅读(29)  评论(0编辑  收藏  举报