网络编程-------->第三天,epoll(多路复用)、服务器模型、网络属性设置、网络超时

    进程间通信


        网络通信
            三要素:ip地址 + 协议 + port端口
        分层结构
            七层、四层
        协议:http, smtp, ftp, dns, telnet, tcp, udp, ip, icmp, igmp, arp, rarp
        tcp和udp
            SYN ACK、FIN ACK


        IO模型


            阻塞、非阻塞、信号驱动、多路复用
            open( fileName, O_RDONLY | O_NDELAY )
            open( fileName, O_RDONLY | O_NONBLOCK )
            socket( AF_INET, SOCK_STREAM/SOCK_DGRAM, 0 )
            int fcntl( 文件描述符,命令,参数 );
            命令:
                F_GETFL : 获取当前文件的打开方式,第三个参数无意义。
                F_SETFL : 设置新的打开方式,第三个参数就是新的打开方式。
            int flag = fcntl( fd, F_GETFL, 0 );
            flag &= ~O_NONBLOCK; //取消非阻塞
            flag |= O_NONBLOCK; //修改为非阻塞
            fcntl( fd, F_SETFL, flag );
            信号驱动:
                当文件的状态发生改变,通知内核,让内核给当前进程发送信号;
                当前进程收到信号,处理信号,对该文件进行IO操作。
            多路复用
                应用场景:一个进程中有多个阻塞IO
                将所有阻塞描述符进行监听,把阻塞集中到某一个地方,
                    只要有文件可以进行IO操作时,阻塞被解除,直接进行IO操作。
                select特点:
                    1. 存放描述符的表需要来回拷贝
                        比如:在调用select时,由userSpace拷贝到kernelSpace
                        在select返回时,由kernelSpace拷贝到userSpace
                    2. 存放描述符的表需要多次轮询
                        比如:在调用select时,kernelSpace轮询,查看哪个描述符可以进行IO操作
                        在select返回时,userSpace轮询,如果某个描述符在表中,则进行IO操作
                    3. 为了提高轮询的效率,先计算存放的最大描述符maxFd,轮询的范围是0~maxFd
                        总共maxFd+1个需要轮询。
                    4. 表的大小是多大?fd_set是什么类型?
                        /* fd_set for select and pselect.  */
                        typedef struct
                        {                        
                            long fds_bits[1024 / (8*sizeof(long))];                    
                        } fd_set;
                        fd_set readFds; 
                        sizeof(readFds) = (1024/(8*sizeof(long))) * sizeof(long)=(1024/8)bytes
                    5. select能监听的描述符的最大值是多少呢?
                        最大值是:1023
                        范围:0~1023,总共1024个


 服务器模型


        循环服务器
            tcp循环服务器:
            创建socket,绑定,监听
            while(1)
            {
                newID = accept接受连接
                while(1) //通信
                {
                    接收消息newID
                    处理消息
                    回复消息newID
                }
                close(newID);
            }
            close(socketID);
            特点:
                前一个客户端如果通信没有结束,后一个客户端无法响应。
            结论:tcp循环服务器很少用
            UDP循环服务器
            创建socket, 绑定 
            while(1)
            {
                接收消息
                处理消息
                回复消息 
            }
            关闭socket
            特点:
                可以同时处理多个客户端
        并发服务器
            通过多进程或者多线程来实现并发。
            tcp并发服务器
            创建socket,绑定,监听
            while(1)
            {
                newID = accept接受连接
                创建进程/线程,子进程/子线程用于通信                
            }
            close(socketID);
            特点:
                a. 通信的过程不会影响接受连接
                b. 如果客户端比较多的情况下,会创建大量的进程/线程,比较耗资源.//线程池
        多路复用服务器
            用于一个进程中有多个阻塞IO的情况

epoll多路复用

//TCP通信 	多路复用服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>		/*memset*/
#include <unistd.h>		/*close*/
#include <sys/time.h>
#include <fcntl.h>
#include <sys/epoll.h>

#define IP "127.0.0.1"	//本机测试地址
#define SPORT 10086
#define CPORT 10010

#define SIZE 100

