当前位置: 代码迷 >> 综合 >> 网络编程-------->第三天,epoll(多路复用)、服务器模型、网络属性设置、网络超时
  详细解决方案

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

热度:65   发布时间:2023-12-03 10:36:45.0

    进程间通信


        网络通信
            三要素: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 100int 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:通常设置为0sockfd = 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,返回epollfdepollfd = epoll_create(SIZE);if(0 > epollfd){perror("epoll_create error");close(sockfd);return -1;}//调用epoll_ctl把等连接sockfd放在入epollfdmemset(&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==等连接sockfdif(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放在入epollfdmemset(&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前面已经置空为0memset(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.关闭socketclose(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 10010int main()
{int sockfd = 0;int newID = 0;int addrLength = 0;char buf[100] = {0};struct sockaddr_in addr;//创建socketsockfd = 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 );//关闭socketclose( 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 10010int 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;//创建socketsockfd = 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");//清空readFdsFD_ZERO(&readFds);//将等连接的套接字放入readFdsFD_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返回后,应用程序需要轮询内核返回的readFdsfor ( 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");}}}}}//关闭socketclose( 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  100void 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;//创建socketsocketID = 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");}//关闭socketclose(socketID);return 0;
}

数据库


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

  相关解决方案