阻塞式/非阻塞式IO
【知识点】
非阻塞式IO 的两种设置方法
(1) 函数fcntl( ),设置 O_NONBLOCK 选项
int flag=fcntl(sockfd,F_GETFL,0);检查文件标志位
fcntl(sockfd,F_SETFL,flag|O_NONBLOCK);设置文件标志位
(2) 函数ioctl( ),设置FIONBIO 选项
int nIO=1;设置非阻塞IO
ioctl(sockfd,FIONBIO,&nIO);
完成y=a+b
a 由客户端从标准输入获得,消耗时间有用户自行掌握。
b 由服务端随机生成,消耗时间t 由服务端的随机确定(不超过1 分钟)。
要求:
(1) 客户端使用非阻塞式IO 进行接收数据。
(2) 客户端向服务器发出请求数据b
(3) 客户端从标准输入获取一个整型
(4) 客户端从服务端获得随机数。
(5) 客户端计算并显示计算结果。
(6) 服务端接到请求后,产生随机消耗时间t。
(7) 服务端sleep(t)后返回一个随机数给客户端。
(8) 可参考以下流程图进行实现。
主要代码:
客户端:(nonbcli.c)
#include "my.h" //调试代码中的头文件,包含许多函数对出错的处理
#define max(a,b) a>b?a:b
int a,b;
int flag;
void str_cli(FILE *fp, int sockfd)
{
Write(sockfd,"fornumber",10);
flag=fcntl(sockfd,F_GETFL,0);
/* 设置非阻塞标志 */
fcntl( sockfd,F_SETFL,flag | O_NONBLOCK );
int maxfdp1, stdineof,sockread;
fd_set rset;
char buf[MAXLINE];
int n;
sockread=0;
stdineof = 0;
FD_ZERO(&rset);
for ( ; ; ) {
if (stdineof == 0)
FD_SET(fileno(fp), &rset);
if(sockread==0)
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if ( (n = Read(sockfd, &b, sizeof(b))) < 0) {
if (errno != EWOULDBLOCK)
printf("read error(socket)\n");
}
sockread=1;
if (stdineof == 1)
break; /* normal termination */
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if ( (n = Read(fileno(fp), buf, MAXLINE)) < 0) {
if (errno != EWOULDBLOCK)
printf("read error(fp)\n");
}
FD_CLR(fileno(fp), &rset);
stdineof = 1;
buf[n]='\0';
a=atoi(buf);
printf("read from stdin:%d\n",a);
if(sockread==1)break;
}
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8000);
if((inet_pton(AF_INET, argv[1], &servaddr.sin_addr))<0)
perror("inet_pton");
flag=fcntl(fileno(stdin),F_GETFL,0);
fcntl(fileno(stdin),F_SETFL,flag|O_NONBLOCK);
Connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
printf("a+b = %d+%d = %d\n",a,b,a+b);
exit(0);
}
服务器端:(nonbserv.v)
#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>/*for close*/
#include <fcntl.h>
#include <sys/wait.h>
#include <netdb.h>
#include <string.h> /*for memset*/
#include <signal.h>
#include <errno.h>
#include <time.h>
int main(int argc,char **argv){
int n,b,t,listenfd,connfd;
struct sockaddr_in my_addr;
struct sockaddr_in remote_addr;
socklen_t len = sizeof(remote_addr);
char buf[1024];
memset(&my_addr,0,sizeof(my_addr));
my_addr.sin_family=AF_INET;
my_addr.sin_addr.s_addr=INADDR_ANY;
my_addr.sin_port=htons(8000);
if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0){
perror("socket");
return 1;
}
int on = 1;
if((setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0) {
perror("setsockopt failed");
exit(1);
}
if (bind(listenfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0){
perror("bind");
return 1;
}
listen(listenfd,5);
for(;;){
printf("waiting...\n");
connfd = accept(listenfd, (struct sockaddr *)&remote_addr,&len);
printf("new client come.\n");
if(connfd<0){
perror("accept error");
}
n = recv(connfd, buf, sizeof(buf), 0);
printf("recv_buf: %s\n", buf); // 打印数据
srand((int)time(0));//生成2个随机数
b=rand()%1000;//不要太大
sleep(5);
if((send(connfd, &b, sizeof(b), 0))<0) // 给客户端回数据
perror("send");
printf("send b: %d\n",b);
printf("client closed!\n");
close(connfd); //关闭已连接套接字
}
close(listenfd);
return 0;
}
运行结果:
客户端:
服务器端:
分析:
客户端是在TCP多路复用代码上的修改,主要是将标准输入和套接口设置成了非阻塞IO来接收数据,还多设置了一个套接口读的标识符sockread,sockfd套接口上读过之后就置sockread为1。服务器端则是普通的TCP循环服务器,多使用了一个获取随机数的函数srand()。
如上面的截图所示,服务器开启后等待客户端的连接请求。客户端开启后请求连接,并发送一个要求(字符串:fornumber)。此时客户端使用select等待标准输入和套接口可读,服务器在获取随机数准备发往客户端。当我们从客户端的标准输入输入a(a=13),没过多久也收到了服务器端发来的数据b(b=628),客户端将计算结果显示。