I/O多路復用

来源:https://www.cnblogs.com/lazyfiish/archive/2022/04/29/16208255.html
-Advertisement-
Play Games

Linux下實現I/O復用的系統調用方式主要:select、poll、epoll。 ...


I/O多路復用

Linux下實現I/O復用的系統調用方式主要:select、poll、epoll。

select

select系統調用可在一段指定時間內,監聽文件描述符上的可讀、可寫和異常等事件,判斷發生的事件需要輪詢。

註:網路程式中,select能處理的異常情況只有一種:socket上接收到帶外數據。

#include <sys/select.h>

//select監聽文件描述符事件
//nfds:		被監聽文件描述符中最大值+1	
//readfds:	可讀事件對應的文件描述符集,對應位置1;會被內核修改,返回時無事件的置0。
//writefds:	可寫事件對應的文件描述符集,對應位置1;會被內核修改,返回時無事件的置0。
//exceptfds:異常事件對應的文件描述符集,對應位置1;會被內核修改,返回時無事件的置0。
//timeout
//return:	返回就緒文件描述符中最大值+1
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

//fd_set比特向量操作
FD_ZERO(fd_set *fdset);				//清楚fdset中所有比特位
FD_SET(int fd, fd_set *fdset);		//設置fdset中比特位fd
FD_CLR(int fd, fd_set *fdset);		//清除fdset中比特位fd
FD_ISSET(int fd, fd_set *fdset);	//測試fdset中比特位fd是否被設置,用於判斷是否有事件發生

文件描述符就緒條件

  • socket可讀
    • 可讀:socket內核接收緩存區位元組數大於或等於低水位標記SO_RCVLOWAT。讀操作返回讀取位元組數。
    • 讀關閉:socket通信的對方關閉連接。讀操作返回0。
    • 錯誤:socket上有未處理的錯誤。可以使用getsockopt讀取和清楚錯誤。
    • 連接請求:監聽socket上有新的連接請求。
  • socket可寫
    • 可寫:socket內核發送緩存區中的可用位元組數大於或等於低水位標記SO_SNDLOWAT。寫操作返回實際寫位元組數。
    • 寫關閉:寫操作被關閉,對寫關閉的socket執行寫操作將觸發一個SIGPIPE信號。
    • 錯誤:socket上有未處理的錯誤。可以使用getsockopt讀取和清楚錯誤。
    • 連接結果:socket使用非阻塞的connect連接成功或者失敗(超時)之後。

poll

poll系統調用與select類似,需要輪詢判斷監聽文件描述符集上的事件,但沒有監聽文件描述符數量限制。

#include <poll.h>

//poll監聽文件描述符上指定的事件
//fds:		pollfd結構類型的數組
//nfds:		pollfd數組長度
//return:	返回就緒文件描述符中最大值+1
int poll(struct pollfd* fds, nfds_t nfds, int timeout);


//pollfd結構體,描述文件描述符上的可讀、可寫、異常等事件
struct pollfd{
    int fd;			//文件描述符
    short events;	//註冊的事件,一系列事件的按位或
    short revents;	//實際發生的事件,由內核填充,一些列事件的按位或
}

epoll

epoll相比select更加高效,主要體現在:

  • epoll使用內核事件表維護監聽的文件描述符集,避免頻繁的用戶空間與內核空間頻繁的拷貝開銷。
  • epoll直接返回發生事件的文件描述符即,避免了用戶程式輪詢的開銷。
  • epoll內部通過註冊回調函數的方式,監聽特定文件描述符上的時間,避免了內核輪詢開銷。
  • epoll提供了高效的邊沿觸發模式,邊沿觸髮帶來編程上的複雜性。

LT與ET模式

使用epoll監聽文件描述符時,有兩種事件觸發模式:

  • 水平觸發(LT):epoll預設使用水平觸發。
  • 邊沿觸發(ET):往epoll內核事件表這種註冊一個文件描述符上的EPOLLET時,將進行邊沿觸發。相比較水平觸發,邊沿觸發更加高效。

內核事件表

