59.1 介绍

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

  • I/O 多路复用的方式主要有两种实现方法

    • fcntl 函数实现(非阻塞方式)
    • select 函数实现

59.1.1 fcntl 非阻塞方式——I/O多路复用/转换

  

59.2 例子

59.2.1 动态数组模块

vector_fd.c

 #include <malloc.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <memory.h>
#include "vector_fd.h" static void encapacity(vector_fd *vfd)
{
if(vfd->counter >= vfd->max_counter){
int *fds = (int *)calloc(vfd->counter + , sizeof(int));
assert(fds != NULL);
memcpy(fds, vfd->fd, sizeof(int) * vfd->counter);
free(vfd->fd);
vfd->fd = fds;
vfd->max_counter += ;
}
} static int indexof(vector_fd *vfd, int fd)
{
int i = ;
for(; i < vfd->counter; i++){
if(vfd->fd[i] == fd) return i;
} return -;
} vector_fd *create_vector_fd(void)
{
vector_fd *vfd = (vector_fd *)calloc(, sizeof(vector_fd));
assert(vfd != NULL); vfd->fd = (int *)calloc(, sizeof(int));
assert(vfd->fd != NULL);
vfd->counter = ;
vfd->max_counter = ; return vfd;
} void destroy_vector_fd(vector_fd *vfd)
{
assert(vfd != NULL);
free(vfd->fd);
free(vfd);
} int get_fd(vector_fd *vfd, int index)
{
assert(vfd != NULL);
if(index < || index > vfd->counter - ) return ; return vfd->fd[index];
} void remove_fd(vector_fd *vfd, int fd)
{
assert(vfd != NULL);
int index = indexof(vfd, fd);
if(index == -) return;
int i = index;
for(; i < vfd->counter - ; i++){
vfd->fd[i] = vfd->fd[i + ];
}
vfd->counter--;
} void add_fd(vector_fd *vfd, int fd)
{
assert(vfd != NULL);
encapacity(vfd);
vfd->fd[vfd->counter++] = fd;
}

vector_fd.h

 #ifndef __VECTOR_FD_H__
#define __VECTOR_FD_H__ typedef struct {
int *fd;
int counter;
int max_counter;
}vector_fd; extern vector_fd *create_vector_fd(void);
extern void destroy_vector_fd(vector_fd *);
extern int get_fd(vector_fd *, int index);
extern void remove_fd(vector_fd *, int fd);
extern void add_fd(vector_fd *, int fd); #endif

  编译成模块:gcc -o obj/vector_fd.o -Iinclude -c src/vector_fd.c

59.2.2 服务器端

  echo_tcp_server_fcntl.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 == ){
/** 客户端已经关闭连接 */
char info[] = "client closed";
write(STDOUT_FILENO, info, sizeof(info));
/** 从动态数组中删除对应的 fd */
remove_fd(vfd, fd);
/** 关闭对应客户端的 socket */
close(fd);
}
else if(size > ){
write(STDOUT_FILENO, buff, sizeof(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);
} void *th_fn(void *arg)
{
int i;
while(){
i = ;
/** 遍历动态数组中的 socket 描述符 */
for(; i < vfd->counter; i++){
do_service(get_fd(vfd, i));
}
} 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)启动的子线程负责遍历动态数组中 socket
* 描述符,并和对应的客户端进行双向通信(采用非阻塞方式读写)
*/
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); /** 将读写修改为非阻塞方式 */
int val;
fcntl(fd, F_GETFL, &val); ///< 获取原来的状态标志
val |= O_NONBLOCK;
fcntl(fd, F_SETFL, val); ///< 添加新的状态标志 /** 将返回的新的 socket 描述符加入到动态数组中 */
add_fd(vfd, fd); } return ;
}

  编译:

  gcc -o bin/echo_tcp_server_fcntl -Iinclude obj/vector_fd.o src/echo_tcp_server_fcntl.c -lpthread

59.2.3 客户端

  echo_tcp_client_fcntl.c

 #include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h> int main(int argc, char *argv[])
{
if(argc < ){
printf("usage: %s ip port\n", argv[]);
exit();
} /** 步骤1: 创建 socket */
int sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < ){
perror("socket error");
exit();
} /** 往 serveraddr 中填入 ip、port 和地址族类型(ipv4) */
struct sockaddr_in serveraddr;
memset(&serveraddr, , sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[]));
/** 将 ip 地址转换成网络字节序后填入 serveraddr 中 */
inet_pton(AF_INET, argv[], &serveraddr.sin_addr.s_addr); /**
* 步骤2: 客户端调用 connect 函数连接到服务器端
*/
if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < ){
perror("connect error");
exit();
} /** 步骤3: 调用 IO 函数(read/write)和服务器端进行双向通信 */
char buff[];
ssize_t size;
char *prompt = "==>";
while(){
memset(buff, , sizeof(buff));
write(STDOUT_FILENO, prompt, );
size = read(STDIN_FILENO, buff, sizeof(buff));
if(size < ) continue;
buff[size - ] = '\0'; if(write(sockfd, buff, sizeof(buff)) < ){
perror("write msg error");
continue;
}
else {
if(read(sockfd, buff, sizeof(buff)) < ){
perror("read msg error");
continue;
}
else {
printf("%s\n", buff);
}
}
} /** 步骤4: 关闭 socket */
close(sockfd); return ;
}

  编译:gcc -o bin/echo_tcp_client_fcntl src/echo_tcp_client_fcntl.c

