非阻塞socket

socket阻塞与非阻塞,同步与异步

1. 概念理解

     在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式:


同步/异步主要针对C端: 
同步:
      
所谓同步,就是在c端发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。

例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事

异步:
      
异步的概念和同步相对。当c端一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

     例如 ajax请求(异步)请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕


阻塞/非阻塞主要针对S:

阻塞
     
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。


     有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 例如,我们在socket中调用recv函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。

   快递的例子:比如到你某个时候到A楼一层(假如是内核缓冲区)取快递,但是你不知道快递什么时候过来,你又不能干别的事,只能死等着。但你可以睡觉(进程处于休眠状态),因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。


非阻塞
      
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

     还是等快递的例子:如果用忙轮询的方法,每隔5分钟到A楼一层(内核缓冲区)去看快递来了没有。如果没来,立即返回。而快递来了,就放在A楼一层,等你去取。


对象的阻塞模式和阻塞函数调用
对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状 态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select就是这样的一个例子。


1. 同步,就是我客户端(c端调用者)调用一个功能,该功能没有结束前,我(c端调用者)死等结果。
2. 异步,就是(c端调用者)调用一个功能,不需要知道该功能结果,该功能有结果后通知我(c端调用者)即回调通知。

同步/异步主要针对C, 但是S端不是完全没有关系,同步/异步机制必须S端配合才能实现.同步/异步是由c端自己控制,但是S端是否阻塞/非阻塞, C端完全不需要关心.


