目錄
1、i/o復用概述:
2、select機制:
(1)、select程序建立如下:
(2)、select的相關函式:
(3)、select的相關代碼演示:
3、epoll機制:
1、epoll實作程序:
2、epoll_event 結構:
3、相關函式:
4、相關命令:
1、i/o復用概述:
通過一種機制,讓單個行程可以監視多個檔案描述符,一旦某個描述符就緒,能夠通知程式進行相應的讀寫操作
解釋:
i/o復用與非阻塞機制類似,但比非阻塞較好,i/o復用通過回圈的請求,但回圈請求的物件不單一,一旦滿足就進行相應的操作,比如:一個服務端建立,多個物件可以連接服務端,進行相應的操作,也可以采取退出,與服務端斷開連接操作,
i/o復用不需要創建行程與執行緒,也不需要對行程進行維護,其優勢就是在于系統開銷小
i/o復用有三種:select、epoll、poll(介紹前2種,select是普通的i/o復用,epoll是更高效的i/o復用,poll是select與epoll之間的過度)
2、select機制:
(1)、select程序建立如下:
1、將需要進行io操作的檔案描述符fd添加到select中
2、阻塞等待select系統呼叫回傳
3、資料到達后,select函式回傳
4、用戶執行緒正式發起read請求,讀取資料并繼續執行
select最大優勢在于,用戶可以在一個執行緒里可以同時處理多個i/o復用的請求
(2)、select的相關函式:
select函式用于在非阻塞中,當一個套接字或一組套接字有信號時通知你,系統提供select函式來實作多路復用輸入/輸出模型,
int select(int maxfd, //最大套接字的值+1
fd_set *rdset,
fd_set *wrset,
fd_set *exset,
struct timeval *timeout
);
引數解釋:
fd_set代表文字描述符的集合,2,3,4分別表示讀資料、寫資料、執行資料集合,內核當中,3個資料會集合成一個,傳入時候通常只需要2給引數,其余的給空就行
struct temval *timeout表示時間設定具體結構如下:
struct timeval
{
__time_t tv_sec; /* Seconds. */
__suseconds_t tv_usec; /* Microseconds. */
};
回傳值如果為-1表示出錯,回傳值為0表示超時,回傳值為其他值的話則表示客戶端與服務端成功去的聯系,
對于fd_set之類的設定給出了以下3種設定方式
1、將指定的檔案描述符集清空
FD_ZERO(fd_set *fdset);
2、用于在檔案描述符集合中增加一個新的檔案描述符
FD_SET(fd_set *fdset);
3、用于測驗指定的檔案描述符是否在該集合中
FD_ISSET(int fd,fd_set *fdset);
(3)、select的相關代碼演示:
1、服務端代碼:
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#define MAXBUF 1024
int main(int argc,char* argv[]){
int sockfd;//用來表示socket句柄
int new_fd;//用來表示客戶端連接進來后的句柄
struct sockaddr_in my_addr,client_addr;//分別為服務端和客戶端的操作
socklen_t len = sizeof(struct sockaddr_in); //存盤(struct sockaddr_in )的記憶體大小
unsigned int myport,lisnum;//設定埠號和監聽的個數
char buf[MAXBUF + 1];
int retval,maxfd;
//select 機制
fd_set rfds; //fd_set本質為一個 long 型別的陣列 8 * 128 = 1024
/*目的:將每個位置表示為0,將對應操作位置改成1*/
struct timeval tv;//超時時間
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket error\n");
return 1;
}
// argv[1]給ip地址,argv[2]給埠號
if(argv[2])
{
myport = atoi(argv[2]);
}else{
myport = 7838;
}
if(argv[3])
{
lisnum = atoi(argv[3]);
}else{
lisnum = 2;
}
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(myport);
if(argv[1])
my_addr.sin_addr.s_addr = inet_addr(argv[1]);
else
my_addr.sin_addr.s_addr = INADDR_ANY;
if((bind(sockfd,(struct sockaddr*)&my_addr,len)) == -1)
{
perror("bind error!\n");
return 1;
}
if(listen(sockfd,lisnum) == -1)
{
perror("listen error!\n");
return 1;
}
/*以上前期服務端的準備作業完成,等待客戶鏈接*/
while (1)
{
printf("wait for connect\n");
if((new_fd = accept(sockfd,(struct sockaddr*)&client_addr,&len)) == -1)
{
perror("accept error!\n");
return 1;
}else{
printf("client ip : %s,port : %d, sock : %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
new_fd);
}
//以上為阻塞,等待客戶端發資訊給服務端
while (1)
{
//每一次回圈將rfds陣列整體清0;
FD_ZERO(&rfds);
FD_SET(0,&rfds);
FD_SET(new_fd,&rfds);
maxfd = new_fd;
tv.tv_sec = 100;
tv.tv_usec = 0;
retval = select(maxfd + 1,&rfds,NULL,NULL,&tv);
if(retval == -1)
{
perror("select error!\n");
return 1;
}else if(retval == 0) continue;
else {
if(FD_ISSET(0,&rfds))//如果是標準輸入回傳
{
memset(buf, 0 ,MAXBUF + 1);
fgets(buf,MAXBUF,stdin);
if(!strncasecmp(buf,"quit",4))//判斷輸入是否為quit(不分大小寫)
{
printf("will quit\n") ;
break;
//該break僅退出select機制內回圈,鏈接并未結束
}
len = send(new_fd,buf,strlen(buf) - 1,0);//將輸入的發給用戶端
if(len < 0)
{
printf("send error\n");
break;
}
}
if(FD_ISSET(new_fd,&rfds))//判斷客戶端是否發送訊息來
{
memset(buf, 0 ,MAXBUF + 1);
len = recv(new_fd,buf,MAXBUF,0);
if(len < 0)
{
printf("recv error\n");
break;
}else{
printf("receive from client message is :%s,byte:%d",buf,len);
}
}
}
}
close(new_fd);
printf("need other connect (no->quit)");
fflush(stdout);
memset(buf,0,MAXBUF + 1);
fgets(buf,MAXBUF,stdin);
if(!strncasecmp(buf,"no",2))
{
printf("quit\n");
break;
}
}
close(sockfd);
return 0;
}
2、客戶端發送接受代碼:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<errno.h>
#define MAXBUF 1024
int main(int agrc,char *argv[])
{
int sockfd;
struct sockaddr_in My_clientaddr;
socklen_t len = sizeof(struct sockaddr_in);
char buf[MAXBUF + 1];
int retval,maxfd;
fd_set rfds;
struct timeval tv;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket error!\n");
return 1;
}
My_clientaddr.sin_family = AF_INET;
My_clientaddr.sin_port = htons(atoi(argv[2]));
if(argv[1])
My_clientaddr.sin_addr.s_addr = inet_addr(argv[1]);
else
My_clientaddr.sin_addr.s_addr = INADDR_ANY;
if(connect(sockfd,(struct sockaddr *)&My_clientaddr,sizeof(My_clientaddr)) != 0)
{
perror("connect error\n");
return 1;
}
printf("prepare for receiving.......\n");
while (1)
{
//每一次回圈將rfds陣列整體清0;
FD_ZERO(&rfds);
FD_SET(0,&rfds);
FD_SET(sockfd,&rfds);
maxfd = sockfd;
tv.tv_sec = 100;
tv.tv_usec = 0;
retval = select(maxfd + 1,&rfds,NULL,NULL,&tv);
if(retval == -1)
{
perror("select error!\n");
return 1;
}else if(retval == 0) continue;
else {
if(FD_ISSET(0,&rfds))//如果是標準輸入回傳
{
memset(buf, 0 ,MAXBUF + 1);
fgets(buf,MAXBUF,stdin);
if(!strncasecmp(buf,"quit",4))//判斷輸入是否為quit(不分大小寫)
{
printf("will quit\n") ;
break;
//該break僅退出select機制內回圈,鏈接并未結束
}
len = send(sockfd,buf,strlen(buf) - 1,0);//將輸入的發給用戶端
if(len < 0)
{
printf("send error\n");
break;
}
}
if(FD_ISSET(sockfd,&rfds))//判斷客戶端是否發送訊息來
{
memset(buf, 0 ,MAXBUF + 1);
len = recv(sockfd,buf,MAXBUF,0);
if(len < 0)
{
printf("recv error\n");
break;
}else{
printf("receive from client message is :%s,byte:%d",buf,len);
}
}
}
}
close(sockfd);
return 0;
}
效果展示:


