Linux网络编程(五)

/*
Linux网络编程(五)——多路IO复用之select()


网络编程中,使用IO复用的典型场合:
1.当客户处理多个描述字时(交互式输入以及网络接口),必须使用IO复用。
2.一个客户同时处理多个套接口。
3.一个tcp服务程序既要处理监听套接口,又要处理连接套接口,一般需要用到IO复用。
4.如果一个服务器既要处理TCP,又要处理UDP,一般也需要用到IO复用。
5.如果一个服务器要处理多个服务或者多个协议,一般需要用到IO复用。
*/
/***********************************************************************
本程序功能:
使用单进程为多个客户端服务,接收到客户端发来的一条消息后,将该消息原样返回给客户端。
首先,建立一个监听套接字来接收来自客户端的连接。每当接收到一个连接后,将该连接套
接字加入客户端套接字数组,通过select实现多路复用。每当select返回时,检查套接字数
组的状态。并进行相应操作,如果是新的连接到来,则将新的连接套接字加入套接字数组,
如果客户端连接套接字变为可读,则对相应客户端进行响应。
***********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <netdb.h>
#include <errno.h>
#define SERV_PORT 2046
#define LISTENQ  32
#define MAXLINE 1024
int
main(int argc, char **argv)
{
    int                    i, maxi, maxfd, listenfd, connfd, sockfd;
    int                    nready, client[FD_SETSIZE];
    ssize_t                n;
    fd_set                rset, allset;
    char                buf[MAXLINE];
    socklen_t            clilen;
    struct sockaddr_in    cliaddr, servaddr;
    if((listenfd = socket(AF_INET, SOCK_STREAM,0))==-1){
        fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
        exit(1);
    }
    /* 服务器端填充 sockaddr结构*/ 
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);

     /* 捆绑listenfd描述符  */ 
    if(bind(listenfd,(struct sockaddr*)(&servaddr),sizeof(struct sockaddr))==-1){
        fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
        exit(1);
    }
   /* 监听listenfd描述符*/
    if(listen(listenfd,LISTENQ)==-1){
        fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
        exit(1);
    }
    maxfd = listenfd;            /* 初始化最大文件描述符*/
    maxi = -1;                    /*client数组索引*/
    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;            /* -1代表未使用*/
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);

    for ( ; ; ) {
        rset = allset;    
        if((nready = select(maxfd+1, &rset, NULL, NULL, NULL))<0){
            fprintf(stderr,"select Error\n");
            exit(1);
        }
        if (FD_ISSET(listenfd, &rset)) {    /*有新的客户端连接到来*/
            clilen = sizeof(cliaddr);
            if((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen))<0){
                fprintf(stderr,"accept Error\n");
                continue;
            }
            char des[sizeof(cliaddr)];
            inet_ntop(AF_INET, &cliaddr.sin_addr, des, sizeof(cliaddr));
            printf("new client: %s, port %d\n",des,ntohs(cliaddr.sin_port));

            for (i = 0; i < FD_SETSIZE; i++)
                if (client[i] < 0) {
                    client[i] = connfd;    /*保存新的连接套接字*/
                    break;
                }
            if (i == FD_SETSIZE){
                fprintf(stderr,"too many clients");
                exit(1);
            }
            FD_SET(connfd, &allset);    /*将新的描述符加入监听数组中*/
            if (connfd > maxfd)
                maxfd = connfd;            
            if (i > maxi)
                maxi = i;                /*当前client数组最大下标值*/
            if (--nready <= 0)
                continue;                /*可读的套接字全部出来完了*/
        }

        for (i = 0; i <= maxi; i++) {    /*检查所有已经连接的客户端是否由数据可读*/
            if ( (sockfd = client[i]) < 0)
                continue;
            if (FD_ISSET(sockfd, &rset)) {
                if ( (n = read(sockfd, buf, MAXLINE)) == 0) {/*客户端主动断开了连接*/
                    close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;
                } else
                    write(sockfd, buf, n);

                if (--nready <= 0)
                    break;                /*可读的套接字全部出来完了*/
            }
        }
    }
}
/*
使用IO复用客户端
*/
/***********************************************************************
本程序(客户端)功能:
1.向服务器发起连接请求,并从标准输入stdin获取字符串,将字符串发往服务器。
2.从服务器中接收字符串,并将接收到的字符串输出到标准输出stdout.
=========================================================================
问题:由于既要从标准输入获取数据,又要从连接套接字中读取服务器发来的数据。
      为避免当套接字上发生了某些事件时,程序阻塞于fgets()调用,使用select
      实现多路IO复用,或等待标准输入,或等待套接口可读。这样一来,若服务器
      进程终止,客户端能马上得到通知。
=========================================================================
对于客户端套接口,需要处理以下三种情况:
1.服务器端发送了数据过来,套接口变为可读,且read返回值大于0
2.服务器端发送了一个FIN(服务器进程终止),套接口变为可读,且read返回值等于0
3.服务器端发送了一个RST(服务器进程崩溃,且重新启动,此时服务器程序已经不认
  识之前建立好了的连接,所以发送一个RST给客户端),套接口变为可读,且read返回-1
  错误码存放在了errno
***********************************************************************/
//使用多路复用select的客户端程序
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <netdb.h>
#define SERV_PORT 2046 
#define MAXLINE 1024
#define max(x,y) (x)>(y) ? (x):(y)
void str_cli(FILE *fp, int sockfd);
int
main(int argc, char **argv)
 {
    int     sockfd;
    struct sockaddr_in servaddr;
    if (argc != 2){
        fprintf(stderr,"usage: tcpcli <IPaddress>\n\a");
        exit(0);
    }
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
        fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
        exit(1);
    }
    /*客户程序填充服务端的资料*/
      memset(&servaddr,0,sizeof(servaddr));
      servaddr.sin_family=AF_INET;
      servaddr.sin_port=htons(SERV_PORT);
      if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
            fprintf(stderr,"inet_pton Error:%s\a\n",strerror(errno));
            exit(1);
      }
       /* 客户程序发起连接请求*/ 
      if(connect(sockfd,(struct sockaddr *)(&servaddr),sizeof(struct sockaddr))==-1){
            fprintf(stderr,"connect Error:%s\a\n",strerror(errno));
            exit(1);
      }
     str_cli(stdin, sockfd);     /*重点工作都在此函数*/
     exit(0);
 }

