IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 网络编程知识四之IO模型 设置获取socket属性 -> 正文阅读

[系统运维]网络编程知识四之IO模型 设置获取socket属性

一.I/O模型讲解
1)阻塞(block)IO :
当资源不满足条件。 此时进程阻塞。进程休眠,不会浪费CPU。最简单效率低。并且进程最终会阻塞在其中一个阻塞函数上,而其它的函数没法及时调用。

常见的阻塞I/O:read / write fgets/scanf send/recv accept

2)非阻塞的方式调用(noblock)
问题:需要不断轮询每个函数,浪费cpu资源 。若是没有数据,则让进程立即返回错误。
错误码如下:
普通文件描述符 EAGAIN
套接字 EAGAIN or EWOULDBLOCK
错误码路径:
/usr/include/asm-generic/errno.h

3)IO多路复用
同时监控多个文件,用一个单进程同时对多路阻塞的IO进行控制,降低系统资源的消耗,提高效率

4)异步信号通知[了解即可]
文件就绪的时候,通过驱动发送信号给应用程序,然后让应用程序对文件进行操作。

二.非阻塞的实现IO

1>文件描述符的属性修改函数

头文件:#include <unistd.h>   
       #include <fcntl.h>
open()
int fcntl(int fd, int cmd); 
int fcntl(int fd, int cmd, long arg);
功能:对文件描述符的一些属性进行设置或者修改。第三个arg参数是否使用由cmd参数的值来决定。
参数:
    @ fd         操作的文件描述符
    @cmd        操作的指令
                 F_GETFL (get file)    arg被忽略不适用  
                            获得fd的文件标志(O_RDONLY ,O_WRONLY,O_RDWR三者选一被获得, 0,1,2,分别是三者的值 )。    
                 F_SETFL (set file)   通过arg给文件描述符设置状态标志。
                                    arg 可用标志:
                                    O_NONBLOCK(非阻塞的IO,例如read没有文件可读的时候,返回-1和errno置EAGAIN错误)
返回值:若是cmd 为F_GETFL 返回一个相应的文件标志。
       若是cmd 为F_SETFL  成功返回0,失败返回-1.

例如:

//把文件描述符设置为非阻塞模式。
int flags = fcntl(fd ,F_GETFL); //获得文件的当前标志位.
flags |= O_NONBLOCK. //在当前标志位添加负责非阻塞的标志.
fcntl(fd,F_SETFL,flags) ;//重新设置fd的状态标志。

