【原创】《Linux高级程序设计》杨宗德著 - TCP高级应用 - 多路复用
文件I/O方式比较
1. 阻塞式文件IO
2. 非阻塞式文件IO
3. 多路复用IO
4. 信号驱动IO(也叫驱动异步IO)
IO阻塞与非阻塞操作
阻塞:如果没有数据可操作,该函数调用将阻塞,导致对应进程暂停执行,当有数据继续执行并返回。
默认read/write函数,以及recv/send函数采用阻塞方式
非阻塞:需要进程立即返回,则需要设置为非阻塞方式,即如果没有数据可接收就立即返回-1表示接收失败,并修改系统全局变量errno。
socket读写,以非阻塞方式调用recv()函数返回时,没有数据可读,将修改errno变量的值为“EAGAIN”,表示recv读数据时,对方没有发送数据过来。
非阻塞应用示例
发送端代码
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXBUF 128
int main(int argc, char **argv)
{
int sockfd, ret, i;
struct sockaddr_in dest, mine;
char buffer[MAXBUF + 1];
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Socket");
exit(EXIT_FAILURE);
}
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(7838);
if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0)
{
perror(argv[1]);
exit(EXIT_FAILURE);
}
bzero(&mine, sizeof(mine));
mine.sin_family = AF_INET;
mine.sin_port = htons(7839);
if (inet_aton(argv[2], (struct in_addr *) &mine.sin_addr.s_addr) == 0)
{
perror(argv[2]);
exit(EXIT_FAILURE);
}
if (bind(sockfd, (struct sockaddr *) &mine, sizeof(struct sockaddr)) == -1)
{
perror(argv[3]);
exit(EXIT_FAILURE);
}
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)
{
perror("Connect ");
exit(EXIT_FAILURE);
}
if(fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1)
{
perror("fcntl");
exit(EXIT_FAILURE);
}
while(1)
{
bzero(buffer, MAXBUF + 1);
ret = recv(sockfd, buffer, MAXBUF, 0);
if(ret > 0)
{
printf("get %d message:%s", ret, buffer);
ret=0;
}
else if(ret < 0)
{
if(errno == EAGAIN)
{
errno=0;
continue;
}
else
{
perror("recv");
exit(EXIT_FAILURE);
}
}
memset( buffer,'\0',MAXBUF+1);
printf("input message to send:");
fgets( buffer,MAXBUF,stdin);
if((ret=send(sockfd,buffer,strlen(buffer),0))==-1)
{
perror("send");
exit(EXIT_FAILURE);
}
}
close(sockfd);
return 0;
}
接收端代码
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#define BUFSIZE 128
int main(int argc,char *argv[])
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int i,byte;
char char_send[BUFSIZE];
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
if (inet_aton(argv[1], (struct in_addr *) & server_address.sin_addr.s_addr) == 0)
{
perror(argv[1]);
exit(EXIT_FAILURE);
}
server_address.sin_port = htons(7838);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, 5);
printf("server waiting for connect\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address, (socklen_t *)&client_len);
for(i=0;i<5;i++)
{
memset(char_send,'\0',BUFSIZE);
printf("input message to send:");
fgets(char_send,BUFSIZE,stdin);
if((byte=send(client_sockfd,char_send,strlen(char_send),0))==-1)
{
perror("send");
exit(EXIT_FAILURE);
}
memset(char_send,'\0',BUFSIZE);
byte = recv(client_sockfd, char_send, BUFSIZE,MSG_DONTWAIT);
if(byte > 0)
{
printf("get %d message:%s", byte, char_send);
byte=0;
}
else if(byte<0)
{
if(errno==EAGAIN)
{
errno=0;
continue;
}
else
{
perror("recv");
exit(EXIT_FAILURE);
}
}
}
shutdown(client_sockfd,2);
shutdown(server_sockfd,2);
}
发送端运行结果
$ ./tcp_unblock_server 172.18.229.62
server waiting for connect
input message to send:hello
input message to send:test
get 3 message:hi
input message to send:go
input message to send:read
input message to send:why
接收端运行结果
$ ./tcp_unblock_client 172.18.229.62 172.18.229.62
get 6 message:hello
input message to send:hi
get 5 message:test
input message to send:go
get 12 message:go
read
why
input message to send:hi
socket多路复用应用
select函数
select提供轮循等待的方式从多个文件描述符中获取状态发变后的消息 。
第1个参数指定要检测的文件描述符的范围。测试范围在0到nfds−1之间的文件描述符(nfds=这些文件描述符中的最大值+1)。
第2、3、4三个参数类型都是fd_set *。这三个参数都是文件描述符的集合。
dfds包含所有因状态变成可读而触发select()函数返回的文件描述符;
wtfds包含所有因状态变成可写而触发select()函数返回的文件描述符;
exfds包含所有因状态发生特殊异常(例如带外数据到来)而触发select()函数返回的文件描述符
文件描述符集合操作
最后一个参数timeout显然是一个超时时限,其类型是struct timeval * 。
如果函数执行错误,将返回-1;
如果因超时而返回,即在timeout所描述的时间范围内没有任何描述符出现异常,则返回0;
如果因一个或多个文件描述符异常而返回,其返回值为产生异常的文件描述符数,并在相应文件描述符集合中清除未产生异常的文件描述符记录,因此,返回后可以根据文件描述符集合的记录查找出是哪一个文件描述符返回。
基本示例
检测某个socket是否有数据可读
pselect函数
poll与ppoll函数
poll函数
poll提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息。
#include <poll.h>
int poll(struct pollfd *fd, nfds_t nfds, int timeout);
参数:
1)第一个参数:一个结构数组,struct pollfd结构如下:
struct pollfd{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
events和revents是通过对代表各种事件的标志进行逻辑或运算构建而成的。events包括要监视的事件,poll用已经发生的事件填充revents。poll函数通过在revents中设置标志肌肤POLLHUP、POLLERR和POLLNVAL来反映相关条件的存在。不需要在events中对于这些标志符相关的比特位进行设置。如果fd小于0, 则events字段被忽略,而revents被置为0.标准中没有说明如何处理文件结束。文件结束可以通过revents的标识符POLLHUN或返回0字节的常规读操作来传达。即使POLLIN或POLLRDNORM指出还有数据要读,POLLHUP也可能会被设置。因此,应该在错误检验之前处理正常的读操作。
poll函数的事件标志符值
常量 说明
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
注意:后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。
2)第二个参数nfds:要监视的描述符的数目。
3)最后一个参数timeout:是一个用毫秒表示的时间,是指定poll在返回前没有接收事件时应该等待的时间。如果 它的值为-1,poll就永远都不会超时。如果整数值为32个比特,那么最大的超时周期大约是30分钟。
timeout值 说明
INFTIM 永远等待
0 立即返回,不阻塞进程
>0 等待指定数目的毫秒数
ppoll函数
#include <poll.h>
int ppoll(struct pollfd *fd, nfds_t nfds, const struct timespec *timeout_ts, const sigset_t *sigmask);
与pselect一样,ppoll函数可以在阻塞过程中屏蔽某些信号,而在timeout时间上,ppoll的时间精度更高。
多路复用应用示例
服务器代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, new_fd;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
char buf[MAXBUF + 1];
fd_set rfds;
struct timeval tv;
int retval, maxfd = -1;
if (argv[2])
myport = atoi(argv[2]);
else
myport = 7838;
if (argv[3])
lisnum = atoi(argv[3]);
else
lisnum = 2;
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
if (argv[1])
my_addr.sin_addr.s_addr = inet_addr(argv[1]);
else
my_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(sockfd, lisnum) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
while (1)
{
printf ("\n----wait for new connect\n");
len = sizeof(struct sockaddr);
if ((new_fd =accept(sockfd, (struct sockaddr *) &their_addr,&len)) == -1) {
perror("accept");
exit(errno);
} else
printf("server: got connection from %s, port %d, socket %d\n",
inet_ntoa(their_addr.sin_addr),ntohs(their_addr.sin_port), new_fd);
while (1)
{
FD_ZERO(&rfds);
FD_SET(0, &rfds);
FD_SET(new_fd, &rfds);
maxfd = new_fd;
tv.tv_sec = 1;
tv.tv_usec = 0;
retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
if (retval == -1)
{
perror("select");
exit(EXIT_FAILURE);
} else if (retval == 0) {
continue;
}
else
{
if (FD_ISSET(0, &rfds))
{
bzero(buf, MAXBUF + 1);
fgets(buf, MAXBUF, stdin);
if (!strncasecmp(buf, "quit", 4)) {
printf("i will quit!\n");
break;
}
len = send(new_fd, buf, strlen(buf) - 1, 0);
if (len > 0)
printf ("send successful,%d byte send!\n",len);
else {
printf("send failure!");
break;
}
}
if (FD_ISSET(new_fd, &rfds))
{
bzero(buf, MAXBUF + 1);
len = recv(new_fd, buf, MAXBUF, 0);
if (len > 0)
printf ("recv success :'%s',%dbyte recv\n", buf, len);
else
{
if (len < 0)
printf("recv failure\n");
else
{
printf("the ohter one end ,quit\n");
break;
}
}
}
}
}
close(new_fd);
printf("need othe connecdt (no->quit)");
fflush(stdout);
bzero(buf, MAXBUF + 1);
fgets(buf, MAXBUF, stdin);
if (!strncasecmp(buf, "no", 2))
{
printf("quit!\n");
break;
}
}
close(sockfd);
return 0;
}
客户端代码
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + 1];
fd_set rfds;
struct timeval tv;
int retval, maxfd = -1;
if (argc != 3)
{
printf("argv format errno,pls:\n\t\t%s IP port\n",argv[0], argv[0]);
exit(EXIT_FAILURE);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Socket");
exit(EXIT_FAILURE);
}
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[2]));
if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0)
{
perror(argv[1]);
exit(EXIT_FAILURE);
}
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)
{
perror("Connect ");
exit(EXIT_FAILURE);
}
printf("\nget ready pls chat\n");
while (1)
{
FD_ZERO(&rfds);
FD_SET(0, &rfds);
FD_SET(sockfd, &rfds);
maxfd = sockfd;
tv.tv_sec = 1;
tv.tv_usec = 0;
retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
if (retval == -1)
{
printf("select %s", strerror(errno));
break;
}
else if (retval == 0)
continue;
else
{
if (FD_ISSET(sockfd, &rfds))
{
bzero(buffer, MAXBUF + 1);
len = recv(sockfd, buffer, MAXBUF, 0);
if (len > 0)
printf ("recv message:'%s',%d byte recv\n",buffer, len);
else
{
if (len < 0)
printf ("message recv failure\n");
else
{
printf("the othe quit ,quit\n");
break;
}
}
}
if (FD_ISSET(0, &rfds))
{
bzero(buffer, MAXBUF + 1);
fgets(buffer, MAXBUF, stdin);
if (!strncasecmp(buffer, "quit", 4)) {
printf("i will quit\n");
break;
}
len = send(sockfd, buffer, strlen(buffer) - 1, 0);
if (len < 0) {
printf ("message send failure");
break;
} else
printf
("send success,%d byte send\n",len);
}
}
}
close(sockfd);
return 0;
}
服务器运行结果
$ ./tcp_sy_chat_server 172.18.229.62 8000
----wait for new connect
server: got connection from 172.18.229.62, port 55410, socket 4
recv success :'hello',5byte recv
recv success :'test',4byte recv
recv success :'why',3byte recv
ready
send successful,5 byte send!
end
send successful,3 byte send!
quit
i will quit!
need othe connecdt (no->quit)no
quit!
客户端运行结果
$ ./tcp_sy_chat_client 172.18.229.62 8000
get ready pls chat
hello
send success,5 byte send
test
send success,4 byte send
why
send success,3 byte send
recv message:'ready',5 byte recv
recv message:'end',3 byte recv
the othe quit ,quit
原文链接
http://blog.csdn.net/geng823/article/details/41750405