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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...