fcntl.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//./a.out newfile
int main(int argc, const char *argv[])
{
	int fd = 0;	
	int flag = 0;
	if(argc < 2)
	{
		fprintf(stderr,"Usage : %s newfile\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

//	fd  = open(argv[1],O_RDONLY);	
//  fd  = open(argv[1],O_WRONLY | O_CREAT | O_TRUNC,0666);	
    fd  = open(argv[1],O_RDWR | O_CREAT | O_TRUNC);	
	if(fd < 0)
	{
		perror("Fail to open");	
		exit(EXIT_FAILURE);
	}
	//1.获得当前文件标志位
	flag = fcntl(fd,F_GETFL);
	switch(flag)
	{
	case O_RDONLY:
		printf("File flag is O_RDONLY\n");
		break;
	case O_WRONLY:
		printf("File flag is O_WRONLY\n");
		break;
	case O_RDWR:
		printf("File flag is O_RDWR\n");
		break;
	}
	printf("flag : %d\n",flag);
	return 0;
}

三.IO多路复用的实现

1.本质:
通过一个单进程创建一个文件描述符表,把多路阻塞的IO存放如表中,进控制。降低系统资源的消耗,提高效率。

在这里插入图片描述

2.基本思想
while(1)
{
利用selcet函数同时监控多个文件。如果没有文件就绪,就阻塞休眠。只要有一个文件就绪,就返回找出就绪的文件,然后处理就绪的文件。
}

常用函数:

1.头文件 
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
功能:实现IO多路复用,检测指定的文件描述符
参数:
@        nfds                文件描述符的个数   (最大的文件描述符maxfd + 1)
@        readfsd             监控的"读操作"文件描述符表
@        writefds            监控的"写操作"文件描述符表
@        exceptfds        其它的文件描述符集合 (一般是指异常的文件描述符集合)
@        timeout            NULL               阻塞方式调用
             struct timeval结构体中的数据为  0     超时时间设置为0s,
												非阻塞的方式调用
                          自己指定超时时间        指定的时间内等待

struct timeval{
        long  tv_sec;    //设置秒数
        long  tv_usec; //设置微秒
}


返回值:
成功 ,  若是返回值>0  返回就绪的文件的描述符的个数,并且在文件描述符表中清除其他未就绪的文件描述符。
       若是返回值=0  , 连接超时
 错误,返回-1, 并置errno

例如:
//设置5s中的超时时间
struct timeval tm = {5,0};
//设置8000微秒的超时时间
struct timeval tm = {0,800}

select (…,&tm)

思考:为什么我们的select第一个参数是最大的文件描述符 + 1呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

typedef struct{
	long int fds_bits[1024 / 32];
}fd_set;
FD_ISSET

注意:fd_set 结构体 实质为一个long类似的数组每一个数组元素都能与一打开的socket文件描述符建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。

 //将fd 从 set 的文件描述符数组集合中清除 [file descriptor clear]
void FD_CLR(int fd, fd_set *set); 

//判断 fd在set的文件描述符数组集合中是否已被设置(就绪)  [file descriptor set]
int  FD_ISSET(int fd, fd_set *set);                                

 //将fd添加到 set对应的文件描述符数组集合中 
void FD_SET(int fd, fd_set *set); 

//将set文件描述符数组集合清零,
void FD_ZERO(fd_set *set);   

例如:
假如我们的服务端想要从键盘输入数据,并且还能接受用户的连接。我们就可以进行如下操作:
利用监测listen_fd STDIN_FILENO 它们是否进行了读操作
<1>建立文件描述符表

fd_set  readfds;   // 定义一个读文件描述符集合
fd_set  readfds_bak; //对文件描述符集合的一个备份

FD_ZERO(&readfds_bak); //将表清空
FD_SET(STDIN_FILENO,&readfds_bak); //添加要监控的文件描述符集合
FD_SET(listenfd,&readfds_bak);

//找到最大的文件描述符
maxfd = listen_fd > STDIN_FILENO ? listenfd :STDIN_FILENO;

while(1)
{
    //重新添加关注的文件描述符集合。
    //由于select每次找到对应的文件描述符集合就会清除文件描述符集合。
    //因此,我们这里创建了一个reasfds_bak,select真正检测的是readfds中
    //的数据,对我们的readfds_bak没有影响。每次清楚之后,重新再把需要检测的文件
    //监控的文件描述符集合赋值回去。
    readfs = readfds_bak;
    n = select(maxfd + 1, &readfds,NULL,NULL,NULL);

   //从0到最大的文件描述符开始,对所有的描述符监控
    for(i = 0;i < =maxfd,;i++)
    {
            if(FD_ISSET(i,readfs))   //判断readfs中对应的文件描述符是否就绪
           {
              //说明标准数据就绪
              if(i === STDIN_FILENO)
              {
						//读的时候一定有数据
                      fgets(buf,sizeof(buf),stdin);
                      fputs(buf,stdout);
              }else if(i == listenfd)
              {
                      //说明有用户请求连接。
                      connect_fd = accept();
              }
            }
    }
    
}

练习:select函数应用于tcp服务器
1.监测标准输入,listen_fd,若标准输入就绪,读数据并打印,若有新连接请求,提取并打印请求方ip和端口如果有用户输入数据,则接收用户数据并打印出来。
server.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int listen_fd = 0;
	int connect_fd = 0;
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(my_addr);
	int n = 0 ;

	fd_set rfds; //添加文件描述符读表
	fd_set rfds_bak; //添加文件描述符读表的备份
	int maxfd = 0;
	int i = 0;

	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&my_addr,0,sizeof(my_addr));	
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));
	my_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.把ip和port进行绑定
	if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
	{
		perror("Fail to bind");	
		exit(EXIT_FAILURE);
	}

	//4.监听客户端的连接
	listen(listen_fd,5);
	printf("Listen....\n");


	//思考:若是我们的服务端需要从键盘输入数据存放到缓冲去中,并且我们的accept
	//     需要接受多个用户的连接请求,除了并发服务器外,还有什么方法?
	// 使用IO多路复用
		
	//1.将关注的表清除,创建一个空表
	FD_ZERO(&rfds_bak);
	FD_ZERO(&rfds);

	//2.STDIN_FILENO,listen_fd,将我们需要的数据添加到表中
	FD_SET(STDIN_FILENO,&rfds_bak);
	FD_SET(listen_fd,&rfds_bak);
	

	//3.寻找最大的文件描述符
	maxfd = listen_fd > STDIN_FILENO ? listen_fd : STDIN_FILENO; 


	while(1)
	{
		//由于每次循环找到,匹配的文件描述符表后,里面的数据
		//会被清除掉,因此,我们需要一个备份
		rfds = rfds_bak;			
		n = select(maxfd + 1,&rfds,NULL,NULL,NULL);
		if(n < 0)
		{
			perror("Fail to select");	
			exit(EXIT_FAILURE);
		}
	
		//0,1,3,4,5,6,7,8,9
		//遍历从0到maxfd中,所有的文件描述符
		for(i = 0;i <= maxfd;i++)
		{
			if(FD_ISSET(i,&rfds))	
			{
				//若是标准输入,表示用户从键盘输入了数据
				//存放到缓冲区,我们可以
				if(i == STDIN_FILENO)	//一直是就绪				
				{
					putchar('>');	

					fgets(buf,sizeof(buf),stdin);
					printf("buf : %s\n",buf);
				}else if(i == listen_fd){ 
					//5.准备接收客户端的连接请求
					connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);	
					if(connect_fd < 0)
					{
						perror("Fail to accept");	
						exit(EXIT_FAILURE);
					}
					
					printf("=============================================");
					printf("connect_fd : %d\n",connect_fd);
					printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
					printf("client port : %d\n", ntohs(client_addr.sin_port));
					printf("=============================================");

					FD_SET(connect_fd,&rfds_bak);
					//重新找到最大的文件描述符集合
					maxfd = connect_fd > maxfd ? connect_fd : maxfd;
				}
			
			}
		}
	}

	close(listen_fd);
	close(connect_fd);
	exit(EXIT_SUCCESS);
}