int main()
{
	//1.设置变量
	int sockfd = 0;
	struct sockaddr_in addr;
	int addrlen = 0;
	int newID = 0;
	char buf[SIZE] = {'\0'};
	//设置epoll变量
	int epollfd = 0;
	struct epoll_event event;
	struct epoll_event arrEvents[10];
	int ret = 0;
	
	//2.创建socket套接字
	//参数1:地址族--->使用网络协议族
	//参数2:套接字类型---->使用流式套接字
	//参数3:通常设置为0
	sockfd = socket(PF_INET, SOCK_STREAM, 0);
	if(0 > sockfd)
	{
		perror("socket error");
		return -1;
	}
	printf("socket ok\n");
	
	//3.绑定地址(自己)ip+port端口
	addrlen = sizeof(addr);
	memset(&addr,0,addrlen);
	addr.sin_family = PF_INET;	//地址族--->使用网络协议族
	addr.sin_port = htons(SPORT);	//htons(主机字节序转换网络字节序函数),short短整型---->设置端口
	addr.sin_addr.s_addr = inet_addr(IP);	//inet_addr(IP地址的转换),转换成32位的网络字节序二进制值
	if(0 > bind(sockfd,(struct sockaddr *)&addr,addrlen))
	{
		perror("bind error");
		close(sockfd);	//关闭套接字
		return -1;
	}
	printf("bind ok\n");

	//4.建立监听----->socket变为被动,只能用于三次握手
	//参数2:监听队列存放三次握手还没有结束的客户端
	if(0 > listen(sockfd,5))
	{
		perror("listen error");
		close(sockfd);	//关闭套接字
		return -1;
	}
	printf("listen ok\n");

	//创建epoll,返回epollfd
	epollfd = epoll_create(SIZE);
	if(0 > epollfd)
	{
		perror("epoll_create error");
		close(sockfd);
		return -1;
	}
	
	//调用epoll_ctl把等连接sockfd放在入epollfd
	memset(&event,0,sizeof(event));
	event.data.fd = sockfd;
	event.events = EPOLLIN;
	if (0 > epoll_ctl( epollfd, EPOLL_CTL_ADD, sockfd, &event))
	{
		perror("epoll_ctl  sockfd  EPOLL_CTL_ADD error");
		close( sockfd );
		close( epollfd );
		return -1;
	}	
	
	while(1)
	{
		//epoll_wait阻塞等待文件可以进行IO操作
		//参数4:时间
		ret = epoll_wait( epollfd, arrEvents, sizeof(arrEvents)/sizeof(arrEvents[0]), -1); 	
		
		int i = 0;
		//遍历epoll_wait返回的数组
		addrlen = sizeof(addr);
		for(i = 0;i < ret;i++)
		{
			//判断arrEvents[i].fd==等连接sockfd
			if(arrEvents[i].data.fd == sockfd)
			{
				memset(&addr,0,addrlen);
				//5.接受连接------>若成功,返回已经连接的套接字
				newID=accept(sockfd, (struct sockaddr *)&addr,&addrlen);
				if(0 > newID)
				{
					perror("accept error");
					close(sockfd);	//关闭套接字
					return -1;
				}
				printf("accept ok %d\r\n", newID);
				printf("client ip=%s, port=%d\r\n", (char *)inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
				//调用epoll_ctl把newID放在入epollfd
				memset(&event,0,sizeof(event));
				event.data.fd = newID;
				event.events = EPOLLIN;
				if (0 > epoll_ctl( epollfd, EPOLL_CTL_ADD, newID, &event))
				{
					perror("epoll_ctl  sockfd  EPOLL_CTL_ADD error");
					break;
				}	
			}
				else
				{
					//6.通信----->接收 发送  使用已经连接的套接字
					//参数3:通常设置为0
					//参数2:为什么用不了strlen测buf有效长度----->因为strlen测有效字符,buf前面已经置空为0
					memset(buf,0,SIZE);
					if(0 < recv(arrEvents[i].data.fd, buf, sizeof(buf)-1,0))
					{
							printf("服务器已收到:%s\n",buf);
							if(0 == strncmp(buf,"quit",4))
							{
								break;				
							}
					}
					else
					{
						printf("nothing,客户端已经关闭\n");
						close(arrEvents[i].data.fd);//关闭已经连接的套接字
						if (0 > epoll_ctl( epollfd, EPOLL_CTL_DEL, arrEvents[i].data.fd, NULL))
						{
							perror("epoll_ctl  EPOLL_CTL_DEL error");
							break;
						}
					}
			
			
					if ( 0 < send(arrEvents[i].data.fd, buf, strlen(buf),0))
					{
						printf("回答完毕\n");
					}
				}
		}	
	}
	
	//8.关闭socket
	close(sockfd);
	close(epollfd);

	return 0;
}

epoll可以监听的描述符总共有多少个,最大值是多少?


   网络属性设置


        int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
        参数:
            sockfd: socket()返回值
            level: 比如:设置地址复用,那么,level是SOL_SOCKET
            optname:选项名称,SO_REUSEADDR
            optval: 当前选项对应传的值,当前选项是地址复用,值有两种允许和不允许, int on = 1;
            optlen: 前面optval指针所指空间的大小,当前选项是地址复用,大小就是sizeof(int);
        解决:当前进程结束后,再次运行时,绑定port失败.
        例: 
            设置本地地址复用  防止前一次进程结束,下次运行进程绑定失败
            int on = 1;
            setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );


  网络超时


        当前的tcp的等连接套接字/已连接套接字和udp的套接字的接收都是阻塞.
        a. setsockopt
            struct timeval tv = {3, 0};        //阻塞3s  0ms
            setsockopt( sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) );   

