TCP/IP网络编程 学习笔记_13 --基于I/O复用的服务端

来源:互联网 发布:ae软件破解版 编辑:程序博客网 时间:2024/06/05 13:31

前言:前面我们讲了多进程的并发服务端,只要有客服端连接请求就会创建新进程,这虽然也是一种解决方案,但创建进程是需要付出极大代价的,这需要大量运算和内存空间,而且每个进程间具有独立的内存空间,所以相互间的数据交换也相对复杂(管道)。
本章将讨论并发服务器的第二种实现方法——基于I/O复用的服务器端构建。

I/O复用

  • 什么是I/O复用?通俗点讲,其实就是一个事件监听,只是这个监听的事件一般是I/O操作里的读(read)与写(write),只要发生了监听的事件它就会响应。注意与一般服务器的区别,一般服务器是连接请求先进入请求队列里,然后,服务端套接字一个个有序去受理。而I/O复用服务器是事件监听,只要对应监听事件发生就会响应,是属于并发服务器的一种。

  • I/O复用的使用
    1,I/O复用的使用其实就是对select函数的使用,说select函数是I/O复用的全部内容也不为过。但这个函数与一般函数不同,它很难使用,我们先来看看它的调用顺序,分为3步:
    步骤一:

    • 设置文件描述符,即注册要监听的文件描述符,如监听标准输入的文件描述符0 -> FD_SET(0, &reads)
    • 指定监视范围,Linux上创建文件对象生成的对应文件描述符是从0开始递增的,所以最大监视范围为最后创建的文件描述符+1。
    • 设置超时,因为select函数是一个阻塞函数,只有监视的文件描述符发生变化才会返回,设置超时就是为了防止阻塞,如果不想设置超时,则传递NULL。

    步骤二:

    • 调用select函数

    步骤三:

    • 查看调用结果,FD_ISSET(0, &reads)发生变化返回真。

    2,再来讲讲select函数,首先,来看看它的定义:

    int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
    maxfd:监视范围
    readset:监视对应文件描述符的接收事件,不监视这个事件则传0
    writeset:监视对应文件描述符的传输事件,不监视则传0
    exceptset:监视异常事件,不监视则传0
    timeout:设置超时时间,不设置则传NULL
    返回值:发生错误返回-1,超时返回0,发生参数2-4事件返回事件发生的对应文件描述符。

    然后,再来看看fd_set数组,它是一个存有0和1的位数组,这个数组中的值0表不监视,1表监视。而每个值对应在数组中的位置与文件描述符一一对应。如Linux上的标准输入的文件描述符是0,则要监听这个文件描述符,就只需把fd_set数组的fd_set[0]设置为1即可。下面是对fd_set数组操作的一些宏:

    FD_ZERO(fd_set *fdset):将fd_set变量的所有位初始化为0
    FD_SET(int fd, fd_set *fdset):设置文件描述符fd为监听状态
    FD_CLR(int fd, fd_set *fdset):取消文件描述符fd的监听状态
    FD_ISSET(int fd, fd_set *fdset):文件描述符fd是否发生相应的监视事件,发生则返回真。

    3,select函数调用示例:

////  main.cpp//  hello_client////  Created by app05 on 15-8-31.//  Copyright (c) 2015年 app05. All rights reserved.//#include <stdio.h>#include <unistd.h>#include <sys/time.h>#include <sys/select.h>#define BUF_SIZE 30int main(int argc, const char * argv[]) {    fd_set reads, temps;    int result, str_len;    char buf[BUF_SIZE];    struct timeval timeout;    /*初始化状态设置*/    FD_ZERO(&reads);   //将位数组reads初始化为0,即不监听任何文件描述符    FD_SET(0, &reads);  //设置监听,监听文件描述符为0的对象(标准输入)    while (1)    {        //因为select调用后,timeout和初始化状态会发生变化,所以需要每次循环前再初始化一次        temps = reads;        timeout.tv_sec = 5;        timeout.tv_usec = 5000;        result = select(1, &temps, 0, 0, &timeout);        if(result == -1)        {            puts("select() error");            break;        }        else if (result == 0)        {            puts("Time-out!");        }        else        {            //监听的文件描述符发生接收监听事件            if (FD_ISSET(0, &temps)) {                str_len = read(0, buf, BUF_SIZE);                buf[str_len] = 0;  //字符串输出结束符                printf("message from console: %s", buf);            }        }    }    return 0;}

