Socket-IO复用技术
系統(tǒng)運(yùn)維
(上一篇地址)前面使用socket完成一個(gè)服務(wù)器對(duì)應(yīng)多個(gè)客戶端的小實(shí)驗(yàn)的時(shí)候,針對(duì)TCP連接,我們必須得創(chuàng)建新的進(jìn)程來與新的客戶端通信。那么,就意味著,1000個(gè)客戶端就有有1000個(gè)server進(jìn)程,這顯然是不實(shí)際的。如果,我們可以提前把要監(jiān)聽的文件描述符放到一個(gè)集合里,一旦其中一個(gè)發(fā)生事件(不管是連上,還是通信),就去處理。這樣,會(huì)方便很多。所以,今天學(xué)習(xí)一下IO復(fù)用。
1 五個(gè)I/O模型
阻塞I/O
非阻塞I/O
I/O復(fù)用(select和poll)
信號(hào)驅(qū)動(dòng)I/O
異步I/O
阻塞IO
最流行的I/O模型是阻塞I/O模型,缺省時(shí),所有的套接口都是阻塞的。
非阻塞IO
IO復(fù)用
信號(hào)驅(qū)動(dòng)IO
異步IO
2 I/O復(fù)用
如果一個(gè)或多個(gè)I/O條件滿足(例如:輸入已準(zhǔn)備好被讀,或者描述字可以承接更多輸出的時(shí)候)我們就能夠被通知到,這樣的能力被稱為I/O復(fù)用,是由函數(shù)select和poll支持的。
I/O復(fù)用網(wǎng)絡(luò)應(yīng)用場合
當(dāng)客戶處理多個(gè)描述字
一個(gè)客戶同時(shí)處理多個(gè)套接口
如果一個(gè)tcp服務(wù)器既要處理監(jiān)聽套接口,又要處理連接套接口
如果一個(gè)服務(wù)器既要處理TCP,又要處理UDP
select
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);//從集合中刪除一個(gè)描述字
int FD_ISSET(int fd, fd_set *set);//描述字是否在該集合中
void FD_SET(int fd, fd_set *set);//添加一個(gè)描述字到集合中
void FD_ZERO(fd_set *set);//清空描述字集合
作用:函數(shù)允許進(jìn)程指示內(nèi)核等待多個(gè)事件中的任一個(gè)發(fā)生,并僅在一個(gè)或多個(gè)事件發(fā)生或經(jīng)過某指定的時(shí)間后才喚醒進(jìn)程
提供了即時(shí)響應(yīng)多個(gè)套接的讀寫事件
參數(shù):
nfds:集合中最大的文件描述符 + 1 (指定被測試的描述字個(gè)數(shù),它的值是要被測試的最大描述字加1,描述字0、1、2…….一直到nfds均被測試)
readfds:要檢查讀事件的容器
writefds:要檢查寫事件的容器
timeout:超時(shí)時(shí)間
返回值:返回觸發(fā)套接字的個(gè)數(shù)
中間的三個(gè)參數(shù)readset、writeset和exceptset指定我們要讓內(nèi)核測試讀、寫和異常條件所需的描述字
如果我們對(duì)某個(gè)條件不感興趣,這三個(gè)參數(shù)中相應(yīng)的參數(shù)就可以設(shè)為空指針
timeout參數(shù)
時(shí)間的結(jié)構(gòu)體如下:
struct timeval(
long tv_sec; //秒
long tv_usec;//微秒
);
timeout參數(shù)有三種可能
永遠(yuǎn)等待下去:僅在有一個(gè)描述字準(zhǔn)備好I/O時(shí)才返回,為此,我們將timeout設(shè)置為空指針
等待固定時(shí)間:在有一個(gè)描述字準(zhǔn)備好I/O是返回,但不超過由timeout參數(shù)所指timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)
根本不等待:檢查描述字后立即返回,這稱為輪詢。定時(shí)器的值必須為0
fd_set參數(shù)
select使用描述字集,它一般是一個(gè)整數(shù)數(shù)組,每個(gè)數(shù)中的每一位對(duì)應(yīng)一個(gè)描述字。
使用流程
使用select完成之前socket的測試,流程如下:
客戶端代碼不變。
#include < sys/types.h>
#include < sys/socket.h>
#include < netinet/in.h> //sockaddr_in
#include < stdio.h>
#include < string.h>
//TCP
int main()
{
int fd;
int ret;
int addrLen;
char acbuf[20] = ;
struct sockaddr_in serAddr = {0};
struct sockaddr_in myAddr = {0};
//1.socket();
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//2.連接connect() 服務(wù)器的地址
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(1234);
serAddr.sin_addr.s_addr = inet_addr(192.168.159.5);
ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(connect);
return -1;
}
//獲取自己的地址
addrLen = sizeof(struct sockaddr_in);
ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen);
if(ret == -1)
{
perror(getsockname);
return -1;
}
printf(client---ip: %s , port: %d\\n,\\
inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));
//3.通信
while(1)
{
printf(send: );
fflush(stdout);
scanf(%s,acbuf);
if(strcmp(acbuf,exit) == 0)
{
break;
}
write(fd,acbuf,strlen(acbuf));
}
//4.close()
close(fd);
return 0;
}
服務(wù)器端:
select.c
#include < sys/types.h>
#include < sys/socket.h>
#include < netinet/in.h> //sockaddr_in
#include < stdio.h>
#include < string.h>
#include < signal.h>
#include < sys/select.h>
#include < unistd.h>
#include < sys/time.h>
//TCP
int main()
{
int fd;
int clientfd;
int ret;
pid_t pid;
int i;
int maxfd; //當(dāng)前最大套接字
int nEvent;
fd_set set = {0}; //監(jiān)聽集合
fd_set oldset = {0}; //存放所有要監(jiān)聽的文件描述符
struct timeval time = {0};
int reuse = 0;
char acbuf[20] = ;
char client_addr[100] = ;
struct sockaddr_in addr = {0}; //自己的地址
struct sockaddr_in clientAddr = {0}; //連上的客戶端的地址
int addrLen = sizeof(struct sockaddr_in);
signal(SIGCHLD,SIG_IGN);
//1.socket()
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//會(huì)出現(xiàn)沒有活動(dòng)的套接字仍然存在,會(huì)禁止綁定端口,出現(xiàn)錯(cuò)誤:address already in use .
//由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,該狀態(tài)會(huì)保留2-4分鐘
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
perror(setsockopet error\\n);
return -1;
}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = inet_addr(192.168.159.5);
ret = bind(fd,(struct sockaddr *)&addr,addrLen);
if(ret == -1)
{
perror(bind);
return -1;
}
//3.listen()
ret = listen(fd,10);
if(ret == -1)
{
perror(listen);
return -1;
}
//創(chuàng)建監(jiān)聽集合
FD_ZERO(&oldset);
FD_SET(fd,&oldset);
//maxfdp1:當(dāng)前等待的最大套接字。比如:當(dāng)前fd的值=3,則最大的套接字就是3
//所以每當(dāng)有客戶端連接進(jìn)來,就比較一下文件描述符
maxfd = fd;
//select
//select之前,set放的是所有要監(jiān)聽的文件描述符;{3,4,5}
//select之后,set只剩下有發(fā)生事件的文件描述符。{3}
while(1)
{
set = oldset;
printf(before accept.\\n);
time.tv_sec = 5;
nEvent = select(maxfd + 1,&set,NULL,NULL,&time); //返回文件描述符的個(gè)數(shù)(即事件的個(gè)數(shù))
printf(after accept.%d\\n,nEvent);
if(nEvent == -1)
{
perror(select);
return -1;
}
else if(nEvent == 0) //超時(shí)
{
printf(time out);
return 1;
}
else
{
//有事件發(fā)生
//判斷是否是客戶端產(chǎn)生的事件
for(i = 0 ; i <= maxfd ; i++)
{
if(FD_ISSET(i,&set))
{
if(i == fd)
{
clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen);
FD_SET(clientfd,&oldset);
printf(client ip:%s ,port:%u\\n,inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
if(clientfd > maxfd)
{
maxfd = clientfd;
}
}
else
{
memset(acbuf,0,20);
if(read(i,acbuf,20) == 0) //客戶端退出
{
close(i);
//還要從集合里刪除
FD_CLR(i,&oldset);
}
else
printf(receive: %s\\n,acbuf);
}
}
}
}
}
return 0;
}
epoll
epoll用到的函數(shù)有以下幾個(gè):
#include <sys/epoll.h>
int epoll_create(int size);//創(chuàng)建epoll
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//操作函數(shù)
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
事件集合的結(jié)構(gòu)體:
(這里 ,還要注意,epoll的超時(shí)參數(shù)是int,單位是us)
使用流程
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> //sockaddr_in
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/epoll.h>
//epoll
//epoll_wait() epoll_creat() epoll_ctl()
//TCP
int main()
{
int fd;
int clientfd;
int ret;
pid_t pid;
int i;
int epfd;
int nEvent;
struct epoll_event event = {0};
struct epoll_event rtl_events[20] = {0}; //事件結(jié)果集
int reuse = 0;
char acbuf[20] = ;
char client_addr[100] = ;
struct sockaddr_in addr = {0}; //自己的地址
struct sockaddr_in clientAddr = {0}; //連上的客戶端的地址
int addrLen = sizeof(struct sockaddr_in);
signal(SIGCHLD,SIG_IGN);
//1.socket()
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//會(huì)出現(xiàn)沒有活動(dòng)的套接字仍然存在,會(huì)禁止綁定端口,出現(xiàn)錯(cuò)誤:address already in use .
//由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,該狀態(tài)會(huì)保留2-4分鐘
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
perror(setsockopet error\\n);
return -1;
}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = inet_addr(192.168.159.5);
ret = bind(fd,(struct sockaddr *)&addr,addrLen);
if(ret == -1)
{
perror(bind);
return -1;
}
//3.listen()
ret = listen(fd,10);
if(ret == -1)
{
perror(listen);
return -1;
}
epfd = epoll_create(1000); //同時(shí)監(jiān)聽的文件描述符
event.data.fd = fd;
event.events = EPOLLIN; //讀
epoll_ctl(epfd,EPOLL_CTL_ADD,fd, &event);
while(1)
{
// nEvent = epoll_wait(epfd,rtl_events,20,-1); //-1:阻塞 0:非阻塞
nEvent = epoll_wait(epfd,rtl_events,20,5000);
if(nEvent == -1)
{
perror(epoll_wait);
return -1;
}
else if(nEvent == 0)
{
printf(time out.);
}
else
{
//有事件發(fā)生,立即處理
for(i = 0; i < nEvent;i++)
{
//如果是 服務(wù)器fd
if( rtl_events[i].data.fd == fd )
{
clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen);
//添加
event.data.fd = clientfd;
event.events = EPOLLIN; //讀
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event);
printf(client ip:%s ,port:%u\\n,inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
}
else
{
//否則 客戶端fd
memset(acbuf,0,20);
ret = read(rtl_events[i].data.fd,acbuf,20);
printf(%d\\n,ret);
if( ret == 0) //客戶端退出
{
close(rtl_events[i].data.fd);
//從集合里刪除
epoll_ctl(epfd,EPOLL_CTL_DEL,rtl_events[i].data.fd,NULL);
}
else
printf(receive: %s\\n,acbuf);
}
}
}
}
return 0;
}
運(yùn)行結(jié)果如前,正常收發(fā)。
總結(jié)
以上是生活随笔為你收集整理的Socket-IO复用技术的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Django笔记-5-视图
- 下一篇: FastComet主机11个数据机房测试