client.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int sockfd = 0;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(server_addr);
	int n = 0 ;
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&server_addr,0,sizeof(server_addr));	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.准备向服务端的请求连接
	if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
	{
		perror("Fail to accept");	
		exit(EXIT_FAILURE);
	}

	while(1)
	{
		memset(buf,0,sizeof(buf));
		putchar('>');
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';

		n = send(sockfd,buf,strlen(buf),0);	

		if(n < 0)
		{
			perror("Fail to recv!\n");	
			exit(EXIT_FAILURE);
		}else if(n == 0){
			printf("clinet is not connect\n");	
			exit(EXIT_FAILURE);
		}

		if(strncmp(buf,"quit",4) == 0)
			break;

	}

	close(sockfd);
	exit(EXIT_SUCCESS);
}


四.设置,获得socket属性

#include <sys/types.h>       
#include <sys/socket.h>

int 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);
功能:获得或者设置socket属性
参数:
    @sockfd          指定socket
    @level           指定操作socket层次
                     可选SOL_SOCKET    //通用的socket选项
                          IPPROTO_IP    //IP层
                          IPPROTO_TCP   //TCP层            
   @optname     由level来选择控制的方式
                    SOL_SOCKET  :SO_BROADCAST 允许发送广播包
                                :SO_RCVTIMEO  接收超时
                                :SO_KEEPALIVE 保持连接

    @optval  设置相应控制方式的值,定义变量传地址
    @optlen  optval之的大小
返回值:成功返回0;失败返回-1,置errno
例如:
设置5s的超时
struct timeval tm = {5,0};
if (setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tm,sizeof(tm)) < 0)
{
    perror("setsockopt fail");
    exit(EXIT_FAILURE);
}

网络超时检测的方法
1>设置socket超时

struct timeval
  {
    __time_t tv_sec;	 /* Seconds.  */
    __suseconds_t tv_usec;	/* Microseconds.  */
  };
  
socket 用sockfd

socklen_t len = sizeof(struct timeval); 
struct timeval tv;

tv.tv_sec = 5;    //超时5s
tv.tv_usec = 0;
//检测5s时间的超时检测
if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tv,len) < 0)
handle_error();

Service.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>	       /* See NOTES */

