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


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

  1. TCP/IP网络编程 学习笔记_1 --网络编程入门

    前言:这个系列网络编程教程实例代码是在Xcode上运行的,MacOSX,是一个基于UNIX核心的系统,所以基于Linux的网络编程代码一般可以直接在Xcode上运行,如果要移植到Windows其实就只 ...

  2. TCP/IP网络编程 学习笔记_3 --给套接字分配IP地址和端口号

    IP地址和端口号 1,IP地址:为使计算机连接到网络并收发数据,必须为其分配IP地址.IP地址分为两类:IPv4(4字节地址族)和IPv6(16字节地址族).它们主要区别就是在表示IP地址所用的字节数 ...

  3. 《TCP/IP 网络编程》笔记

    本文主要基于<TCP/IP 网络编程>这本书进行总结,主要针对 Linux 网络编程部分进行阐述,Windows 网络编程部分有需要建议阅读原书籍. 一.基础知识 网络编程 网络编程就是编 ...

  4. TCP/IP网络编程(一)

    TCP/IP网络编程读书笔记 第1章 理解网络编程和套接字 1.1 理解网络编程和套接字 1.1.1 构建打电话套接字 1.1.2 编写 Hello World 套接字程序 1.2 基于Linux的文 ...

  5. TCP/IP网络编程之基于TCP的服务端/客户端(一)

    TCP/IP网络编程之基于TCP的服务端/客户端(一) 理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字.因为TCP套接字是面向连接的,因此又称为基于 ...

  6. TCP/IP网络编程之基于TCP的服务端/客户端(二)

    回声客户端问题 上一章TCP/IP网络编程之基于TCP的服务端/客户端(一)中,我们解释了回声客户端所存在的问题,那么单单是客户端的问题,服务端没有任何问题?是的,服务端没有问题,现在先让我们回顾下服 ...

  7. TCP/IP详解学习笔记-基本概念

    为什么会有TCP/IP协议 在世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别.就好像圣经中上帝打乱了各地人的口音,让他们无法合作一样 ...

  8. 基础才是王道——TCP/IP详解学习笔记 这位仁兄写得太好了

    TCP/IP详解学习笔记 这位仁兄写得太好了 TCP/IP详解学习笔记   这位仁兄写得太好了. http://blog.csdn.net/goodboy1881/category/204448.as ...

  9. TCP/IP网络编程:P1->理解网络编程和套接字

    本系列文章为<TCP/IP网络编程----尹圣雨>学习笔记 文章目录 一.理解网络编程和套接字 1.1 构建接电话套接字 1.2 编写"Hello world!"服务器 ...

最新文章

  1. java 同步包_Java并发程序设计(四)JDK并发包之同步控制
  2. MySQL5.7 支持一个表有多个INSERT/DELETE/UPDATE触发器
  3. mongodb 服务器时区设置_关于MongoDB-Balancer设置时间窗口的问题
  4. 【模型解读】浅析RNN到LSTM
  5. JAVA——DES/ECB/PKCS7Padding加密算法[Cannot find any provider supporting DESEDE/CBC/PKCS7Padding]解决方案
  6. ov5640帧率配置_逃离塔科夫怎么提升帧率 帧率优化建议_单机游戏_游戏攻略
  7. @SuppressWarnings使用的正确姿势
  8. ssrf漏洞内网渗透_渗透小白看了也能明白的SSRF
  9. access重复数据累计_小程序·云开发之数据库自动备份丨云开发101
  10. u盘启动pxe安装linux,U盘启动安装centos5.5+centos6.3+PXE网络安装CentOS
  11. Java数字匹配的kmp算法
  12. 学习笔记:2019 张小龙在微信公开课上的演讲
  13. 自动化登陆博客园脚本
  14. vue2.x 给一个对象里添加一个没有的属性
  15. mysql 多数据源_SpringBoot+多数据源(MySQL)
  16. 用友NC安装教程、用友NC65安装教程、用友NC57安装教程、NC安装教程、NC65授权教程
  17. AMSim高级系统建模与仿真软件安装坡姐过程的踩坑心得
  18. 在线画图工具ProcessOn
  19. http长连接与主动断开方
  20. VMware虚拟机安装win10卡顿优化

热门文章

  1. AsyncTask隐藏的陷阱
  2. 无法访问windows安装服务
  3. 关于UBNT 8.5.1版本无法从有线端管理设备的bug
  4. Open Session in View
  5. 某地法院HP EVA8400删除VDISK后数据恢复成功
  6. 腾讯为何做不了电商?
  7. MediaPlayer+TextureView实现视频播放器
  8. 2022美国大学生数学建模竞赛F题思路
  9. Houdini Procedural Animation Techniques (cmiVFX--H11)
  10. 文本比对-python