3、epoll機制:
一個檔案是需要消耗資源的,所以對于檔案的監聽來說是有限度的,select里面如果監聽1024個左右的檔案句柄的時候可能會出一些問題,如果超過1024出現的問題更大,每個行程對于file的監控是有限的,所以如果能夠脫離檔案就會更合適一些,
為解決上面的問題,實作大規模并發服務器,采取了下方的epoll機制
epoll 是通過 epoll_fd 物件來實作,
epoll_fd 簡單理解為一個單個的檔案,檔案中存在多個 epoll_event 物件,每個 epoll_event 都表示一個事件,這些事件都可以添加到epoll_fd物件中,linux 對于事件的處理很方便,所以 epoll 可以實作一個高并發的監聽,
1、epoll實作程序:
1.1、創建 epoll_fd 物件
1.2、設定 epoll_event 物件
1.2.1、創建 epoll_event 物件
1.2.2、設定 epoll_event 物件
物件.events = EPOLLIN;//資料的讀取
物件.data.fd = listen_socket;
1.2.3、使用epoll_event物件
int epoll_ctl(int epfd,
int op,
int fd,
struct epoll_event *event);
1.3、使用 epoll_fd 物件
int epoll_wait(int epfd,
struct epoll_event *events,
int maxevents,
int timeout);
2、epoll_event 結構:
struct epoll_event {
__uint32_t events; //事件型別
epoll_data_t data; //資料
};
EPOLLIN: 表示對應的檔案描述符可以讀 EPOLLOUT
EPOLLET:表示被 epoll 設為邊緣觸發
3、相關函式:
3.1、進行各種控制操作以改變已打開檔案的的各種屬性
int fcntl(int fd,
int cmd,
long arg);
O_NONBLOCK:表示非阻塞i/o
3.2、獲取或者設定與某個套接字關聯的選項
int setsockopt(int sock,
int level,
int optname,
const void *optval,
socklen_t optlen);
level:SOL_SOCKET
optname:SO_REUSEADDR
4、相關命令:
可以作為 client 發起 tcp 或 udp 連接
nc ip地址 埠號 就可以實作
因此可以不需要寫用戶端代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/352226.html
標籤:其他