//./a.out ip port
int main(int argc, const char *argv[])
{
	int sockfd = 0;	
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	socklen_t len =  sizeof(my_addr);
	int n = 0;
	char buf[1024] = {0};

	struct timeval tv = {5,0};//设置5s的超时机制
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建文件描述符
	sockfd = socket(AF_INET,SOCK_DGRAM ,0);
	if(sockfd < 0)
	{
		perror("Fail to socket!");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和port,方便绑定
	memset(&my_addr,0,sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));
	my_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.把ip地址和port与sockfd绑定
	if(bind(sockfd,(struct sockaddr *)&my_addr,len) < 0)
	{
		perror("Fail to bind");	
		exit(EXIT_FAILURE);
	}

	//把sockfd设置为5s的接收超时
	if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) < 0)
	{
		perror("Fail to setsocketopt");	
		exit(EXIT_FAILURE);
	}

	//4.接收数据 ,循环接收,阻塞接收,若是没有数据
	//发送过来,则阻塞
	while(1)
	{
		memset(buf,0,sizeof(buf));
		n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&len);
		if(n < 0)
		{ 
			perror("recvfrom timeout deal with....\n");	
			continue;
		}else{

		printf("===============================\n");
		printf("Recv from IP : %s\n",inet_ntoa(client_addr.sin_addr));
		printf("Recv fromt port : %d\n", ntohs(client_addr.sin_port));
		printf("Recv %d bytes : %s\n",n,buf);
		}
	}
	return 0;
}

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>

//./a.out ip port
int main(int argc, const char *argv[])
{
	int sockfd = 0;	
	struct sockaddr_in server_addr;
	socklen_t len =  sizeof(server_addr);
	int n = 0;
	char buf[1024] = {0};
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建文件描述符
	sockfd = socket(AF_INET,SOCK_DGRAM ,0);
	if(sockfd < 0)
	{
		perror("Fail to socket!");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和port,方便绑定
	memset(&server_addr,0,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//发送数据
	while(1)
	{
		memset(buf,0,sizeof(buf));
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0'; //'\n'-->'\0'

		n = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&server_addr,len);
		if(n < 0)
		{
			perror("Fail to recvfrom");	
			exit(EXIT_FAILURE);
		}

	}
	return 0;
}

每5s中如果没有输入的话就会出现出错提醒。

2>select函数设置超时检测

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

socket由sockfd标识
例:
fd_set rfds,fds_bak;

FD_ZERO(&fds_bak);
FD_SET(&sockfd,&fds_bak);
struct timeval tv = {5,0};

Service.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int listen_fd = 0;
	int connect_fd = 0;
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(my_addr);
	int n = 0 ;

	struct timeval tv = {10, 0};
	struct timeval *p = NULL;
	p = &tv;

	fd_set rfds; //添加文件描述符读表
	fd_set rfds_bak; //添加文件描述符读表的备份
	int maxfd = 0;
	int i = 0;

	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&my_addr,0,sizeof(my_addr));	
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));
	my_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.把ip和port进行绑定
	if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
	{
		perror("Fail to bind");	
		exit(EXIT_FAILURE);
	}

	//4.监听客户端的连接
	listen(listen_fd,5);
	printf("Listen....\n");


	//思考:若是我们的服务端需要从键盘输入数据存放到缓冲去中,并且我们的accept
	//     需要接受多个用户的连接请求,除了并发服务器外,还有什么方法?
	// 使用IO多路复用
		
	//1.将关注的表清除,创建一个空表
	FD_ZERO(&rfds_bak);
	FD_ZERO(&rfds);

	//2.STDIN_FILENO,listen_fd,将我们需要的数据添加到表中
	FD_SET(STDIN_FILENO,&rfds_bak);
	FD_SET(listen_fd,&rfds_bak);
	

	//3.寻找最大的文件描述符
	maxfd = listen_fd > STDIN_FILENO ? listen_fd : STDIN_FILENO; 


	while(1)
	{
		//由于每次循环找到,匹配的文件描述符表后,里面的数据
		//会被清除掉,因此,我们需要一个备份
		rfds = rfds_bak;	
		//设置10s的超时,10s后超时非阻塞调用		
		n = select(maxfd + 1,&rfds,NULL,NULL,p);
		if(n < 0)
		{
			perror("Fail to select");	
			exit(EXIT_FAILURE);
		} else if (n == 0) {
			printf("select will timeout!\n");
			sleep(1);
			p->tv_sec = 10;
			p->tv_usec = 0;
		}
	
		//0,1,3,4,5,6,7,8,9
		//遍历从0到maxfd中,所有的文件描述符
		for(i = 0;i <= maxfd;i++)
		{
			if(FD_ISSET(i,&rfds))	
			{
				//若是标准输入,表示用户从键盘输入了数据
				//存放到缓冲区,我们可以
				if(i == STDIN_FILENO)	//一直是就绪				
				{
					putchar('>');	

					fgets(buf,sizeof(buf),stdin);
					printf("buf : %s\n",buf);
				}else if(i == listen_fd){ 
					//5.准备接收客户端的连接请求
					connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);	
					if(connect_fd < 0)
					{
						perror("Fail to accept");	
						exit(EXIT_FAILURE);
					}
					
					printf("=============================================");
					printf("connect_fd : %d\n",connect_fd);
					printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
					printf("client port : %d\n", ntohs(client_addr.sin_port));
					printf("=============================================");

					FD_SET(connect_fd,&rfds_bak);
					//重新找到最大的文件描述符集合
					maxfd = connect_fd > maxfd ? connect_fd : maxfd;
				} else {
					memset(buf, 0, sizeof(buf));
					n = recv(i, buf, sizeof(buf), 0);
					if (n < 0) {
						perror("Fail to recv");
						exit(EXIT_FAILURE);
					} else if (n == 0) {
						printf("client no connect\n");
						exit(EXIT_FAILURE);
					}
					//打印出接受的数据字节数
					printf("Recv %d bytes: %s\n", n, buf);

					//若是发现接受的字符串是quit,说明客户端断开连接i
					//此时的i应该从文件描述符集合中清除,FD_CLR, close(i)
					if (strncmp(buf, "quit", 4) == 0) {
						FD_CLR(i, &rfds_bak);
						close(i);
					}
				}
			
			}
		}
	}

	close(listen_fd);
	close(connect_fd);
	exit(EXIT_SUCCESS);
}