epoll在內核中使用事件表(紅黑樹)維護監聽的文件描述符,並支持對事件表的增刪改。

  • 創建事件表:epoll_create,返回一個內核事件表的文件描述符。
  • 操作事件表:epoll_ctl,對事件表進行增、刪、改。
  • 監聽事件表:epoll_wait,監聽事件表上的事件。
#include <sys/epoll.h>

//epoll事件結構體
strcut epoll_event{		
    _uint32_t events;	//epoll事件
    epoll_data_t data;	//用戶數據,包含監聽的文件描述符信息
}
//用戶數據聯合體
typedef union epoll_data{
    void* ptr;		//指定與fd相關的用戶數據,用戶數據包含監聽的文件描述符
    int fd;			//監聽的文件描述符fd(常用)
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;


//創建epoll內核事件表
//size:	提示內核需要的事件表多大
int epoll_create(int size);


//操作epoll內核事件表
//epfd:	epoll內核事件表
//op:	參數指定操作,包括增(EPOLL_CTL_ADD)、刪(EPOLL_CTL_DEL)、改(EPOLL_CTL_MOD)
//fd:	被操作的文件描述符
//event:事件 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);


//監聽事件表上文件描述符的事件
//epfd: 	epoll內核事件表
//events:	就緒事件數組,由內核修改
//maxevents:最多監聽事件數
//timeout:	
//return:	就緒文件描述符個數
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

示例

服務端

服務端代碼功能:

  1. 創建socket
  2. 為socket綁定固定地址和埠
  3. 監聽套接字等待客戶端連接
  4. 接收客戶端連接,獲取連接socket
  5. 使用select、poll或epoll監聽連接socket的可讀事件
  6. 連接socket可讀事件發生時,從連接socket中讀取數據

select

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	if (argc <= 2)
	{
		printf("usage: %s ip_address port_number\n", basename(argv[0]));
		return 1;
	}
	const char *ip = argv[1];
	int port = atoi(argv[2]);
	printf("ip is %s and port is %d\n", ip, port);

	int ret = 0;
	struct sockaddr_in address;
	bzero(&address, sizeof(address));
	address.sin_family = AF_INET;
	inet_pton(AF_INET, ip, &address.sin_addr);
	address.sin_port = htons(port);

	int listenfd = socket(PF_INET, SOCK_STREAM, 0);
	assert(listenfd >= 0);

	ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
	assert(ret != -1);

	ret = listen(listenfd, 5);
	assert(ret != -1);

	struct sockaddr_in client_address;
	socklen_t client_addrlength = sizeof(client_address);
	int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);
	if (connfd < 0)
	{
		printf("errno is: %d\n", errno);
		close(listenfd);
	}

	char remote_addr[INET_ADDRSTRLEN];
	printf("connected with ip: %s and port: %d\n", inet_ntop(AF_INET, &client_address.sin_addr, remote_addr, INET_ADDRSTRLEN), ntohs(client_address.sin_port));

	char buf[1024];
	fd_set read_fds;
	fd_set exception_fds;

	FD_ZERO(&read_fds);
	FD_ZERO(&exception_fds);

	int nReuseAddr = 1;
	setsockopt(connfd, SOL_SOCKET, SO_OOBINLINE, &nReuseAddr, sizeof(nReuseAddr));
	while (1)
	{
		memset(buf, '\0', sizeof(buf));
		FD_SET(connfd, &read_fds);
		FD_SET(connfd, &exception_fds);

		ret = select(connfd + 1, &read_fds, NULL, &exception_fds, NULL);
		printf("select one\n");
		if (ret < 0)
		{
			printf("selection failure\n");
			break;
		}

		if (FD_ISSET(connfd, &read_fds))
		{
			ret = recv(connfd, buf, sizeof(buf) - 1, 0);
			if (ret <= 0)
			{
				printf("can not read\n");
				break;
			}
			printf("get %d bytes of normal data: %s\n", ret, buf);
		}
		else if (FD_ISSET(connfd, &exception_fds))
		{
			ret = recv(connfd, buf, sizeof(buf) - 1, MSG_OOB);
			if (ret <= 0)
			{
				printf("can not read\n");
				break;
			}
			printf("get %d bytes of oob data: %s\n", ret, buf);
		}
	}

	close(connfd);
	close(listenfd);
	return 0;
}

