像ssh这样的登录服务通常要同时处理网络连接和用户输人,这也可以使用IO复用来实现。我们以poll为例实现一个简单的聊天室程序,以阐述如何使用IO复用技术来同时处理网络连接和用户输人。
该聊天室程序能让所有用户同时在线群聊,
它分为客户端和服务器两个部分。其中客户端程序有两个功能:一是从标准输人终端读入用户数据,并将用户数据发送至服务器﹔二是往标准输出终端打印服务器发送给它的数据。
服务器的功能是接收客户数据,并把客户数据发送给每一个登录到该服务器上的客户端〈数据发送者除外)。
客户端代码:
#define _GUN_SOURCE 1
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<poll.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#define BUFFER_SIZE 64
int main(int argc,char* argv[])
{
if(argc<=2)
{
printf("Usage: %s ip_address port_number\n",basename(argv[0]));
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
address.sin_port=htons(port);
inet_pton(AF_INET,ip,&address.sin_addr);
int sockfd=socket(PF_INET,SOCK_STREAM,0);
assert(sockfd>=0);
if(connect(sockfd,(struct sockaddr*)&address,sizeof(address))<0)
{
printf("connection failed\n");
close(sockfd);
return 1;
}
pollfd fds[2];
/*注册文件描述符0(标准输入)文件描述符sockfd上的可读事件*/
fds[0].fd=0;
fds[0].events=POLLIN;
fds[0].revents=0;
fds[1].fd=sockfd;
fds[1].events=POLLIN|POLLRDHUP;//接收到的是有效数据还是对方关闭连接的
fds[1].revents=0;
char read_buf[BUFFER_SIZE];
int pipefd[2];
int ret=pipe(pipefd);
assert(ret>=0);
while(1)
{
ret=poll(fds,2,-1);
if(ret<0)
{
printf("poll failure\n");
break;
}
if(fds[1].revents&POLLRDHUP)
{
printf("server close the connection");
break;
}
else if(fds[1].revents& POLLIN);
{
memset(read_buf,'\0',BUFFER_SIZE);
recv(fds[1].fd,read_buf,BUFFER_SIZE-1,0);
printf("%s\n",read_buf);
}
if(fds[0].revents&POLLIN)
{
ret=splice(0,NULL,pipefd[1],NULL,32678,SPLICE_F_MORE|SPLICE_F_MOVE);
ret=splice(pipefd[0],NULL,fds[1].fd,NULL,32678,SPLICE_F_MORE|SPLICE_F_MOVE);
}
}
close(sockfd);
return 1;
}
服务端
:
#define _GUN_SOURCE 1
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<poll.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#include<errno.h>
#define LIMIT_USER 5
#define BUFFER_SIZE 64
#define LIMIT_FD 65535
int setnoblock(int fd)
{
int old_option=fcntl(fd,F_GETFD);
int new_option=old_option|O_NONBLOCK;
fcntl(fd,F_SETFD,new_option);
return old_option;
}
struct client_data
{
struct sockaddr_in address;
char* write_buf;
char buf[BUFFER_SIZE];
};
int main(int argc,char* argv[])
{
if(argc<=2)
{
printf("Usage: %s ip_address port_number\n",basename(argv[0]));
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
address.sin_port=htons(port);
inet_pton(AF_INET,ip,&address.sin_addr);
int sockfd=socket(PF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
int ret=bind(sockfd,(struct sockaddr*)&address,sizeof(address));
assert(ret!=-1);
ret=listen(sockfd,LIMIT_USER);
assert(ret=-1);
/*创建users数组,分配FD_LINIT个client_data对象。
可以预期﹔每个可能的socket连接都可以获得一个这样的对象,
并且socket 的值可以直接用来索引(作为数组的下标) socket连接对应的
client_data对象,这是将socket和客户数据关联的筒单而高效的方式*/
client_data* user=new client_data[LIMIT_USER];
/*
poll 采用的是轮询 所以提高poll的性能是限制用户的数量
*/
pollfd fds[LIMIT_USER+1];
int user_counter=0;
for(int i=1;i<=LIMIT_USER;i++)
{
fds[i].fd=-1;
fds[i].events=0;
}
fds[0].fd=sockfd;
fds[0].events=POLLIN|POLLERR;
fds[0].revents=0;
while(1)
{
ret=poll(fds,user_counter+1,-1);
if(ret<0)
{
printf("poll failure\n");
return 1;
}
for(int i=0;i<user_counter+1;i++)
{
if((fds[i].fd==sockfd)&&(fds[i].revents&POLLIN))
{
struct sockaddr_in client;
socklen_t client_len=sizeof(client);
int connfd=accept(sockfd,(struct sockaddr*)&client,&client_len);
if(connfd<0)
{
printf("error is :%d\n",errno);
continue;
}
if(user_counter>=LIMIT_USER)
{
const char* info="too many user\n";
printf("%s",info);
send(connfd,info,strlen(info),0);
close(connfd);
continue;
}
user_counter++;
user[connfd].address=client;
setnoblock(connfd);
fds[user_counter].fd=connfd;
fds[user_counter].events=POLLIN|POLLRDHUP|POLLERR;
fds[user_counter].revents=0;
printf("comes a new user ,now hava %d users\n",user_counter);
}
else if(fds[i].revents&POLLERR)
{
printf("get an error from %d\n",fds[i].fd);
char error[100];
memset(error,'\0',100);
socklen_t length=sizeof(error);
if(getsockopt(fds[i].fd,SOL_SOCKET,SO_ERROR,&error,&length)<0)
{
printf("get socket option failed\n");
continue;
}
}
else if(fds[i].revents&POLLRDHUP)
{
user[fds[i].fd]=user[fds[user_counter].fd];
close(fds[i].fd);
fds[i]=fds[user_counter];
i--;
user_counter--;
printf("a client left\n");
}
else if(fds[i].revents&POLLIN)
{
int connfd=fds[i].fd;
memset(user[connfd].buf,'\0',BUFFER_SIZE);
ret=recv(connfd,user[connfd].buf,BUFFER_SIZE-1,0);
printf("get %d bytes of client data %s from %d\n",ret,user[connfd].buf,connfd);
if(ret<0)
{
if(errno!=EAGAIN)
{
user[fds[i].fd]=user[fds[user_counter].fd];
fds[i]=fds[user_counter];
i--;
user_counter--;
}
}
else if(ret==0)
{}
else
{
for (int j=1;j<=user_counter;++j)
{
if(fds[j].fd==connfd)
{
continue;
}
fds[j].events|=~POLLIN;
fds[j].events|=POLLOUT;
user[fds[j].fd].write_buf=user[connfd].buf;
}
}
}
else if(fds[i].revents&POLLOUT)
{
int connfd=fds[i].fd;
if(!user[connfd].write_buf)
{
continue;
}
ret =send(connfd,user[connfd].write_buf,strlen(user[connfd].write_buf),0);
fds[i].events|=~POLLOUT;
fds[i].events=POLLIN;
}
}
}
delete [] user;
close(sockfd);
return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129703.html