//tcp通信的服务器
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>

#define IP  "127.0.0.1"  //本机测试地址
#define SPORT 10086
#define CPORT 10010

int main()
{
	int sockfd = 0;
	int newID = 0;
	int addrLength = 0;
	char buf[100] = {0};
	struct sockaddr_in addr;
	
	//创建socket
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 )
	{
		perror("socket error");
		return -1;
	}
	printf("socket ok \r\n");
	
	int on = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	
	//绑定自己的地址
	addrLength = sizeof(addr);
	memset(&addr, 0, addrLength );
	addr.sin_family = PF_INET;
	addr.sin_port = htons( SPORT );
	addr.sin_addr.s_addr = inet_addr( IP );	
	if (0 > bind( sockfd, (struct sockaddr *)&addr, addrLength) )
	{
		perror("bind error");
		close(sockfd);
		return -1;
	}
	printf("bind ok \r\n");
	
	//监听 socket变成被动,只能用于三次握手
	if (0 > listen( sockfd, 5 ))
	{
		perror("listen error");
		close(sockfd);
		return -1;
	}
	printf("listen ok \r\n");
	
	struct timeval t = {7, 0};
	setsockopt( sockfd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t) );
	
	memset(&addr, 0, addrLength);	
	//接受连接 -- 成功时,将返回已经连接的套接字
	newID = accept( sockfd, (struct sockaddr *)&addr, &addrLength );
	if ( newID < 0 )
	{
		perror("accept error");
		close(sockfd);
		return -1;
	}
	printf("accept ok \r\n");
	printf("client ip=%s, port=%d\r\n", (char *)inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
		
	//通信 接收  发送    使用已经连接的套接字
	if ( 0 < recv( newID, buf, sizeof(buf) - 1, 0 ))
		printf("服务器收到:%s\r\n", buf);
	else 
		printf("nothing\r\n");
	
	//关闭已经连接的套接字
	close( newID );
	//关闭socket
	close( sockfd );
	
	return 0;
}


        b. select 
            struct timeval tv = {3, 0};
            select( maxFd + 1, &readFds, NULL, NULL, &tv );

//tcp通信的多路复用服务器
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>

#define IP  "127.0.0.1"  //本机测试地址
#define SPORT 10086
#define CPORT 10010

int main()
{
	int sockfd = 0;
	int newID = 0;
	int addrLength = 0;
	char buf[100] = {0};
	struct sockaddr_in addr;
	int maxFd = 0;
	fd_set readFds;
	fd_set tmpFds;
	
	//创建socket
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 )
	{
		perror("socket error");
		return -1;
	}
	printf("socket ok \r\n");
	
	//绑定自己的地址
	addrLength = sizeof(addr);
	memset(&addr, 0, addrLength );
	addr.sin_family = PF_INET;
	addr.sin_port = htons( SPORT );
	addr.sin_addr.s_addr = inet_addr( IP );	
	if (0 > bind( sockfd, (struct sockaddr *)&addr, addrLength) )
	{
		perror("bind error");
		close(sockfd);
		return -1;
	}
	printf("bind ok \r\n");
	
	//监听 socket变成被动,只能用于三次握手
	if (0 > listen( sockfd, 5 ))
	{
		perror("listen error");
		close(sockfd);
		return -1;
	}
	printf("listen ok \r\n");
		
	//清空readFds
    FD_ZERO(&readFds);
	//将等连接的套接字放入readFds
	FD_SET( sockfd, &readFds );
	tmpFds = readFds;
	//maxFd是select所有监听描述符中的最大值
	maxFd = sockfd;
	
	while(1)
	{	
		struct timeval t = {7, 0};
		//tmpFds保存最完整的被监听的描述符
		readFds = tmpFds;
		//调用select函数,它是一个阻塞函数
		int j = select( maxFd + 1, &readFds, NULL, NULL, &t );
		if ( j < 0 )
		{
			perror("select error");
			break;
		}
		else if ( 0 == j )
		{
			printf("select time out , try again\r\n");
			continue;
		}
		
		int i = 0;
		//select返回后,应用程序需要轮询内核返回的readFds
		for ( i = 0; i < maxFd + 1; i++ )
		{
			if ( FD_ISSET( i, &readFds ) )
			{ 
				if ( i == sockfd ) //等连接socket
				{
					memset(&addr, 0, addrLength);	
					//接受连接 -- 成功时,将返回已经连接的套接字
					newID = accept( sockfd, (struct sockaddr *)&addr, &addrLength );
					if ( newID < 0 )
					{
						perror("accept error");
						close(sockfd);
						return -1;
					}
					printf("accept ok %d\r\n", newID);
					printf("client ip=%s, port=%d\r\n", (char *)inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
					//把newID放到tmpFds里
					FD_SET( newID, &tmpFds );
					if ( maxFd < newID )
					{
						maxFd = newID;
					}
				}
				else 
				{
					memset(buf, 0, sizeof(buf));
					//通信 接收  发送    使用已经连接的套接字
					if ( 0 < recv( i, buf, sizeof(buf) - 1, 0 ))
					{
						printf("服务器收到:%s\r\n", buf);
					}
					else 
					{
						printf("nothing,客户端已经关闭\r\n");
						close(i);//关闭已经连接的套接字
						FD_CLR(i, &tmpFds);
					}
					if ( 0 < send( i, buf, strlen(buf), 0 ) )
					{
						printf("回答完毕!\r\n");
					}
				}
			}
		}
	}
	//关闭socket
	close( sockfd );
	
	return 0;
}


        c. SIGALRM
            void handle( int sig ){...}
            sigaction( SIGALRM, NULL, &act );
            act.sig_handle = handle;
            act.sig_flags &= ~SA_RESTART; //
            sigaction( SIGALRM, &act, NULL);
            alarm(5);