poll

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>

int main(int argc, char *argv[])
{
	if (argc <= 2)
	{
		printf("usage: %s ip_address port_number\n", basename(argv[0]));
		return 1;
	}
	const char *ip = argv[1];
	int port = atoi(argv[2]);
	printf("ip is %s and port is %d\n", ip, port);

	int ret = 0;
	struct sockaddr_in address;
	bzero(&address, sizeof(address));
	address.sin_family = AF_INET;
	inet_pton(AF_INET, ip, &address.sin_addr);
	address.sin_port = htons(port);

	int listenfd = socket(PF_INET, SOCK_STREAM, 0);
	assert(listenfd >= 0);

	ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
	assert(ret != -1);

	ret = listen(listenfd, 5);
	assert(ret != -1);

	struct sockaddr_in client_address;
	socklen_t client_addrlength = sizeof(client_address);
	int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);
	if (connfd < 0)
	{
		printf("errno is: %d\n", errno);
		close(listenfd);
	}

	char remote_addr[INET_ADDRSTRLEN];
	printf("connected with ip: %s and port: %d\n", inet_ntop(AF_INET, &client_address.sin_addr, remote_addr, INET_ADDRSTRLEN), ntohs(client_address.sin_port));

	char buf[1024];
	pollfd poll_fds[1];
	bzero(poll_fds, sizeof(poll_fds));
	poll_fds[0].fd = connfd;
	poll_fds[0].events = POLLIN;

	int nReuseAddr = 1;
	setsockopt(connfd, SOL_SOCKET, SO_OOBINLINE, &nReuseAddr, sizeof(nReuseAddr));
	while (1)
	{
		memset(buf, '\0', sizeof(buf));

		ret = poll(poll_fds, 1, -1);
		printf("select one\n");
		if (ret < 0)
		{
			printf("selection failure\n");
			break;
		}

		if (poll_fds[0].events == POLLIN)
		{
			ret = recv(connfd, buf, sizeof(buf) - 1, 0);
			if (ret <= 0)
			{
				printf("can not read\n");
				break;
			}
			printf("get %d bytes of normal data: %s\n", ret, buf);
		}
	}

	close(connfd);
	close(listenfd);
	return 0;
}

epoll

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>

int main(int argc, char *argv[])
{
    if (argc <= 2)
    {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);
    printf("ip is %s and port is %d\n", ip, port);

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    struct sockaddr_in client_address;
    socklen_t client_addrlength = sizeof(client_address);
    int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);
    if (connfd < 0)
    {
        printf("errno is: %d\n", errno);
        close(listenfd);
    }

    char remote_addr[INET_ADDRSTRLEN];
    printf("connected with ip: %s and port: %d\n", inet_ntop(AF_INET, &client_address.sin_addr, remote_addr, INET_ADDRSTRLEN), ntohs(client_address.sin_port));

    char buf[1024];

    //創建內核事件表
    int epfd = epoll_create(10);

    //註冊事件
    epoll_event event;
    event.data.fd = connfd;
    event.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);

    //就緒事件數組
    epoll_event events[10];

    int nReuseAddr = 1;
    setsockopt(connfd, SOL_SOCKET, SO_OOBINLINE, &nReuseAddr, sizeof(nReuseAddr));
    while (1)
    {
        memset(buf, '\0', sizeof(buf));

        ret = epoll_wait(epfd, events, 1, -1);
        printf("select one\n");
        if (ret < 0)
        {
            printf("errno: %d\n", errno);
            printf("selection failure\n");
            break;
        }

        if (events[0].events == EPOLLIN)
        {
            ret = recv(connfd, buf, sizeof(buf) - 1, 0);
            if (ret <= 0)
            {
                printf("can not read\n");
                break;
            }
            printf("get %d bytes of normal data: %s\n", ret, buf);
        }
    }

    close(connfd);
    close(listenfd);
    return 0;
}

