1. 基本概念
IO 多路复用是指一旦发现进程指定的一个或者多个IO条件准备读,它就通知该进程;IO多路复用适合于如下场合:
* 处理多个文件描述符
* 既有TCP监听套接字,又有已连接套接字
* 既有UDP,又有TCP
* 同时处理多个协议
与多进程和多线程技术相比,IO多用复用技术的最大优势是系统开销小,系统不必对每个连接的创建进程或者线程处理。
2. select函数
此函数准许进程 指示内核等待多个事件的发生;只有等到一个或者多个事件的发生或经历指定的超时时间后才被唤醒并返回该时间。
函数原型
#include<sys/select.h>
#include<sys/time.h>
int select(int maxfd,fd_set *readfds,fd_set *writefds,fd_set *excepfds, const struct timeval *timeout)
- 函数介绍
a) maxfd表示指定待检测的文件描述符的个数,值为待检测最大描述值加1 ;
b) readfds,writefds,excepfds表示需要内核检测的读,写,异常条件的描述符集合。用户不关心的集合可以设置为NULL; struct fd_set需要4个宏进行操作:
void FD_ZERO(struct fd_set *fdset) ; //清空集合
void FD_SET(int fd,struct fd_set *fdset) ; //将文件描述符添加到集合中
void FD_ISSET(int fd, struct fd_set *fdset) ;//判断描述符是否存在于集合中
void FD_CLR(int fd,struct fd_set *fdset) ; //将指定文件 描述符从集合中去除;
c) timeout 表示超时,struct timeval结构体用于指定超时的时间;有三种可能:
* timeout==NULL;即表示无限等待,直到有事件被触发;
* timeout时间大于0;即表示等待一段固定时间则返回;
* timeout时间秒数和微秒数都等于0;表示立即返回(轮询);
3. 服务端程序
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<sys/time.h>
#include<sys/select.h>
#include<unistd.h>
#include<errno.h>
#define SIZE 10
typedef struct serv_context{
int cli_cnt ; //客户端个数
int clifds[SIZE] ; //客户端
fd_set allfds ;
int maxfd ; //句柄最大数
}serv_context_st ;
static serv_context_st *s_src_ctx =NULL;
/*
functionName : createsock
description: create socket and change the socket attribute; bind the address
Input : port, ip
Output : success : fd, error : -1
*/
int createSock(int port,char *ip)
{
int sock =-1;
int buffsize = 1024 ;
int optive = 1 ;
struct sockaddr_in servAddr ;
sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP) ;
if(sock<0)
{
printf("[%s][%d]error create socket",__FILE__,__LINE__) ;
return -1 ;
}
int ret = setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void*)&optive,sizeof(optive)) ;
if(ret <0)
{
printf("[%s][%d]setsocket opt SO_REUSERADDR is error\n",__FILE__,__LINE__) ;
return -1 ;
}
ret = setsockopt(sock,SOL_SOCKET,SO_RCVBUF,&buffsize,sizeof(buffsize)) ;
if(ret<0)
{
printf("[%s][%d]setsockopt is error SO_RECVBUF\n",__FILE__,__LINE__) ;
return -1 ;
}
ret = setsockopt(sock,SOL_SOCKET,SO_SNDBUF,&buffsize,sizeof(buffsize)) ;
if(ret<0)
{
printf("[%s][%d] setsockopt is error SO_SNDBUF\n",__FILE__,__LINE__);
return -1 ;
}
bzero(&servAddr,sizeof(servAddr)) ;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(port) ; //网络字节序
servAddr.sin_addr.s_addr= inet_addr(ip) ;
ret = bind(sock,(struct sockaddr *)&servAddr,sizeof(servAddr)) ;
if(ret<0)
{
printf("[%s][%d]bind is error",__FILE__,__LINE__) ;
close(sock) ;
return -1 ;
}
return sock ;
}
int uninit()
{
if(s_src_ctx)
{
free(s_src_ctx) ;
s_src_ctx = NULL;
}
return 0 ;
}
int init()
{
int i = 0 ;
s_src_ctx = (serv_context_st*)malloc(sizeof(serv_context_st));
if(NULL == s_src_ctx){
fprintf(stderr,"malloc the context is error") ;
return -1 ;
}
memset(s_src_ctx,0,sizeof(serv_context_st)) ;
for(i=0;i<SIZE;i++)
{
s_src_ctx->clifds[i]= -1 ;
}
return 0 ;
}
int accept_client_proc(int servfd)
{
int clifd = -1 ;
struct sockaddr_in cliAddr ;
socklen_t cliAddrLen ;
cliAddrLen = sizeof(cliAddr) ;
memset(&cliAddr,0,sizeof(struct sockaddr_in));
printf("accept client proc is called!\n") ;
ACCEPT:
clifd = accept(servfd,(struct sockaddr*)&cliAddr,&cliAddrLen) ;
if(-1 == clifd)
{
if(EINTR==errno)
{
goto ACCEPT ;
}
else
{
fprintf(stderr,"accept fail error:%s",strerror(errno));
return -1 ;
}
}
fprintf(stdout,"accept a new client:%s:%d\n",inet_ntoa(cliAddr.sin_addr),ntohs(cliAddr.sin_port)) ;
int i = 0;
for (i = 0 ;i<SIZE;i++)
{
if(s_src_ctx->clifds[i] == -1)
{
s_src_ctx->clifds[i] = clifd ;
s_src_ctx->cli_cnt++ ;
break ;
}
}
if (i == SIZE)
{
fprintf(stderr,"too many clients.\n") ;
return -1 ;
}
return 0 ;
}
int handleCliMsg(int clifd,char *recvBuf,int dataLen)
{
struct sockaddr_in cliAddr ;
socklen_t cliAddrLen ;
char sendBuf[4096] ;
getpeername(clifd,(struct sockaddr*)&cliAddr,&cliAddrLen) ;
fprintf(stdout,"from %s:%d recv buf :%s\n",inet_ntoa(cliAddr.sin_addr),
ntohs(cliAddr.sin_port),recvBuf) ;
snprintf(sendBuf+4,4096,"i have recv:%s",recvBuf);
*(unsigned short*)sendBuf = htons(0) ;
*(unsigned short*)(sendBuf+2) = htons(strlen(sendBuf+4)) ;
int left = strlen(sendBuf+4) + 4;
int pos = 0 ;
while(left >0)
{
int writeDataLen = write(clifd,sendBuf+pos,left) ;
if (writeDataLen <0)
{
if((errno==EINTR) || (errno == EWOULDBLOCK) || (errno==EAGAIN))
{
fd_set writeFds ;
FD_ZERO(&writeFds) ;
FD_SET(clifd,&writeFds) ;
struct timeval tv ;
tv.tv_sec = 10 ;
tv.tv_usec = 0 ;
int ret = select(clifd+1,NULL,&writeFds,NULL,&tv) ;
if (ret > 0 ) //success
{
continue ;
}
else if(ret == 0) //time out
{
close(clifd) ;
return -1 ;
}
else // error
{
close(clifd) ;
return -1 ;
}
}
else
{
close(clifd) ;
return -1 ;
}
}
else if (writeDataLen == 0)
{
close(clifd) ;
return -1 ;
}
else
{
left -= writeDataLen ;
pos += writeDataLen ;
}
}
return 0 ;
}
int recv_client_msg(fd_set* readFd)
{
int i = 0 ;
int clifd = -1 ;
int dataLen = 0 ;
char recvBuf[4096] ; //recv buffer
char *ptr = recvBuf ;
int leftLen = 4096 ;
memset(recvBuf,0,4096);
for(i = 0 ;i<SIZE ;i++)
{
clifd = s_src_ctx->clifds[i] ;
if(clifd <0)
{
continue ;
}
if(FD_ISSET(clifd,readFd))
{
dataLen = read(clifd,ptr,leftLen) ;
if(dataLen<0) // some error
{
if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
{
continue ;
}
else
{
FD_CLR(clifd,readFd) ;
close(clifd) ;
s_src_ctx->clifds[i] = -1 ;
return 1 ;
}
}
else if(dataLen==0)// end of EOF // the client closed
{
printf("the client sock is close\n") ;
close(clifd) ;
s_src_ctx->clifds[i]=-1 ;
break ;
}
printf("recv the data len is :%d\n",dataLen) ;
handleCliMsg(clifd,recvBuf,dataLen) ; //you can deal it asny
return 0 ; // do it success
}
}
if(SIZE==i)
{
fprintf(stderr,"some wrong with the session") ;
return 1 ;
}
return 1 ;
}
int closeAll()
{
int i = 0 ;
int clifd = -1 ;
for (i = 0 ;i<SIZE ;i++)
{
clifd = s_src_ctx->clifds[i] ;
close(clifd) ;
s_src_ctx->clifds[i] = -1 ;
}
return 0 ;
}
int handle_client_proc(int servfd)
{
fd_set *readFd = &s_src_ctx->allfds;
int clifd = -1 ;
int i = 0 ;
int retval = -1 ;
//set the time out
struct timeval timeout ;
while(1){
FD_ZERO(readFd) ;
FD_SET(servfd,readFd) ;//add the listen fd
s_src_ctx->maxfd = servfd ;
for(i = 0 ;i<s_src_ctx->cli_cnt ;i++)
{
clifd = s_src_ctx->clifds[i] ;
if (clifd != -1)
{
FD_SET(clifd,readFd) ;
}
// find the max fd
s_src_ctx->maxfd = (clifd > s_src_ctx->maxfd? clifd:s_src_ctx->maxfd) ;
}
timeout.tv_sec = 10;//秒
timeout.tv_usec = 0;//微秒
retval = select(s_src_ctx->maxfd+1,readFd,NULL,NULL,&timeout) ;
if(retval<0)
{
if(errno==EAGAIN || errno== EINTR|| errno==EWOULDBLOCK)
{
continue ;
}
else // some else error
{
fprintf(stderr,"select is error:%d\n",strerror(errno)) ;
close(servfd) ;
closeAll() ;
return ;
}
}
else if(retval==0) // time out
{
fprintf(stdout,"select is timeout:%d\n",strerror(errno)) ;
continue ;
}
else
{
printf("some one is recv\n") ;
switch(FD_ISSET(servfd,readFd))
{
case 0: //handle the server message
recv_client_msg(readFd) ;
break ;
default://handle the client massage
accept_client_proc(servfd);
}
}
}
return 0 ;
}
int main()
{
int port = 44444;
char *ip = "127.0.0.1";
int listenFd = -1 ;
init(s_src_ctx) ;
listenFd = createSock(port,ip) ;
listen(listenFd,5) ;
handle_client_proc(listenFd) ;
close(listenFd);
uninit() ;
return 0 ;
}
4. 客户端程序
#include<stdio.h>
#include<netinet/in.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#define PORT 44444
#define IP "127.0.0.1"
#define PACKETMAX 512
#define PACKETMIN 4
typedef struct buffer{
char buf[1024] ;
int startPos ;
int endPos ;
int max ;
}Buffer_s;
typedef struct Conn
{
int fd ;
Buffer_s *buffer ;
int lConnState ;
int revents ;
}CONN;
int createServSock()
{
int servSock = -1 ;
servSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP) ;
if( servSock < -1)
{
printf("create sock is error:%s",strerror(errno)) ;
exit(-1) ;
}
return servSock ;
}
int tcpRecv(CONN *conn )
{
if (NULL == conn)
{
printf("error conn is NULL\n") ;
return -1 ;
}
int nread = recv(conn->fd,conn->buffer->buf+conn->buffer->endPos,1024-conn->buffer->endPos,0) ;
if (nread <0)
{
int status = errno ;
if ((status==EINTR) || (status==EWOULDBLOCK) ||(status == EAGAIN))
{
printf("%d socket is error:%s\n",conn->fd,strerror(status)) ;
return 0 ;
}
/* other errors*/
close(conn->fd) ;
conn->fd = -1 ;
return -1 ;
}
else if(nread ==0)
{
printf("%d socket is eof\n",conn->fd) ;
close(conn->fd) ;
conn->fd = -1 ;
return -1 ;
}
else
{
conn->buffer->endPos += nread ;
conn->buffer->buf[conn->buffer->endPos] = '\0' ;
}
return 0 ;
}
int dealMessage(CONN *pConn ,int liv_MessageLen )
{
/* deal message
*
*
deal end
*/
printf("dealMessage,headLen:%d,bodyLen:%d bodyMessage:%s\n",htons(*(unsigned short*)(pConn->buffer->buf)),htons(*(unsigned short*)(pConn->buffer->buf+2)),pConn->buffer->buf+4) ;
/* update the buffer pointer*/
pConn->buffer->startPos += liv_MessageLen + PACKETMIN ;
printf("startPos:%p,endPos:%p\n",pConn->buffer->buf+pConn->buffer->startPos,pConn->buffer->buf+pConn->buffer->endPos) ;
return 0 ;
}
int checkBuf(Buffer_s *buffer)
{
if (NULL == buffer)
{
printf("The buffer is error\n") ;
return 0 ;
}
if(buffer->startPos == buffer->endPos)
{
buffer->startPos = buffer->endPos = 0 ;
return 0 ;
}
if ( buffer->startPos != 0 )
{
memmove(buffer->buf,buffer->buf + buffer->startPos, buffer->endPos - buffer->startPos) ;
buffer->endPos = buffer->endPos - buffer->startPos ;
buffer->startPos = 0 ;
}
if ((buffer->endPos - buffer->startPos) > 0 )
{
return 1 ;
}
return 0 ;
}
int recvMessage(CONN *pConn)
{
/* recv data from conn*/
if ( -1 == tcpRecv(pConn))
{
return -1 ;
}
int liv_DataLen = 0 ;
DealMessageAgain:
printf("error\n") ;
liv_DataLen = pConn->buffer->endPos - pConn->buffer->startPos ;
if (liv_DataLen < PACKETMIN)
{
printf("message header not recieve completed,continue to recv\n") ;
return -1 ;
}
int liv_HeadLen = ntohs(*(unsigned short*)(pConn->buffer->buf + pConn->buffer->startPos)) ;
int liv_BodyLen = ntohs(*(unsigned short*)(pConn->buffer->buf + pConn->buffer->startPos +2)) ;
int liv_MessageLen = liv_HeadLen + liv_BodyLen ;
if ( (liv_MessageLen < 0) || (liv_MessageLen > PACKETMAX)) /* maybe the data is dirty*/
{
printf("maybe the data is dirty,socket:%d\n",pConn->fd) ;
close(pConn->fd) ;
pConn->fd = -1 ;
return -1 ;
}
if (liv_DataLen < liv_MessageLen) /* may be the package is not recv compeleted*/
{
printf("the message is not compelete, %d need to recv!\n",liv_MessageLen - liv_DataLen) ;
return -1 ;
}
int ret = dealMessage(pConn,liv_MessageLen) ; /*if parse data is error, we will close the conn.*/
if (ret < 0)
{
close(pConn->fd) ;
pConn->fd = -1 ;
return -1 ;
}
printf("%d startPos:%p,endPos:%p\n",__LINE__,pConn->buffer->buf+pConn->buffer->startPos,pConn->buffer->buf+pConn->buffer->endPos) ;
if (checkBuf(pConn->buffer))
{
goto DealMessageAgain ;
}
return 0 ;
}
int sendMessage(int fd,Buffer_s *buf) //Writen
{
int nWrited = 0 ;
fd_set writeFd ;
int left = buf->endPos - buf->startPos;
char *ptr = buf->buf+buf->startPos ;
while(left >0)
{
nWrited = send(fd,ptr,left,0) ;
if(nWrited <0)
{
if(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
{
printf("recv the error:%s",strerror(errno)) ;
FD_ZERO(&writeFd) ;
FD_SET(fd,&writeFd) ;
int ret = select(fd+1,NULL,&writeFd,NULL,NULL) ;
if (ret < 0 ) /* some is error */
{
printf("tcp connect is error\n") ;
close(fd) ;
fd = -1 ;
return -1 ;
}
else if (ret ==0 ) /* time out */
{
printf("select is time out\n") ;
return -1 ;
}
else /* fd can write again */
{
continue ;
} // end if ret
}
else
{
printf("recv the error:%s",strerror(errno)) ;
close(fd) ;
exit(-1) ;
}
}
else if(nWrited == 0 )
{
close(fd) ;
printf("the socket is closed!") ;
return 0 ;
}
else
{
printf("Write %d byte data\n",nWrited) ;
left -= nWrited ;
ptr += nWrited ;
}
}
buf->startPos = buf->endPos = 0 ;
printf("data is writed\n");
return 0 ;
}
int main()
{
int ret = -1 ;
CONN conn ;
conn.buffer = (Buffer_s *)malloc(sizeof(Buffer_s)) ;
memcpy(conn.buffer->buf, "luojian is a smaller",strlen("luojian is a smaller")) ;
conn.buffer->endPos= strlen(conn.buffer->buf) ;
conn.buffer->max = 1024 ;
conn.buffer->startPos = 0 ;
int servSock = -1 ;
struct sockaddr_in servAddr ;
struct sockaddr_in localAddr;
memset(&servAddr,0,sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(PORT) ;
servAddr.sin_addr.s_addr = inet_addr(IP) ;
conn.fd = createServSock() ;
again:
ret = connect(conn.fd,(struct sockaddr *)&servAddr,sizeof(servAddr)) ;
if (ret < 0 )
{
if(errno == EINTR || errno == EAGAIN)
{
goto again;
}
else
{
printf("some error:%s",strerror(errno)) ;
exit(-1) ;
}
}
memset(&localAddr,0,sizeof(localAddr)) ;
socklen_t len = sizeof(localAddr) ;
ret = getsockname(conn.fd,(struct sockaddr*)&localAddr,&len) ;
if(ret <0)
{
printf("connect to serv is ok") ;
}
else
{
printf("connect to serv is ok,and local addr is:%s",inet_ntoa(localAddr.sin_addr)) ;
}
sendMessage(conn.fd,conn.buffer);
recvMessage(&conn) ;
close(conn.fd) ;
return 0 ;
}