rfds = fds_bak;
select(maxfd + 1,&rfds,NULL,NULL,&tv); //设置5s的超时,非阻塞

3> 超时机制的常用实例。针对不同的网络异常进行处理

  1. 客户端出了问题(死机、重启了、网络断了…)
  2. 服务端出了问题(死机、重启了、网络断了、内存耗尽、…)
  3. 网络不正常

解决方法:设置心跳包。

Keepalive,是TCP中一个可以检测死TCP连接的机制。原理很简单,TCP协议会在空闲了一定的时间之后发送数据给对方。如果对方在一定的时间内没有回应,则表示超时,撤销连接。

typedef struct
{
     int type;        //发送包的类型,数据包,还是心跳包
     char buf[1024]; //发送的数据
}

#define  DATA_PACKET   10   //数据包
#define HEADT_PACKET  20    //心跳包

客户端: 创建子线程每过1s发送一个心跳包,主线程正常从键盘输入数据发送。
心跳包类型 MSG msg = {HEART_PACKET,“I am alive!”};
服务器端:
客气子进程进行接收客户端消息。通过alarm设置定时,收到正常包正常打印,收到心跳包重新打印数据,若是一定时间内没有收到内没有收到心跳包,就结束当前进程。

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define N 1024

typedef struct {
	int type;
	char buf[1024];
} msg_t;

#define DATA_PACKET 10
#define HEADT_PACKET 20

void handle_alarm() {
	printf("the GAME OVER!\n");
	exit(EXIT_SUCCESS);
	return;
}

void do_client(int sockfd) {
	msg_t msg;
	int n = 0;

	if (signal(SIGALRM, handler_alarm) == SIG_ERR) {
		perror("Fail to signal");
		exit(EXIT_FAILURE);
	}
	//10s后发送SIGALARM信号,10s是超时时间,10s后若是没有,收到心跳包,则结束该进程。
	//这里的定时不能删掉,否则第一次建立连接套接字后,服务器会一直阻塞在下面的recv等待
	//用户的发送数据,有了这条语句后10s没有数据发过来的话直接发送SIGALARM,处理handler_alarm函数,结束进程
	
	alarm(10);

	while(1) {
		memset(&msg, 0, sizeof(msg));
		if ((n = recv(sockfd, &msg, sizeof(msg), 0)) > 0)
		{
			if (msg.type == DATA_PACKET) {
				printf("Recv %d bytes : %s\n", strlen(msg.buf), msg.buf);
				continue;
			} else if (msg.type == HEADT_PACKET) {
				printf("head recv : %s\n", msg.buf);
				alarm(10);
			}
			if (strncmp(msg.buf, "quit", 4) == 0)
				break;
		}
		exit(EXIT_SUCCESS);
	}
}