客戶端

客戶端代碼功能:

  1. 創建套接字
  2. 請求連接
  3. 發送數據
  4. 關閉連接。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    if (argc <= 2)
    {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in server_address;
    bzero(&server_address, sizeof(server_address));
    server_address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &server_address.sin_addr);
    server_address.sin_port = htons(port);

    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(sockfd >= 0);
    if (connect(sockfd, (struct sockaddr *)&server_address, sizeof(server_address)) < 0)
    {
        printf("connection failed\n");
    }
    else
    {
        printf("send oob data out\n");
        const char *oob_data = "abc";
        const char *normal_data = "123";
        send(sockfd, normal_data, strlen(normal_data), 0);
        send(sockfd, oob_data, strlen(oob_data), MSG_OOB);
        send(sockfd, normal_data, strlen(normal_data), 0);
    }

    close(sockfd);
    return 0;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目前【騰訊雲簡訊】為客戶提供【國內簡訊】、【國內語音】和【海外簡訊】三大服務,騰訊雲簡訊SDK支持以下操作: 國內簡訊 國內簡訊支持操作: • 指定模板單發簡訊 • 指定模板群發簡訊 • 拉取簡訊回執和簡訊回覆狀態 海外簡訊 海外簡訊支持操作: • 指定模板單發簡訊 • 指定模板群發簡訊 • 拉取短 ...
  • 前言 最近在學習java,遇到了一個經典列印題目,空心金字塔,初學者記錄,根據網上教程,有一句話感覺很好,就是先把麻煩的問題轉換成很多的簡單問題,最後一一解決就可以了,然後先死後活,先把程式寫死,後面在改成活的。 如下圖是空心金字塔最終的實現效果,先要求用戶輸入層數然後輸出 一.普通矩形 首先我們先 ...
  • 近日,New Relic發佈了最新的2022 Java生態系統報告,這份報告可以幫助我們深入的瞭解Java體系的最新使用情況,下麵就一起來看看2022年,Java發展的怎麼樣了,還是Java 8 YYDS嗎? Java 11成為新的標準 在2020年的時候,Java 11已經推出了1年多,但當時Ja ...
  • 框架,本質上是一些實用經驗集合。即是前輩們在實際開發過程中積攢下來的實戰經驗,累積成一套實用工具,避免你在開發過程中重覆去造輪子,特別是幫你把日常中能遇到的場景或問題都給屏蔽掉,框架的意義在於屏蔽掉開發的基礎複雜度、屏蔽掉此類共性的東西,同時建立嚴格的編碼規範,讓框架使用者開箱即用,並且只需要關註差... ...
  • 前言 又到了每日分享Python小技巧的時候了,今天給大家分享的是Python中兩種常見的數據類型合併方法。好奇知道是啥嗎?就不告 訴你,想知道就往下看呀。話不多說,直接上… 1 合併字典 在某些場景下,我們需要對兩個(多個)字典進行合併。例如需要將如下兩個字典進行合併: 1 dict1 = {"a ...
  • # Spring概述 1、Spring是輕量級開源JavaEE框架 2、Spring可以解決企業應用開發的複雜性 3、組成核心IOC、Aop IOC:控制反轉,把創建對象過程交給Spring進行管理 Aop:面向切麵,不修改源代碼進行功能增強 4、Spring特點 方便解耦,簡化開發 Aop編程支持 ...
  • 有朋友好奇為什麼將 閉包 歸於語法糖,這裡簡單聲明下,C# 中的所有閉包最終都會歸結於 類 和 方法,為什麼這麼說,因為 C# 的基因就已經決定了,如果大家瞭解 CLR 的話應該知道, C#中的類最終都會用 MethodTable 來承載,方法都會用 MethodDesc 來承載, 所以不管你怎麼玩 ...
  • linux網路編程示例 簡單TCP、UDP編程示例。 TCP TCP客戶端與伺服器通信模型 伺服器代碼 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...