这里写图片描述

实现I/O复用的服务端

用I/O复用的方式实现前面的回声程序

  • 服务端代码
////  main.cpp//  hello_server////  Created by app05 on 15-8-31.//  Copyright (c) 2015年 app05. All rights reserved.//#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/time.h>#include <sys/select.h>#define BUF_SIZE 100void error_handling(char *message);int main(int argc, const char * argv[]) {    int serv_sock, clnt_sock;    struct sockaddr_in serv_adr, clnt_adr;    struct timeval timeout;    fd_set reads, cpy_reads;    socklen_t adr_sz;    int fd_max, str_len, fd_num;    char buf[BUF_SIZE];    if (argc != 2) {        printf("Usage: %s <port> \n", argv[0]);        exit(1);    }    serv_sock = socket(PF_INET, SOCK_STREAM, 0);    memset(&serv_adr, 0, sizeof(serv_adr));    serv_adr.sin_family = AF_INET;    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);    serv_adr.sin_port = htons(atoi(argv[1]));    if(bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)        error_handling("bind() error");    if(listen(serv_sock, 5) == -1)        error_handling("listen() error");    FD_ZERO(&reads);    FD_SET(serv_sock, &reads);    fd_max = serv_sock;    while (1)    {        cpy_reads = reads;        timeout.tv_sec = 5;        timeout.tv_usec = 5000;        //监听服务端套接字和与客服端连接的服务端套接字的read事件        if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1)            break;        if(fd_num == 0)            continue;        if (FD_ISSET(serv_sock, &cpy_reads))//受理客服端连接请求        {            adr_sz = sizeof(clnt_adr);            clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);            FD_SET(clnt_sock, &reads);            if(fd_max < clnt_sock)                fd_max = clnt_sock;            printf("connected client: %d \n", clnt_sock);        }        else//转发客服端数据        {            str_len = read(clnt_sock, buf, BUF_SIZE);            if (str_len == 0)//客服端发送的退出EOF            {                FD_CLR(clnt_sock, &reads);                close(clnt_sock);                printf("closed client: %d \n", clnt_sock);            }            else            {                write(clnt_sock, buf, str_len);            }        }    }    close(serv_sock);    return 0;}void error_handling(char *message){    fputs(message, stderr);    fputc('\n', stderr);    exit(1);}

  • 客服端代码用以前写的,随便copy一个来
////  main.cpp//  hello_client////  Created by app05 on 15-7-6.//  Copyright (c) 2015年 app05. All rights reserved.//#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <arpa/inet.h>#include <sys/socket.h>#define BUF_SIZE 1024void error_handling(char *message){    fputs(message, stderr);    fputc('\n', stderr);    exit(1);}int main(int argc, const char * argv[]) {    int sock;    char message[BUF_SIZE];    int str_len, recv_len, recv_cnt;    struct sockaddr_in serv_adr;    if(argc != 3)    {        printf("Usage: %s <IP> <port> \n", argv[0]);        exit(1);    }    sock = socket(PF_INET, SOCK_STREAM, 0);    if(sock == -1)        error_handling("socket() error");    memset(&serv_adr, 0, sizeof(serv_adr));    serv_adr.sin_family = AF_INET;    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);    serv_adr.sin_port = htons(atoi(argv[2]));    if (connect(sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)        error_handling("connect() error");    else        puts("Connected ...............");    while (1) {        fputs("Input message(Q to quit): ", stdout);        fgets(message, BUF_SIZE, stdin);        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))            break;        str_len = write(sock, message, strlen(message));        /*这里需要循环读取,因为TCP没有数据边界,不循环读取可能出现一个字符串一次发送         但分多次读取而导致输出字符串不完整*/        recv_len = 0;        while (recv_len < str_len) {            recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1);            if(recv_cnt == -1)                error_handling("read() error");            recv_len += recv_cnt;        }        message[recv_len] = 0;        printf("Message from server: %s", message);    }    close(sock);    return 0;}

这里写图片描述
这里写图片描述

0 0
原创粉丝点击