struct termios tio
是什么
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>#define SERIAL_PORT "/dev/ttyS0"
#define BUF_SIZE 256int main(void)
{int fd;struct termios tio;/* 1. 打開串口 */fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_SYNC);if (fd < 0) {perror("open");return 1;}/* 2. 配置串口:115200 8N1,無流控 */memset(&tio, 0, sizeof(tio));cfsetospeed(&tio, B115200);cfsetispeed(&tio, B115200);tio.c_cflag &= ~PARENB; /* 無校驗 */tio.c_cflag &= ~CSTOPB; /* 1 停止位 */tio.c_cflag &= ~CSIZE;tio.c_cflag |= CS8; /* 8 數據位 */tio.c_cflag |= (CLOCAL | CREAD);tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* 原始模式 */tio.c_iflag &= ~(IXON | IXOFF | IXANY); /* 無軟件流控 */tio.c_oflag &= ~OPOST; /* 原始輸出 */tcflush(fd, TCIOFLUSH);tcsetattr(fd, TCSANOW, &tio);/* 3. 寫入數據 */char *tx = "hello world\n";if (write(fd, tx, strlen(tx)) < 0) {perror("write");close(fd);return 1;}/* 4. 循環讀取并查找 "flush" */char buf[BUF_SIZE];int len, total = 0;char line[BUF_SIZE * 2] = {0};while (1) {len = read(fd, buf, sizeof(buf));if (len < 0) {perror("read");break;}if (len == 0)continue;/* 把新數據追加到 line 緩沖區 */if (total + len < sizeof(line) - 1) {memcpy(line + total, buf, len);total += len;line[total] = '\0';}/* 判斷是否出現 "flush" */if (strstr(line, "flush")) {printf("Received \"flush\", exit.\n");break;}/* 打印剛收到的數據(可選) */printf("Rx[%d]: %.*s", len, len, buf);fflush(stdout); //立即刷新緩沖區//把 C 標準庫 為 `stdout`(通常是終端)維護的用戶態緩沖區立即刷到內核,確保之前 `printf/puts` 的內容立刻出現在屏幕上}close(fd);return 0;
}
- pthread_create
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <pthread.h>#define SERIAL_PORT "/dev/ttyS0"
#define BUF_SIZE 256static int stop = 0; /* 線程退出標志 */
static pthread_t tid;/* 串口初始化:115200 8N1,阻塞讀 */
static int open_serial(void)
{int fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY);if (fd < 0) {perror("open " SERIAL_PORT);return -1;}struct termios tio;tcgetattr(fd, &tio);cfmakeraw(&tio);cfsetispeed(&tio, B115200);cfsetospeed(&tio, B115200);tcflush(fd, TCIOFLUSH);tcsetattr(fd, TCSANOW, &tio);return fd;
}/* 線程函數:一直讀串口直到看到 "flush" */
static void *rx_thread(void *arg)
{int fd = (long)arg;char buf[BUF_SIZE];char line[BUF_SIZE * 2] = {0};int total = 0;while (!stop) {int n = read(fd, buf, sizeof(buf));if (n <= 0) continue;/* 追加到行緩沖區并檢查子串 */if (total + n < sizeof(line) - 1) {memcpy(line + total, buf, n);total += n;line[total] = '\0';}if (strstr(line, "flush")) {printf("[RX-THREAD] Found \"flush\", exiting.\n");break;}}close(fd);return NULL;
}int main(void)
{int fd = open_serial();if (fd < 0) return 1;/* 創建接收線程 */if (pthread_create(&tid, NULL, rx_thread, (void *)(long)fd) != 0) {perror("pthread_create");close(fd);return 1;}/* 主線程可以干別的事,這里簡單等待用戶輸入 */printf("Press Enter to quit...\n");getchar();/* 通知線程退出并等待結束 */stop = 1;pthread_cancel(tid); /* 若線程阻塞在 read(),可強制喚醒 */pthread_join(tid, NULL);printf("Main thread exit.\n");return 0;
}
- poll
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <poll.h>#define SERIAL_PORT "/dev/ttyS0"
#define BUF_SIZE 256static int open_serial(void)
{int fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NONBLOCK);if (fd < 0) { perror("open"); return -1; }struct termios tio;tcgetattr(fd, &tio);cfmakeraw(&tio);cfsetispeed(&tio, B115200);cfsetospeed(&tio, B115200);tcflush(fd, TCIOFLUSH);tcsetattr(fd, TCSANOW, &tio);return fd;
}int main(void)
{int fd = open_serial();if (fd < 0) return 1;char line[BUF_SIZE * 2] = {0};int total = 0;struct pollfd pfd = { .fd = fd, .events = POLLIN };while (1) {int ret = poll(&pfd, 1, -1); /* 無限等待可讀 */if (ret < 0) { perror("poll"); break; }if (pfd.revents & POLLIN) {char buf[BUF_SIZE];int n = read(fd, buf, sizeof(buf));if (n <= 0) continue;/* 追加到行緩沖并檢查 */if (total + n < sizeof(line) - 1) {memcpy(line + total, buf, n);total += n;line[total] = '\0';}if (strstr(line, "flush")) {printf("Got flush, exit.\n");break;}}}close(fd);return 0;
}
- epoll
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/epoll.h>#define SERIAL_PORT "/dev/ttyS0"
#define BUF_SIZE 256
#define MAX_EVENTS 1static int open_serial(void)
{int fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NONBLOCK);if (fd < 0) { perror("open"); return -1; }struct termios tio;tcgetattr(fd, &tio);cfmakeraw(&tio);cfsetispeed(&tio, B115200);cfsetospeed(&tio, B115200);tcflush(fd, TCIOFLUSH);tcsetattr(fd, TCSANOW, &tio);return fd;
}int main(void)
{int fd = open_serial();if (fd < 0) return 1;int epfd = epoll_create1(0);if (epfd < 0) { perror("epoll_create1"); return 1; }struct epoll_event ev = { .events = EPOLLIN, .data.fd = fd };if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0) {perror("epoll_ctl"); return 1;}char line[BUF_SIZE * 2] = {0};int total = 0;struct epoll_event events[MAX_EVENTS];while (1) {int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);if (nfds < 0) { perror("epoll_wait"); break; }for (int i = 0; i < nfds; ++i) {if (events[i].events & EPOLLIN) {char buf[BUF_SIZE];int n = read(fd, buf, sizeof(buf));if (n <= 0) continue;if (total + n < sizeof(line) - 1) {memcpy(line + total, buf, n);total += n;line[total] = '\0';}if (strstr(line, "flush")) {printf("Got flush, exit.\n");goto out;}}}}
out:close(fd);close(epfd);return 0;
}
深入淺出理解select、poll、epoll的實現
select | poll | epoll | |
---|---|---|---|
性能 | 隨著連接數的增加,性能急劇下降,處理成千上萬的并發連接數時,性能很差 | 隨著連接數的增加,性能急劇下降,處理成千上萬的并發連接數時,性能很差 | 隨著連接數的增加,性能基本沒有變化 |
連接數 | 一般1024 | 無限制 | 無限制 |
內存拷貝 | 每次調用select拷貝 | 每次調用poll拷貝 | fd首次調用epoll_ctl拷貝,每次調用epoll_wait不拷貝 |
數據結構 | bitmap | 數組 | 紅黑樹 |
內在處理機制 | 線性輪詢 | 線性輪詢 | FD掛在紅黑樹,通過事件回調callback |
時間復雜度 | O(n) | O(n) | O(1) |
目前支持I/O多路復用的系統調用有
select,pselect,poll,epoll
- 在Linux的緩存IO機制中,操作系統會將IO的數據緩存在文件系統的頁緩存(page cache)。也就是說,數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩存區拷貝到應用程序的地址空間中
select
select
存在三個問題- 每次調用
select
,都需要把被監控的fds集合從用戶態空間拷貝到內核態空間 - 能監聽端口的數量有限,單個進程所能打開的最大連接數由
FD_SETSIZE
宏定義 - 被監控的
fds
集合中,只要有一個有數據可讀,整個socket
集合就會被遍歷一次調用sk
的poll
函數收集可讀事件
- 每次調用
poll
- 針對select遺留的三個問題中(問題(2)是fd限制問題,問題(1)和(3)則是性能問題)
- poll只是使用pollfd結構而不是select的fd_set結構,這就解決了select的問題(2)fds集合大小1024限制問題
epoll
- 紅黑樹
二叉樹
AVL樹 - 平衡因子
紅黑樹
- 理解左旋右旋,實現插入
文檔鏈接說明
-
參考47
(99+ 封私信 / 57 條消息) IO多路復用——深入淺出理解select、poll、epoll的實現 - 知乎 -
參考48
5分鐘學二叉搜索樹(手機動畫版)_嗶哩嗶哩_bilibili
二叉搜索樹的刪除操作(手機動畫版)_嗶哩嗶哩_bilibili -
參考49
紅黑樹的旋轉操作_嗶哩嗶哩_bilibili
紅黑樹的插入算法_嗶哩嗶哩_bilibili