1.项目介绍:
在linux系统下,实现服务端和客户端的简单通信:
1.1 服务端采用的是poll机制实现对输入输出事件进行管理
1.2 客户端socekt设置成了非阻塞;并且在接收函数出采用poll机制监听socket;
2.问题介绍
问题1:客户端poll机制监听socket时,会不断的监听成功;
理论上:
接收缓冲区的 “第一个这是第1个超级女生,编号001。” 这段数据被recv后,poll应该阻塞(因为缓冲区中已经没有数据),直到收到"第一个这是第2个超级女生,编号002"才停止阻塞;
实际上:
问题2:poll函数在缓冲区没数据时并没有阻塞; 而且recv在缓冲区没数据时不断接收数据;
2.2 recv函数返回值>0 但是recv(socket,buffer,10,0);但是用于接收缓冲区中数据的buffer的长度却为0,没有任何数据;
理论上:
缓冲区如果没有数据可读,返回值应该小于0,错误代码除了errno==EAGAIN的情况,其余错误都应该退出;
实际上:
recv返回值大于0,但是没有任何数据被读取到;
服务端代码如下:
#include<stdio.h>
#include<string.h>
#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
#include<sys/fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include <poll.h>
#define MAXSIZE 1024
int initserver(int port); //初始化服务端端口号
int main(int argc,char *argv[])
{
//帮助文档
if(argc!=2)
{
printf("Using:./tcppoll port\n");
printf("Example:/project/public/socket1/tcppoll 5050\n");
printf("本程序演示poll模型的特点:监听事件的发生。\n\n\n");
return -1;
}
//初始化端口
int listenfd=initserver(atoi(argv[1]));
printf("listenfd=%d\n",listenfd);
if(listenfd<0){printf("initserver() failed.\n");return -1;}
//定义pollfd结构体 poll函数用该结构体接收是否有事件的发生
struct pollfd pfds[MAXSIZE];
//初始化结构体
for(int ii=0;ii<MAXSIZE;ii++)
{
pfds[ii].fd=-1; //该值为-1时,相应的字段事件将被忽虑,然后reevent返回值是0;
}
//把监听listenfd的socket和读事件添加到数组中
pfds[listenfd].fd=listenfd;
pfds[listenfd].events=POLLIN;
pfds[listenfd].revents=0;
int maxfd=listenfd; //最大的socket
//用poll模型实现监听多个socket的动作
while(1)
{
int iret=poll(pfds,maxfd+1,-1); //poll机制监听事件 iret记录其返回值
if(iret<0){perror("poll() error.");break;} //<0 调用函数失败
if(iret==0){printf("poll timeout.");continue;} //设置的超时时间内没发生任何事情,我们的poll函数没有设置超时时间
//iret>0表示有事件发生,查看发生事件的socket
for(int eventfd=0;eventfd<=maxfd;eventfd++) //遍历被监听的socket
{
if(pfds[eventfd].fd==-1)continue;
if( (pfds[eventfd].revents&POLLIN)==0)continue;
//若发生的事件的socket为listenfd,则表示有新的客户端连接上来
if(eventfd==listenfd)
{
//为客户端的连接建立通信socket
struct sockaddr_in sock;
socklen_t socklen=sizeof(sock);
int clientsock=accept(listenfd,(struct sockaddr*)&sock,&socklen);
if(clientsock<0){ perror("accept() failed");continue; }
printf("accept client(socket=%d) ok.\n",clientsock);
//将该socket加入poll的结构体中
pfds[clientsock].fd=clientsock;
pfds[clientsock].events=POLLIN;
pfds[clientsock].revents=0;
//每次新增一个socket,都要与原maxfd比较看哪个最大
if(maxfd<clientsock)maxfd=clientsock;
}
else
{
// printf("有数据可读取.\n");
char buffer[1024];memset(buffer,0,sizeof(buffer));
//客户端发送的数据可读
if(recv(eventfd,buffer,sizeof(buffer),0)<=0)
{
printf("client(socket=%d) disconnect.\n",eventfd);
close(eventfd); //关闭socket
pfds[eventfd].fd=-1;
//检验断开连接的socket是否是最大socket
if(eventfd==maxfd)
{
for(int ii=maxfd;ii>=0;ii--)
{
if(pfds[ii].fd==-1)continue;
maxfd=ii;break;
}
}
}
else
{
//发送报文给对端
printf("recv(client=%d)=%s\n",eventfd,buffer);
send(eventfd,buffer,sizeof(buffer),0);
}
}
}
}
return 0;
}
int initserver(int port) //初始化服务端端口号
{
int listenfd; //监听socket
if( (listenfd=socket(AF_INET,SOCK_STREAM,0))==-1 ){ perror("socket() failed."); return -1;}
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);
// 第2步:把服务端用于通讯的地址和端口绑定到socket上。
struct sockaddr_in servaddr; // 服务端地址信息的数据结构。
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET; // 协议族,在socket编程中只能是AF_INET。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
servaddr.sin_port = htons(port); // 指定通讯端口。
if ( bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr) ) != 0 )
{ perror("bind() failed.\n"); close(listenfd); return -1; }
// 第3步:把socket设置为监听模式。
if (listen(listenfd,5) != 0 ) { perror("listen() failed.\n"); close(listenfd); return -1; }
return listenfd;
}
客户端代码:
#include<poll.h> #include<stdio.h> #include<string.h> #include<unistd.h> #include<stdlib.h> #include<netdb.h> #include<sys/types.h> #include<sys/socket.h> #include<arpa/inet.h> #include<fcntl.h> #include <errno.h> int main(int argc,char *argv[]) { if (argc!=3) { printf("Using:./demo0101 ip port\nExample:./demo0101 127.0.0.1 5005\n\n"); return -1; } // 第1步:创建客户端的socket。 int sockfd; if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; } // 第2步:向服务器发起连接请求。 struct hostent* h; if ( (h = gethostbyname(argv[1])) == 0 ) // 指定服务端的ip地址。 { printf("gethostbyname failed.\n"); close(sockfd); return -1; } struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通讯端口。 memcpy(&servaddr.sin_addr,h->h_addr,h->h_length); fcntl(sockfd,F_SETFL,O_NONBLOCK); //FSETFL设置sockfd描述符为O_NONBLOCK参数 意思是把socket描述符设置成非阻塞状态 int ret; if (ret=(connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr))) != 0) // 向服务端发起连接清求。 { if(errno==EINPROGRESS); //连接正在进行中,啥也不干 else { perror("connect"); close(sockfd); return -1; } // printf("ret=%d,error=%d,EINPROGRESS=%d\n",ret,errno,EINPROGRESS); } int iret; char buffer[1024]; // 第3步:与服务端通讯,发送一个报文后等待回复,然后再发下一个报文。 for (int ii=0;ii<10;ii++) { memset(buffer,0,sizeof(buffer)); sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1); if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // 向服务端发送报文。 { perror("send"); break; } printf("发送:%s\n",buffer); memset(buffer,0,sizeof(buffer)); //poll函数监听socket读事件,非阻塞状态下函数返回快,防止错误退出 struct pollfd pfd; pfd.fd=sockfd; pfd.events=POLLIN; pfd.revents=0; int iret0; while(1) { iret0=poll(&pfd,1,-1); if(iret0<0)break; if(iret0==0)continue; // printf("iret0=%d,QIAN_revents=%d\n",iret0,pfd.revents); if(pfd.revents&POLLIN==0)continue; //查看返回时间是否为空 memset(buffer,0,sizeof(buffer)); if ( (iret=recv(sockfd,buffer,10,0))<=0) // 接收服务端的回应报文。每次接收10个字节 { if(EAGAIN==errno)break; //非阻塞太快无数据可读 else {printf("iret=%d,errno=%d,EAGAIN=%d\n",iret,errno,EAGAIN); break;} } /* if ( (iret=recv(sockfd,ptr,10,0))<=0) // 接收服务端的回应报文。 { if(EAGAIN==errno); else {printf("iret=%d,errno=%d,EAGAIN=%d\n",iret,errno,EAGAIN); break;} } //ptr=ptr+iret; /printf("接收:%s\n",ptr); */ printf("接收:%s(strlen=%d---iret=%d)\n",buffer,strlen(buffer),iret); } } sleep(1); // 每隔一秒后再次发送报文。 } // 第4步:关闭socket,释放资源。 close(sockfd); }
服务端运行结果: 出现clients(client=4)disconnect的原因是我在客户端使用了crtl+c终止了客户端。
客户端运行结果:
没有数据poll没有阻塞,没有数据recv也没有退出,客户的接收的buffer输出为空,长度为0,但是recv返回值却为10>0,
我想问一下有没有知道原因的同学,求告知;