60.1 介绍

  

  

  

  

  

60.2 例子

  

  echo_tcp_server_select.c

 #include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include "vector_fd.h" vector_fd *vfd;
int sockfd; void sig_handler(int signo)
{
if(signo == SIGINT){
printf("server close\n");
/** 步骤6: 关闭 socket */
close(sockfd);
/** 销毁动态数组 */
destroy_vector_fd(vfd);
exit();
}
} /**
* fd 对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
*/
void do_service(int fd)
{
char buff[];
memset(buff, , sizeof(buff)); /**
* 因为采用非阻塞方式,若读不到数据直接返回,
* 直接服务于下一个客户端,
* 因此不需要判断 size < 0 的情况 */
ssize_t size = read(fd, buff, sizeof(buff)); if(size == ){
/** 客户端已经关闭连接 */
printf("client closed\n");
/** 从动态数组中删除对应的 fd */
remove_fd(vfd, fd);
/** 关闭对应客户端的 socket */
close(fd);
}
else if(size > ){
printf("%s\n", buff);
if(write(fd, buff, size) < ){
if(errno == EPIPE){
/** 客户端关闭连接 */
perror("write error");
remove_fd(vfd, fd);
close(fd);
}
perror("protocal error");
}
}
} void out_addr(struct sockaddr_in *clientaddr)
{
char ip[];
memset(ip, , sizeof(ip));
int port = ntohs(clientaddr->sin_port);
inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
printf("%s(%d) connected!\n", ip, port);
} /** 遍历出动态数组中所有的描述符并加入到描述符集 set
* 中,同时此函数返回动态数组中最大的那个描述符 */
int add_set(fd_set *set)
{
FD_ZERO(set); ///< 清空描述符集
int max_fd = vfd->fd[0];
int i = ;
for(; i < vfd->counter; i++){
int fd = get_fd(vfd, i);
if(fd > max_fd) max_fd = fd;
FD_SET(fd, set); ///< 将 fd 加入到描述符集中
} return max_fd;
} void *th_fn(void *arg)
{
/** 设置超时时间 2s */
struct timeval t;
t.tv_sec = ;
t.tv_usec = ; int n = ;
int maxfd;
fd_set set; ///< 描述符集
maxfd = add_set(&set); /**
* 调用 select 函数会阻塞,委托内核去检查传入的描述符是否准备好,
* 若有则返回准备好的描述符;超时则返回 0
* 第一个参数为描述符集中的描述符的范围(最大描述符 + 1)
*/
while((n = select(maxfd + , &set, NULL, NULL, &t)) >= ){
/** 检测哪些描述符准备好, 并和这些准备好的描述符对应的客户端进行数据的双向通信 */
if(n > ){
int i = ;
for(; i < vfd->counter; i++){
int fd = get_fd(vfd, i);
if(FD_ISSET(fd, &set)){
do_service(fd);
}
}
}
/** 重新设置时间和清空描述符集 */
t.tv_sec = ;
t.tv_usec = ;
/** 重新遍历动态数组中最新的描述符放置到描述符集中 */
maxfd = add_set(&set);
} return (void *);
} int main(int argc, char *argv[])
{
if(argc < ){
printf("usage: %s #port\n", argv[]);
exit();
} if(signal(SIGINT, sig_handler) == SIG_ERR){
perror("signal sigint error");
exit();
} /** 步骤1: 创建 socket(套接字)
* 注: socket 创建在内核中,是一个结构体.
* AF_INET: IPV4
* SOCK_STREAM: tcp 协议
* AF_INET6: IPV6
*/
sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < ){
perror("socket error");
exit();
} /**
* 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
*/
struct sockaddr_in serveraddr;
memset(&serveraddr, , sizeof(struct sockaddr_in));
/** 往地址中填入 ip、port、internet 地址族类型 */
serveraddr.sin_family = AF_INET; ///< IPV4
serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
perror("bind error");
exit();
} /**
* 步骤3: 调用 listen 函数启动监听(指定 port 监听)
* 通知系统去接受来自客户端的连接请求
* (将接受到的客户端连接请求放置到对应的队列中)
* 第二个参数: 指定队列的长度
*/
if(listen(sockfd, ) < ){
perror("listen error");
exit();
} /** 创建放置套接字描述符 fd 的动态数组 */
vfd = create_vector_fd(); /** 设置线程的分离属性 */
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int err;
if((err = pthread_create(&th, &attr, th_fn, (void *))) != ){
perror("pthread create error");
exit();
}
pthread_attr_destroy(&attr); /**
* 1)主控线程获得客户端的链接,将新的 socket 描述符放置到动态数组中
* 2) (a)启动的子线程调用 select 函数委托内核去检查传入到 select
* 中的描述符是否准备好.
* (b)利用 FD_ISSET 来找出准备好的那些描述符,
* 并和对应的客户端进行双向通信(非阻塞)
*/
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr); while(){
/**
* 步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
* socket 描述符
* 注意: 若没有客户端连接,调用此函数后会阻塞, 直到获得一个客户端的连接
*/
/** 主控线程负责调用 accept 去获得客户端的连接 */
int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
if(fd < ){
perror("accept error");
continue;
} out_addr(&clientaddr); /** 将返回的新的 socket 描述符加入到动态数组中 */
add_fd(vfd, fd);
} return ;
}

  编译运行测试:

  

