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;
}
相關文章
- 【面試】I/O 複用面試
- Linux下的5種I/O模型與3組I/O複用Linux模型
- js將小數轉換為整數程式碼例項JS
- 基本複製應用例項(轉)
- Epoll多路I/O複用技術
- 網路程式設計-I/O複用程式設計
- I/O多路複用技術(multiplexing)
- Epoll程式設計-I/O多路複用程式設計
- 9i RAC轉換為SINGLE例項
- js將小數轉換為整數簡單程式碼例項JS
- CSS將英文字元轉換為小寫例項程式碼CSS字元
- db file async I/O submit 等待事件優化MIT事件優化
- [js常用]百度將文字轉化為語音例項JS
- 玩轉 PHP 網路程式設計全套之 I/O 複用PHP程式設計
- 從網路I/O模型到Netty,先深入瞭解下I/O多路複用模型Netty
- 嵌入式C語言強化筆記--__I、 __O 、__IOC語言筆記
- js將物件轉換為字串程式碼例項JS物件字串
- 等待事件:Disk file operations I/O事件
- 資料庫規範化三個正規化應用例項(轉)資料庫
- 一個小應用的dbcp和c3p0配置例項
- jquery將物件序列化為字串程式碼例項jQuery物件字串
- Netty權威指南:I/O 多路複用技術Netty
- 一文搞懂I/O多路複用及其技術
- js將字串轉換為xml物件程式碼例項JS字串XML物件
- javascript將字串轉換為陣列程式碼例項JavaScript字串陣列
- js將字串轉換為數字程式碼例項JS字串
- javascript將字串轉換為整數程式碼例項JavaScript字串
- javascript將物件轉換為數字程式碼例項JavaScript物件
- 將RAC軟體轉換為單例項軟體單例
- 優化磁碟I/O優化
- 【等待事件】等待事件系列(1)--User I/O型別事件型別
- 詳解Go語言I/O多路複用netpoller模型Go模型
- 將時間戳轉換為時間例項程式碼時間戳
- js將字串轉換為編碼序列程式碼例項JS字串
- js將漢字轉換為拼音程式碼例項JS
- js將陣列元素轉換為字串程式碼例項JS陣列字串
- (轉)MySQL優化例項MySql優化
- jQuery將表單序列化為物件的程式碼例項jQuery物件