Linux高性能服务器之poll应用(16)

像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;


}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值