59.2.4 测试运行

  

五十九、linux 编程—— I/O 多路复用 fcntl的更多相关文章

  1. 六十、linux 编程—— I/O 多路复用 select

    60.1 介绍 60.2 例子 echo_tcp_server_select.c #include <netdb.h> #include <netinet/in.h> #inc ...

  2. 第三百五十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)介绍以及安装

    第三百五十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)介绍以及安装 elasticsearch(搜索引擎)介绍 ElasticSearch是一个基于 ...

  3. “全栈2019”Java第五十九章:抽象类与抽象方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. SpringBoot进阶教程(五十九)整合Codis

    上一篇博文<详解Codis安装与部署>中,详细介绍了codis的安装与部署,这篇文章主要介绍介绍springboot整合codis.如果之前看过<SpringBoot进阶教程(五十二 ...

  5. JAVA学习第五十九课 — 网络编程概述

    网络模型 OSI(Open System Interconnection)开放系统互连:參考模型 TCP/IP 网络通讯要素 IP地址 port号 传输协议 网络參考模型 七层OSI模型的基本概念要了 ...

  6. C#编程(五十九)----------集合的性能

    各种集合的性能 许多集合类提供了相同的功能,例如,SortedList类与SortedDictionary类的功能几乎完全相同.但是,其性能常常有很大的区别.SortedList集合使用的内存少,So ...

  7. Linux学习之CentOS(十九)------linux 下压缩与解压之 tar、gzip、bzip2、zip、rar

    将文件存储到归档文件中或者从归档文件中获取原始文件,以及为文件创建归档文件 tar [option] [modifiers] [file-list] 参数 file-list是tar进行归档和提取的文 ...

  8. 五十三、linux 编程——TCP 编程基本介绍

    53.1 socket 套接字 53.1.1 介绍 Socket(套接字)是一种通讯机制,它包含一整套的调用接口和数据结构的定义,它给应用进程提供了使用如 TCP/UDP 灯网络协议进行网络通讯的手段 ...

  9. Linux学习之十九-Linux磁盘管理

    Linux磁盘管理 1.相关知识 磁盘,是计算机硬件中不可或缺的部分磁盘,是计算机的外部存储器中类似磁带的装置,将圆形的磁性盘片装在一个方的密封盒子里,这样做的目的是为了防止磁盘表面划伤,导致数据丢失 ...

随机推荐

  1. Android串口开发

    参考资料: https://www.jianshu.com/p/9249ed03e745 GitHUb地址: https://github.com/AIlll/AndroidSerialPort An ...

  2. name 'reload' is not defined解决方法

    今天在学习scrapy的时候,在网上找了一段代码,运行出了一点问题. 命令行报错: name 'reload' is not defined 原因是,python版本的问题 原代码如下: import ...

  3. Jsp的基本知识

    jsp页面的基本组成部分:指令,表达式,小脚本,声明,注释,静态内容. 指令元素有三种: 1.page:eg <%@ page 属性名="属性值" 属性名="属性值 ...

  4. 解决CSDN需要登录才能看全文

    本来今天学习遇到一些问题,在网上翻着博客,突然在csdn里就提示要登录才能看全文. 看了下页面源码博客内容已经拿到本地了,只是加了一层罩,也是挺无语的,暂时先用这种方法解决吧: (function() ...

  5. c++ primer plus 第二章 \n与endl在输出上的区别

        在书上看到如下一段话:     一个差别是,endl确保程序继续运行前刷新输出(将其立即显示在屏幕上):而使用"\n"不能提供这样的保证,这意味着在有些系统中,有时可能在您 ...

  6. 关于当前Web前端技术的一些感悟和笔记

    最近这些年,随着前端应用技术突飞猛进,产生了很多新的前端框架,当然也引入了数不胜数的前端技术概念,前端不在是早期Web Form的拖拉处理方式,也不再是Ajax+HTML那么简单,随着前端技术的发展, ...

  7. maven笔记学习

    一.修改setting.xml文件中的镜像 在导入他人项目或者在导入项目时,我们会出现在项目中不能识别maven配置的库文件的情况那么我们可以重新下载本地库, 首先我们可以修改我们安装的maven环境 ...

  8. CRM公海自动回收规则

    企微云CRM操作指南 – 道一云|企微https://wbg.do1.com.cn/xueyuan/2568.html 销售云 - 美洽 - 连接客户,亲密无间https://meiqia.com/s ...

  9. Linux centos ansible

    创建m01.backup.nfs.web01.web02 m01(172.16.1.61).backup(172.16.1.41).nfs(172.16.1.31).web01(172.16.1.7) ...

  10. elasticsearch补全功能之只补全筛选后的部分数据context suggester

    官方文档https://www.elastic.co/guide/en/elasticsearch/reference/5.0/suggester-context.html 下面所有演示基于elast ...