//udp通信  被动接收  服务端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>

#define IP "127.0.0.1"
#define SPORT 50000
#define CPORT 50001
#define SIZE  100

void handler( int sig )
{
	if ( SIGALRM == sig )
	{
		printf("sig---------------------\r\n");
	}
	else 
	{
		printf("wrong wrong \r\n");
	}
}

int main()
{
	int socketID = 0;
	int addrLength = 0;
	struct sockaddr_in addr ;
	char buf[SIZE] = {0};
	int ret = 0;
	
	//创建socket
	socketID = socket( PF_INET, SOCK_DGRAM, 0);
	if ( socketID < 0 )
	{
		perror("socket error");
		return -1;
	}
	printf(" socket ok\r\n" );
	
	//绑定自己的地址
	addrLength = sizeof(addr);
	memset( &addr, 0, addrLength );
	addr.sin_family = PF_INET;
	addr.sin_port = htons( SPORT );
	addr.sin_addr.s_addr = INADDR_ANY;
	if ( 0 > bind( socketID, (struct sockaddr *)&addr, addrLength ))
	{
		perror("bind error");
		close(socketID);
		return -1;
	}
	printf("bind ok\r\n");
	
	struct sigaction  act;
    sigaction(SIGALRM, NULL, &act);//获取当前进程对SIGALRM的处理方式
    act.sa_handler = handler;
    act.sa_flags &= ~SA_RESTART; //不再重启阻塞
    sigaction(SIGALRM, &act, NULL);//设置当前进程对SIGALRM的处理方式
    alarm(5);
	
	//接收消息
	memset( buf, 0, SIZE );
	memset( &addr, 0, addrLength );
	ret = recvfrom( socketID, buf, SIZE - 1, 0, (struct sockaddr *)&addr, &addrLength);
	if ( ret > 0 )
	{
		printf("ip=%s, port=%d ", (char *)inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
		printf(" said:%s\r\n", buf);
	}
	else if ( ret < 0 )
	{
		perror(" recvfrom error ");
	}		
	else
	{
		printf("recvfrom return %d\r\n", ret);
	}
	
	//发送消息	
	//输入准备发送的消息
	memset( buf, 0, SIZE );
	strncpy( buf, "太好了,真棒!", SIZE - 1 );
	ret = sendto( socketID, buf, strlen(buf), 0, (struct sockaddr *)&addr, addrLength );
	if ( ret > 0 )
	{
		printf("send ok\r\n");
	}
		
	//关闭socket
	close(socketID);
	return 0;
}

数据库


        数据持久化-->文件(普通文件, 数据库)
        相关工作
            在公司比较重要的岗位/人员稳定
        常见的数据库
            oracle, DB2, sqlServer, mySql, sqlite
        数据库的特点*****(在就业前网上查一下)
        安装数据库
            1. apt-get install ***
            2. 下载源码, 自行编译, 拷贝库到指定位置, 调用库中的函数实现自己的功能
            3. 下载源码, 将源码和自己的代码一起编译生成可执行文件即可.
        操作数据库
            1. 图形化界面(测试人员/终端用户)
            2. shell命令(运维人员)
            3. API应用程序接口(函数)(软件开发人员)
        学习的重点: SQL(结构化查询语言)******        
---------------
进程:
    进程是程序运行的一次过程。
    进程是动态的,不能保存的。
    进程是操作系统分配资源的最小单位。
线程:
    轻量级的进程。
    线程是操作系统调度的最小单位。
    线程只有独立的栈段,和主线程共享其它内存资源。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值