3. 阻塞,      就是调用我(s端被调用者,函数),我(s端被调用者,函数没有接收完数据或者没有得到结果之前,我不会返回。
4. 非阻塞,  就是调用我(s端被调用者,函数,我(s端被调用者,函数立即返回,通过select通知调用者


同步IO和异步IO的区别就在于:数据访问的时候进程是否阻塞!

阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回!


同步和异步都只针对于本机SOCKET而言的。

同步和异步,阻塞和非阻塞,有些混用,其实它们完全不是一回事,而且它们修饰的对象也不相同。
阻塞和非阻塞是指当server端的进程访问的数据如果尚未就绪,进程是否需要等待,
简单说这相当于函数内部的实现区别,也就是未就绪时是直接返回还是等待就绪;

而同步和异步是指client端访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞(区别就绪与读写二个阶段,同步的读写必须阻塞),异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O,操作完毕的通知,这可以使进程在数据读写时也不阻塞。(等待"通知")

阻塞socket:

阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有得到结果之后才会返回。

对于文件操作read和fread函数调用会将线程阻塞,accept、recv和recvfrom函数调用会将线程阻塞

为避免整个进程被阻塞后挂起,所以在阻塞模式下,往往需要采用多线程技术。

但一个进程中的可并发的线程数总是有限的,在处理大量客户端socket连接(上万个)通过线程并发并不方便,效率不高。

非阻塞socket:

非阻塞调用是指调用立刻返回。在非阻塞模式下,accept、recv和recvfrom函数会立刻返回。

在非阻塞状态下调用accept函数,如果没有客户端socket连接请求,那么accpet函数返回-1,同时errno置为11

在非阻塞状态下调用recv、recvfrom函数,如果没有数据,函数返回-1,同时errno置为11,如果socket已经关闭,函数返回0

在非阻塞状态下对于一个已经关闭的socket调用send函数,将引发一个SIGPIPE信号,进程必须捕捉这个信号,因为SIGPIPE在系统默认的处理方式是关闭状态。

fcntl函数调用:

fcntl函数可以将文件或者socket描述符设置为阻塞或者非阻塞状态

int fcntl(int fd,int cmd,.../*arg*/);

参数:fd为要设置的文件描述符或者socket

参数:F_GETFL为得到目前状态,F_SETFL为设置状态

宏定义O_NONBLOCK代表非阻塞,0代表阻塞

返回值为描述符当前状态,失败小于0。

sercer端socket设置为非阻塞状态实例1:

函数实现功能:若没有client连接,则server端关闭,不再等待来自client的连接。

server.c

void setnonblocking(int st)
{
	int opts = fcntl(st, F_GETFL);
	if (opts < 0)
	{
				printf("fcntl failed %s\n", strerror(errno));
	}
	opts = opts | O_NONBLOCK;
	if (fcntl(st, F_SETFL, opts) < 0)
	{
		printf("fcntl failed %s\n", strerror(errno));
	}
}
int main()
{
	int st = socket(AF_INET,SOCK_STREAM,0);
	setnonblocking(st);//设置为非阻塞状态
	int on = 1;
	if(setsockopt(st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) == -1)
	{
		printf("setsockopt failed %s\n",strerror(errno));
		return 0;
	}
	struct sockaddr_in addr;//定义一个套接字地址的结构
	memset(&addr,0,sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8080);//将本地字节顺序转化为网络字节顺序
	addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY代表这个server上的所有地址
	//服务器端程序需要将IP与server程序绑定
	if(bind(st,(struct sockaddr *) &addr,sizeof(addr)) == -1)
	{
		printf("bind failed %s\n",strerror(errno));
		return 0;
	}
	//服务器端开始监听listen,监听指定端口的客户端连接
	if(listen(st,20) == -1)
	{
		printf("listen failed %s\n", strerror(errno));
		return 0;
	}

	char s[1024];
	int client_st = 0;//client端socket
	struct sockaddr_in client_addr;//表示client端的IP地址
	int i = 0;
	for(i = 0;i < 5;i++)
	{
		memset(&client_addr,0,sizeof(client_addr));
		socklen_t len = sizeof(client_addr);
		//accept会阻塞,直到有新的客户端连接起来,accept返回一个client的socket描述符
		//同时原来的套接口继续监听指定端口号
		//若没有客户端与服务器端连接,程序则会一直等待,直到有客户端连接才继续执行
		client_st = accept(st,(struct sockaddr *) &client_addr,&len);//accept返回一个client的socket描述符
		if(client_st == -1)
		{
			if(errno == EAGAIN)
			{
				sleep(1);
				continue;
			}
			else
				printf("accept failed %s\n", strerror(errno));
			}
		while(1)
		{
			memset(s,0,sizeof(1024));
			int rc = recv(client_st,s,sizeof(s),0);//recv是阻塞调用,若没有收到消息,则会挂起,此处接收的是来自客户端的套接字描符的数据
			if(rc > 0)//接收了来自client的消息
			{
				printf("recv is %s\n",s);
				memset(s,0,sizeof(s));
			}
			else
			{
				if(rc == 0)
				{
					printf("client socket closed \n");
				}else
				{
					printf("recv failed %s\n", strerror(errno));
				}
				break;
			}

		}
		close(client_st);//关闭client端socket
	}
	close(st);//关闭server端listen的socket
	return 0;
}

client.c

//客户端

int main(void)
{
	//分配套接口和初始化socket函数,成功返回一个客户端的套接字描述符
	int st = socket(AF_INET,SOCK_STREAM,0);//初始化socket
	//定义一个套接字地址的结构
	struct sockaddr_in addr;
	memset(&addr,0,sizeof(addr));
	addr.sin_family = AF_INET;//设置结构地址类型为TCP/IP地址
	addr.sin_port = htons(8080);//指定一个端口号:8080,htons函数:将short类型从host字节类型到net类型的转化
	addr.sin_addr.s_addr = inet_addr("192.168.1.103");//将字符串类型的IP地址转化为int,赋值给addr结构成员

	//客户端只需要connect,不需要bind
	//调用connect连接到结构addr指定的IP地址和端口号
	//即:客户端调用connect与服务器端进行连接
	if(connect(st,(struct sockaddr *) &addr,sizeof(addr)) == -1)
	{
		printf("connect failed %s\n",strerror(errno));
		return 0;
	}

	//与服务器端已经建立了连接,接下来可以实现发送和接收操作了

	char buf[1024];
	memset(buf,0,sizeof(buf));
	while(1)
	{
		read(STDIN_FILENO,buf,sizeof(buf));//从键盘中读取用户输入
		if(send(st,buf,strlen(buf),0) == -1)//发送buf的数据
		{
			printf("send failed %s\n",strerror(errno));
			return 0;
		}
	}
	//发送和接收后,需要断开连接
	close(st);//关闭socket
	return EXIT_SUCCESS;
}
连接到server端的client的socket设置为非阻塞状态实例2:

函数实现:server端等待来自客户端的连接,若客户端连接但未发送消息,将不再等待客户端的消息,











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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值