I/O複用3個小例項+將signal轉化為IO事件
I/O複用3個小例項:
-
nonblock connect():利用error:EINPROGRESS
非阻塞connect()
man手冊connect()
The socket is nonblocking and the connection cannot be completed immediately. (UNIX domain sockets failed with EAGAIN instead.) It is possible to select(2) or poll(2) for completion by selecting the socket for writing. After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).
非阻塞socket發起連線不能立馬完成,利用io多路的寫事件,然後用getsocketopt拿到socket的error,判斷error是否==0,=0表示連線成功
#include "me.h" #define BUFFER_SIZE 1023 int setnonblocking(int fd) { int old_option = fcntl(fd,F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd,F_SETFL,new_option); return old_option; } //Linux下nonblock-connect() int unblock_connect(const char *ip,int port,int time) { struct sockaddr_in addr; memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET,ip,&addr.sin_addr); int sockfd = socket(AF_INET,SOCK_STREAM,0); int fdopt = setnonblocking(sockfd);//fdopt儲存的是原來的fd status int ret = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr)); // 非阻塞connect直接連線成功 if (ret == 0) { printf("connect with server immediately\n"); fcntl(sockfd,F_SETFL,fdopt); return sockfd; } else if (errno != EINPROGRESS) { printf("unblock connect not support\n"); return -1; } //select使用 fd_set readfds; fd_set writefds; struct timeval timeout; FD_ZERO(&readfds); FD_SET(sockfd,&writefds); timeout.tv_sec = time; timeout.tv_usec = 0; ret = select(sockfd + 1,&readfds,&writefds,NULL,&timeout); if (ret <= 0) { //超時未發生,或者有錯誤 printf("connection time out\n"); close(sockfd); return -1; } //或者sockfd不在返回的writefds表中 if (!FD_ISSET(sockfd,&writefds)) { printf("no events on sockfd found\n"); close(sockfd); return -1; } //E IN PROGRESS 情況, int error = 0; socklen_t length = sizeof(error); //獲得SOL_SOCKET的SO_ERROR選項 if (getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&length) < 0) { printf("get socket option failed\n"); close(sockfd); return -1; } //error!=0說明不連線不成功,(出現其他錯誤 if (error != 0) { printf("connection failed after select with the error: %d\n",error); close(sockfd); return -1; } //連線成功 printf("connection ready after select with the socket: %d\n",sockfd); fcntl(sockfd,F_SETFL,fdopt);//恢復成 block fd return sockfd; } int main(int argc,char* argv[]) { if (argc <= 2) error_handle("argc <= 2") const char* ip = argv[1]; int port = atoi(argv[2]); int sockfd = unblock_connect(ip,port,10); if (sockfd < 0) return 1; close(sockfd); return 0; }
在*inx下不具有移植性
-
epoll實現監聽同一埠的tcp和udp服務
#include "me.h" #define MAX_EVENT_NUMBER 1024 #define TCP_BUFFER_SIZE 512 #define UDP_BUFFER_SIZE 1024 int setnonblocking(int fd) { int old_option = fcntl(fd,F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd,F_SETFL,new_option); return old_option; } void addfd(int epollfd,int fd) { struct epoll_event event; event.data.fd = fd; event.events = EPOLLIN | EPOLLET;//邊沿觸發模式 epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event); setnonblocking(fd); } int main(int argc,char* argv[]) { if (argc <= 2) error_handle("argc <= 2") const char *ip = argv[1]; int port = atoi(argv[2]); struct sockaddr_in addr; memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET,ip,&addr.sin_addr); //建立TCP SOCKET並且bind and listen int listenfd = socket(AF_INET,SOCK_STREAM,0); assert(listenfd >= 0); int ret = bind(listenfd,(struct sockaddr*)&addr,sizeof(addr)); assert(ret != -1); ret = listen(listenfd,5); assert(ret != -1); //建立UDP SOCKET memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET,ip,&addr.sin_addr); int udpfd = socket(AF_INET,SOCK_DGRAM,0); assert(udpfd >= 0); //監聽相同埠 ret = bind(udpfd,(struct sockaddr*)&addr,sizeof(addr)); assert(ret != -1); struct epoll_event events[MAX_EVENT_NUMBER]; int epollfd = epoll_create(5); assert(epollfd != -1); //註冊TCP UDP 監聽socket上的可讀事件 addfd(epollfd,listenfd); addfd(epollfd,udpfd); while(1) { int number = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);//一直阻塞到有事件發生 if (number < 0) { printf("epoll failure\n"); break; } for (int i=0; i<number; i++) { int sockfd = events[i].data.fd; if (sockfd == listenfd) { struct sockaddr_in client; socklen_t client_addrlength = sizeof(client); int connfd = accept(sockfd,(struct sockaddr*)&client,&client_addrlength); addfd(epollfd,connfd); } //udp socket有EPOLLIN事件應該是可讀 else if (sockfd == udpfd) { char buf[UDP_BUFFER_SIZE]; memset(buf,'\0',UDP_BUFFER_SIZE); struct sockaddr_in client; socklen_t client_addrlength = sizeof(client); ret = recvfrom(sockfd,buf,UDP_BUFFER_SIZE-1,0,(struct sockaddr*)&client,&client_addrlength); if (ret > 0)//echo服務 sendto(sockfd,buf,UDP_BUFFER_SIZE-1,0,(struct sockaddr*)&client,client_addrlength); } else if (events[i].events & EPOLLIN) { //建立TCP連線的新加入的TCP SOCKET char buf[TCP_BUFFER_SIZE]; //ET模式不重複觸發,迴圈讀取完畢 while(1) { memset(buf,'\0',TCP_BUFFER_SIZE); ret = recv(sockfd,buf,TCP_BUFFER_SIZE-1,0); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) break;//下次再處理 close(sockfd);//其他的error, epoll_ctl(epollfd,EPOLL_CTL_DEL,sockfd,NULL); break; } else if (ret == 0) { close(sockfd); epoll_ctl(epollfd,EPOLL_CTL_DEL,sockfd,NULL); break; } else {//正常接收,正常傳送 send(sockfd,buf,ret,0); } } } else { printf("something else happened \n"); } } } close(listenfd); return 0; }
-
poll實現的聊天室server,
#define _GNU_SOURCE #include "me.h" #define USER_LIMIT 50 #define BUFFER_SIZE 64 #define FD_LIMIT 65535 //儲存client的資料 typedef struct { struct sockaddr_in addr; char* write_buf; char buf[BUFFER_SIZE]; }client_data; int setnonblocking(int fd) { int old_option = fcntl(fd,F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd,F_SETFL,new_option); return old_option; } int main(int argc,char* argv[]) { if (argc <= 2) error_handle("argc <= 2") const char* ip = argv[1]; int port = atoi(argv[2]); struct sockaddr_in addr; memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET,ip,&addr.sin_addr); int listenfd = socket(AF_INET,SOCK_STREAM,0); assert(listenfd >= 0); int ret = bind(listenfd,(struct sockaddr*)&addr,sizeof(addr)); assert(ret != -1); ret = listen(listenfd,5); assert(ret != -1); client_data* users = malloc(sizeof(client_data) * FD_LIMIT); struct pollfd fds[USER_LIMIT + 1];//最多監聽USER_LIMIT個使用者 int user_counter = 0; for (int i=0; i <= USER_LIMIT; i++) { fds[i].fd = -1; fds[i].events = 0; } //把listenfd首先加入到poll事件中 fds[0].fd = listenfd; fds[0].events = POLLIN | POLLERR; fds[0].revents = 0; while(1) { ret = poll(fds,user_counter+1,-1); if (ret < 0) { printf("poll failure\n"); break; } //poll會返回所有註冊的事件,要遍歷一次所有的事件 for(int i=0; i<user_counter+1; i++) { //處理新連線 if ((fds[i].fd == listenfd) && (fds[i].revents & POLLIN)) { struct sockaddr_in client; socklen_t client_addrlength = sizeof(client); int connfd = accept(listenfd,(struct sockaddr*)&client,&client_addrlength); if (connfd < 0) { printf("errno is: %d\n",errno); continue; } /*請求超過連線限制*/ if (user_counter >= USER_LIMIT) { const char* info = "too many users\n"; printf("%s",info); send(connfd,info,strlen(info),0); close(connfd); continue; } //儲存新連線connfd的資訊 user_counter++; users[connfd].addr = client; setnonblocking(connfd);//設定連線sock是non block的 fds[user_counter].fd = connfd; fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR; fds[user_counter].revents = 0; printf("comes a new user,now have %d users\n",user_counter); } //fd發生error事件 else if (fds[i].revents & POLLERR) { printf("get an error from %d\n",fds[i].fd); char errors[100]; memset(errors,'\0',sizeof(errors)); socklen_t length = sizeof(errors); if (getsockopt(fds[i].fd,SOL_SOCKET,SO_ERROR,errors,&length) < 0) printf("get socket option failed\n"); continue; } //客戶端關閉連線 else if (fds[i].revents & POLLRDHUP) { //客戶端資源 / 監聽事件 2者移動到i處 users[fds[i].fd] = users[fds[user_counter].fd]; fds[i] = fds[user_counter--]; close(fds[i--].fd); printf("a client left\n"); } //客戶端發來資訊 else if (fds[i].revents & POLLIN) { int connfd = fds[i].fd; memset(users[connfd].buf,'\0',BUFFER_SIZE); ret = recv(connfd,users[connfd].buf,BUFFER_SIZE-1,0); printf("get %d bytes of client data %s from %d \n",ret,users[connfd].buf,connfd); if (ret < 0) { if (errno != EAGAIN)//如果是其他錯誤,則關閉此連線,釋放監聽事件 { close(connfd); users[fds[i].fd] = users[fds[user_counter].fd]; fds[i--] = fds[user_counter--]; } } else if (ret == 0) {} else {//接收到客戶端發來的資料, for (int j=1; j<=user_counter; j++) { if (fds[j].fd == connfd)//不需要將資訊傳送給自己 continue; // fds[j].events |= ~POLLIN; fds[j].events &= ~POLLIN;//取消原POLLIN事件 fds[j].events |= POLLOUT;//在有接受資料的fd上註冊POLLOUT事件 users[fds[j].fd].write_buf = users[connfd].buf; } } } //傳送待傳送的資料 else if (fds[i].revents & POLLOUT) { int connfd = fds[i].fd; if (!users[connfd].write_buf) continue; ret = send(connfd,users[connfd].write_buf,strlen(users[connfd].write_buf),0); users[connfd].write_buf = NULL;//清除帶傳送資料 // fds[i].events |= ~POLLOUT; fds[i].events &= ~POLLOUT;//取消原POLLOUT事件 fds[i].events |= POLLIN; } } } free(users); close(listenfd); return 0; }
可以用下面的client來測試聊天室的效果:
#include <me.h> #define BUF_SIZE 100 #define NAME_SIZE 20 void *send_msg(void*); void *recv_msg(void*); char name[NAME_SIZE] = "[DEFAULT]"; char msg[BUF_SIZE]; int main(int argc,char *argv[]) { int sock; struct sockaddr_in serv_adr; pthread_t snd_thread,rcv_thread; void *thread_return; if (argc != 4) { printf("Usage : %s <IP> <port> <name>\n",argv[0]); exit(1); } sprintf(name,"[%s]",argv[3]);//將名字加入到待傳送的buf中 sock = socket(AF_INET,SOCK_STREAM,0); memset(&serv_adr,0,sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = inet_addr(argv[1]); serv_adr.sin_port = htons(atoi(argv[2])); if (connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1) error_handle("connect() error") //建立傳送和接受訊息的執行緒 pthread_create(&snd_thread,NULL,send_msg,(void*)&sock); pthread_create(&rcv_thread,NULL,recv_msg,(void*)&sock); pthread_join(snd_thread,&thread_return); pthread_join(rcv_thread,&thread_return); close(sock); return 0; } void* send_msg(void *arg) { int sock = *((int *)arg); char name_msg[NAME_SIZE + BUF_SIZE]; while(1) { fgets(msg,BUF_SIZE,stdin); if (!strcmp(msg,"q\n") || !strcmp(msg,"Q\n")) { close(sock); exit(0); } sprintf(name_msg,"%s %s",name,msg); write(sock,name_msg,strlen(name_msg)); } return NULL; } void* recv_msg(void *arg) { int sock = *((int*)arg); char name_msg[NAME_SIZE + BUF_SIZE]; int str_len; while(1)//伺服器傳送訊息到所有客戶端 { str_len = read(sock,name_msg,NAME_SIZE+BUF_SIZE-1); if (str_len == -1) return (void*)-1; name_msg[str_len] = 0; fputs(name_msg,stdout); //接受訊息,列印到客戶端 } return NULL; }
將訊號通過全雙工pipe轉化為IO事件 然後 由IO多路統一處理訊號和IO事件
利用signal的SA_RESTART flags,系統呼叫被訊號處理函式中斷後能夠重新被呼叫
這裡僅修改了4個訊號的預設處理函式,
通過kill -signo pid測試是否成功轉換為IO事件
#include "me.h"
#define MAX_EVENT_NUMBER 1024
int pipefd[2];
int setnonblocking(int fd)
{
int old_option = fcntl(fd,F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return old_option;
}
void addfd(int epollfd,int fd)
{
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;//邊沿觸發模式
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
setnonblocking(fd);//邊沿模式下,fd需要nonblock,避免觸發後一直被block
}
//訊號處理函式
void sig_handler(int signo)
{
int save_errno = errno;
int msg = signo;
send(pipefd[1],(char*)&msg,1,0);//1Byte大小足夠表示所有訊號了
errno = save_errno;
}
//註冊訊號signo的處理函式
void addsig(int signo)
{
struct sigaction sa;
memset(&sa,0,sizeof(sa));
sa.sa_handler = sig_handler;
sa.sa_flags |= SA_RESTART;//重啟被中斷的系統呼叫
sigfillset(&sa.sa_mask);
assert(sigaction(signo,&sa,NULL) != -1);
}
int main(int argc,char* argv[])
{
if (argc <= 2)
error_handle("argc <= 2")
printf("pid = %d\n",getpid());
const char *ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET,ip,&addr.sin_addr);
int listenfd = socket(AF_INET,SOCK_STREAM,0);
assert(listenfd >= 0);
int ret = bind(listenfd,(struct sockaddr*)&addr,sizeof(addr));
if (ret == -1)
{
printf("errno is %d\n",errno);
return 1;
}
ret = listen(listenfd,5);
assert(ret != -1);
struct epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
assert(epollfd != -1);
addfd(epollfd,listenfd);
//全雙工,本地,unnamed socket
ret = socketpair(AF_UNIX,SOCK_STREAM,0,pipefd);
assert(ret != -1);
setnonblocking(pipefd[1]);
addfd(epollfd,pipefd[0]);
addsig(SIGHUP);
addsig(SIGCHLD);
addsig(SIGTERM);
addsig(SIGINT);
bool stop_server = false;
while(!stop_server)
{
int number = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
//函式執行錯誤的原因不是被訊號處理程式中斷了
if ((number < 0) && (errno != EINTR))
{
printf("epoll failure\n");
break;
}
for (int i=0; i<number; i++)
{
int sockfd = events[i].data.fd;
if (sockfd == listenfd)
{
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connfd = accept(sockfd,(struct sockaddr*)&client,&client_addrlength);
addfd(epollfd,connfd);
}
//本地socket有EPOLLIN事件發生
else if((sockfd == pipefd[0]) && (events[i].events & EPOLLIN))
{
int signo;
char signals[1024];//給每個signal排隊
ret = recv(pipefd[0],signals,sizeof(signals),0);
if (ret == -1)
continue;
else if (ret == 0)
continue;
else
{
//接收到由ret個訊號處理程式發過來的signo
for (int i=0; i<ret; i++)
{
switch(signals[i])
{
case SIGCHLD:
case SIGHUP:
printf("receive SIGCHLD / SIGHUP\n");
continue;
case SIGTERM:
case SIGINT:
stop_server = true;
}
}
}
}
else{}
}
}
printf("close fds\n");
close(listenfd);
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
相關文章
- Linux下的5種I/O模型與3組I/O複用Linux模型
- 【面試】I/O 複用面試
- I/O多路複用技術(multiplexing)
- 玩轉 PHP 網路程式設計全套之 I/O 複用PHP程式設計
- [js常用]百度將文字轉化為語音例項JS
- 多路I/O複用:select、poll、epoll(二)
- 網路程式設計-I/O複用程式設計
- IO多路複用小故事
- 從網路I/O模型到Netty,先深入瞭解下I/O多路複用模型Netty
- Netty權威指南:I/O 多路複用技術Netty
- 將RAC軟體轉換為單例項軟體單例
- 一文搞懂I/O多路複用及其技術
- 詳解Go語言I/O多路複用netpoller模型Go模型
- 如何將獨立例項轉換成叢集例項EU
- 將外掛類路徑轉為型別,並建立例項。型別
- 【Linux網路程式設計】I/O 多路複用技術Linux程式設計
- Debug和幾個小例項
- Redis使用IO多路複用進行事件處理機制Redis事件
- 一篇文章幫你徹底搞清楚“I/O多路複用”和“非同步I/O”的前世今生非同步
- 談談對不同I/O模型的理解 (阻塞/非阻塞IO,同步/非同步IO)模型非同步
- LinuxI/O多路複用Linux
- 3D網頁小實驗——將txt配置文字轉化為3D陳列室3D網頁
- 如何區分例項化網格中的每個例項
- 理解IO多路複用
- CSS3旋轉效果程式碼例項CSSS3
- 計算機I/O與I/O模型計算機模型
- Java中I/O流:阻塞和非阻塞範例Java
- 將網站轉化為應用程式Unite for Mac網站Mac
- Java I/O流 複製檔案速度對比Java
- IO通訊模型(三)多路複用IO模型
- 2018 Google I/O 中最重要的十項更新Go
- Java介面為什麼不能例項化Java
- 用bonnie++測試磁碟I/O
- CSS3小黃人效果程式碼例項CSSS3
- [轉載] python複數型別-Python 複數屬性和方法操作例項Python型別
- Android Q Beta 3 亮相 Google I/O'19AndroidGo
- 如何在 Go 中將 []byte 轉換為 io.Reader?Go
- Taro:將已有微信小程式轉換為多端應用微信小程式