void
str_cli(FILE *fp, int sockfd)
{
    int            maxfdp1, stdineof;
    fd_set        rset;/*用于存放可读文件描述符集合*/
    char        buf[MAXLINE];
    int        n;
    stdineof = 0;/*用于标识是否结束了标准输入*/
    FD_ZERO(&rset);
    while(1){
        if (stdineof == 0)
            FD_SET(fileno(fp), &rset);
        FD_SET(sockfd, &rset);
        maxfdp1 = max(fileno(fp), sockfd) + 1;
        if(select(maxfdp1, &rset, NULL, NULL, NULL)<0){/*阻塞,直到有数据可读或出错*/
            fprintf(stderr,"select Error\n");
            exit(1);
        }
        if (FD_ISSET(sockfd, &rset)) {    /*套接口有数据可读*/
            if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
                if (stdineof == 1)
                    return;        /*标准输入正常结束*/
                else
                    fprintf(stderr,"str_cli: server terminated prematurely");
            }
            write(fileno(stdout), buf, n);/*将收到的数据写到标准输出*/
        }
        if (FD_ISSET(fileno(fp), &rset)) {  /*标准输入可读*/
            if ( (n = read(fileno(fp), buf, MAXLINE)) == 0) {
                stdineof = 1;
                /*向服务器发送FIN,告诉它,后续已经没有数据发送了,但仍为读而开放套接口*/
                if(-1==shutdown(sockfd, SHUT_WR)){
                    fprintf(stderr,"shutdown Error\n");
                }
                FD_CLR(fileno(fp), &rset);
                continue;
            }
            write(sockfd, buf, n);
        }
    }
}
/**********************************************************
注:关于close与shutdown的区别:
    close()将描述字的访问计数减1,仅在访问计数为0时才关闭套接字。
    而shutdown可以激发TCP的正常连接终止系列,而不管访问计数。
    close()终止了数据传输的两个方向:读、写。
***********************************************************/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值