void handler_process(int signum) {
	waitpid(-1, NULL, WNOHANG);
	printf("child exit is success!\n");
	return;
}

//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int listen_fd = 0;
	int connect_fd = 0;
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(my_addr);
	int n = 0 ;

	pid_t pid;
	int ret =0;
	
	struct timeval tv = {10, 0};
	struct timeval *p = NULL;
	p = &tv;

	fd_set rfds; //添加文件描述符读表
	fd_set rfds_bak; //添加文件描述符读表的备份
	int maxfd = 0;
	int i = 0;

	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}
	
	//对僵尸态子进程进行回收
	if (signal(SIGCHLD, handler_process) ==  SIG_ERR) {
		perror("Fail to signal");
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&my_addr,0,sizeof(my_addr));	
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));
	my_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.把ip和port进行绑定
	if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
	{
		perror("Fail to bind");	
		exit(EXIT_FAILURE);
	}

	//4.监听客户端的连接
	listen(listen_fd,5);
	printf("Listen....\n");


	//思考:若是我们的服务端需要从键盘输入数据存放到缓冲去中,并且我们的accept
	//     需要接受多个用户的连接请求,除了并发服务器外,还有什么方法?
	// 使用IO多路复用
		
	//1.将关注的表清除,创建一个空表
	FD_ZERO(&rfds_bak);
	FD_ZERO(&rfds);

	//2.STDIN_FILENO,listen_fd,将我们需要的数据添加到表中
	FD_SET(STDIN_FILENO,&rfds_bak);
	FD_SET(listen_fd,&rfds_bak);
	

	//3.寻找最大的文件描述符
	maxfd = listen_fd > STDIN_FILENO ? listen_fd : STDIN_FILENO; 


	while(1)
	{
		//由于每次循环找到,匹配的文件描述符表后,里面的数据
		//会被清除掉,因此,我们需要一个备份
		rfds = rfds_bak;	
		//设置10s的超时,10s后超时非阻塞调用		
		n = select(maxfd + 1,&rfds,NULL,NULL,p);
		if(n < 0)
		{
			perror("Fail to select");	
			exit(EXIT_FAILURE);
		} else if (n == 0) {
			printf("select will timeout!\n");
			sleep(1);
			p->tv_sec = 10;
			p->tv_usec = 0;
		}
	
		//0,1,3,4,5,6,7,8,9
		//遍历从0到maxfd中,所有的文件描述符
		for(i = 0;i <= maxfd;i++)
		{
			if(FD_ISSET(i,&rfds))	
			{
				//若是标准输入,表示用户从键盘输入了数据
				//存放到缓冲区,我们可以
				if(i == STDIN_FILENO)	//一直是就绪				
				{
					putchar('>');	

					fgets(buf,sizeof(buf),stdin);
					printf("buf : %s\n",buf);
				}else if(i == listen_fd){ 
					//5.准备接收客户端的连接请求
					connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);	
					if(connect_fd < 0)
					{
						perror("Fail to accept");	
						exit(EXIT_FAILURE);
					}
					
					printf("=============================================");
					printf("connect_fd : %d\n",connect_fd);
					printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
					printf("client port : %d\n", ntohs(client_addr.sin_port));
					printf("=============================================");

					FD_SET(connect_fd,&rfds_bak);
					//重新找到最大的文件描述符集合
					maxfd = connect_fd > maxfd ? connect_fd : maxfd;
				} else {
					memset(buf, 0, sizeof(buf));
					n = recv(i, buf, sizeof(buf), 0);
					if (n < 0) {
						perror("Fail to recv");
						exit(EXIT_FAILURE);
					} else if (n == 0) {
						printf("client no connect\n");
						exit(EXIT_FAILURE);
					}
					//打印出接受的数据字节数
					printf("Recv %d bytes: %s\n", n, buf);

					//若是发现接受的字符串是quit,说明客户端断开连接i
					//此时的i应该从文件描述符集合中清除,FD_CLR, close(i)
					if (strncmp(buf, "quit", 4) == 0) {
						FD_CLR(i, &rfds_bak);
						close(i);
					}
				}
			
			}
		}
	}

	close(listen_fd);
	close(connect_fd);
	exit(EXIT_SUCCESS);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 13:09:20  更:2022-05-09 13:10:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 18:44:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码