六十、linux 编程—— I/O 多路复用 select的更多相关文章

  1. 第五十五节,IO多路复用select模块加socket模块,伪多线并发

    IO多路复用select模块加socket模块,伪多线并发,并不是真正的多线程并发,实际通过循环等待还是一个一个处理的 IO多路复用,lo就是文件或数据的输入输出,IO多路复用就是可以多用户操作 IO ...

  2. 五十九、linux 编程—— I/O 多路复用 fcntl

    59.1 介绍 前面介绍的函数如,recv.send.read 和 write 等函数都是阻塞性函数,若资源没有准备好,则调用该函数的进程将进入阻塞状态.我们可以使用 I/O 多路复用来解决此问题(即 ...

  3. 五十六、linux 编程——UDP 编程模型

    56.1 UDP 编程模型 56.1.1 编程模型 UDP 协议称为用户数据报文协议,可靠性比 TCP 低,但执行效率高 56.1.2 API (1)发送数据 函数参数: sockfs:套接字文件描述 ...

  4. Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)

    Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...

  5. Linux IO多路复用 select

    Linux IO多路复用 select 之前曾经写过简单的服务器,服务器是用多线程阻塞,客户端每一帧是用非阻塞实现的 后来发现select可以用来多路IO复用,就是说可以把服务器这么多线程放在一个线程 ...

  6. Linux 多路复用 select / poll

    多路复用都是在阻塞模式下有效! linux中的系统调用函数默认都是阻塞模式,例如应用层读不到驱动层的数据时,就会阻塞等待,直到有数据可读为止. 问题:在一个进程中,同时打开了两个或者两个以上的文件,读 ...

  7. Python(七)Socket编程、IO多路复用、SocketServer

    本章内容: Socket IO多路复用(select) SocketServer 模块(ThreadingTCPServer源码剖析) Socket socket通常也称作"套接字" ...

  8. 基本套接字编程(3) -- select篇

    1. I/O复用 我们学习了I/o复用的基本知识,了解到目前支持I/O复用的系统调用有select.pselect.poll.epoll.而epoll技术以其独特的优势被越来越多的应用到各大企业服务器 ...

  9. 深入理解JAVA I/O系列六:Linux中的IO模型

    IO模型 linux系统IO分为内核准备数据和将数据从内核拷贝到用户空间两个阶段. 这张图大致描述了数据从外部磁盘向运行中程序的内存中移动的过程. 用户空间.内核空间 现在操作系统都是采用虚拟存储器, ...

随机推荐

  1. 走进Java Map家族 (1) - HashMap实现原理分析

    在Java世界里,有一个古老而神秘的家族——Map.从底层架构到上层应用,他们活跃于世界的每一个角落.但是,每次出现时,他们都戴着一张冷硬的面具(接口),深深隐藏着自己的内心.所有人都认识他们,却并非 ...

  2. centos7新增硬盘

    centos7新增硬盘 步骤:分区---格式化---挂载(配置开机自动挂载) 1.分区 fdisk -l 查看硬盘信息确认新硬盘的名称(以/dev/sdb为例) fdisk /dev/sdb  管理硬 ...

  3. eclipse 开发web 项目,使用gradle 需要安装的插件

    1.Buildship Gradle 扩展 eclipse IDE 以支持使用 Gradle 构建软件.此解决方案由 Eclipse 基金会提供 2.EGradle Editor (主要用来编写gra ...

  4. 在Windows7中的DPI与主题的问题

    测试环境Windows7x64,vb6.0 测试在XP系统下,DPI计算似乎没问题 Screen.TwipsPerPixelX=1440/DPI=1440/96=15Screen.TwipsPerPi ...

  5. Java实现遍历N级树形目录结构

    最近挺忙,一直在做项目,然后有个树形目录结构需要返回给前端,这里给大家说一下实现的思路. 具体达到的效果类似: 一级目录A: 二级目录A: 三级目录: 四级目录: 文件.txt 二级目录B: 文件1. ...

  6. ASP.NET Core 搭配 Nginx 的真实IP问题

    一.前言 Nginx(Engine X)是一个高性能HTTP和反向代理服务,是由俄罗斯人伊戈尔·赛索耶夫为访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发 ...

  7. [安全转帖]浅析安全威胁情报共享框架OpenIOC

    浅析安全威胁情报共享框架OpenIOC https://www.freebuf.com/sectool/86580.html Indicator of compromise Outline: 1. I ...

  8. zookeeper-3.5.4-beta安装

    官网地址 https://zookeeper.apache.org/ 下载文件解压进入conf目录下将zoo_sample.cfg名称修改为zoo.cfg # The number of millis ...

  9. mysql时间比较

    ' and ZXBZ ='Y' AND SQRQ >= '2017-04-28 00:00:00' AND SQRQ <= '2017-04-28 23:59:59'; ;

  10. VS你正在调试 XXXX 的发布版本

    按理来说,生成模式选择Debug就能解决这个问题,但是有些时候,即使生成切换到Debug,也依然还是提示正在调试 发布的版本 ,无奈一个文件一个文件的对比,最后发现是Properties文件夹下面的A ...