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. 做优化的数据库工程师请参考!CynosDB的计算层设计优化揭秘

    本文由云+社区发表 本文作者:孙旭,腾讯数据库开发工程师,9年数据库内核开发经验:熟悉数据库查询处理,并发控制,日志以及存储系统:熟悉PostgreSQL(Greenplum,PGXC等).Terad ...

  2. July 11th, 2018. Wednesday, Week 28th.

    It is during our darkest moments that we must focus to see the light. 越是在艰难的时候就越要着眼于光明. From Aristol ...

  3. Install Docker Compose

    https://docs.docker.com/compose/install/ sudo curl -L "https://github.com/docker/compose/releas ...

  4. completable 用法

    CompletableFuture 前面我们使用过jdk5 提出future的用法,但是在获取结果上并不是那么友好 在Java8中,CompletableFuture提供了非常强大的Future的扩展 ...

  5. 【刷题】【LeetCode】007-整数反转-easy

    [刷题][LeetCode]总 用动画的形式呈现解LeetCode题目的思路 参考链接-空 007-整数反转 方法: 弹出和推入数字 & 溢出前进行检查 思路: 我们可以一次构建反转整数的一位 ...

  6. Tomcat FAIL - Deploy Upload Failed, Exception: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (110960596) exceeds the confi

    https://maxrohde.com/2011/04/27/large-war-file-cannot-be-deployed-in-tomcat-7/ Go to the web.xml of ...

  7. FJUTOJ-周赛2016-12-16

    注:fjutoj基本每周都有一次周赛,欢迎大家都来参加! 网址:http://59.77.139.92/index.jsp A题:来源 POJ 2773 题意:给两个数m和k,问第k 个和m 互素的数 ...

  8. 微信支付之01------获取订单微信支付二维码的接口------Java实现

    [ 前言:以前写过一个获取微信二维码支付的接口,发现最近公司新开的项目会经常用到,现在我又翻出代码看了一遍,觉得还是把整个代码流程记下来的好 ] 借鉴博客: 他这篇博客写得不错,挺全的:https:/ ...

  9. 如何实现Echart不刷新页面,多语言切换下的地图数据重新加载,api请求数据加载,soketed数据实时加载

    可视化项目中经常用到ecahrt,各种异步加载,连接socket,多语言切换等问题,现在汇总一下: Ecahrt初始化,全局统一init,可以初始化为0,等待后续数据操作 1.如果是api重新请求,数 ...

  10. Activiti6-TaskService(学习笔记)重要

    任务管理服务: 可以看出来,TaskService操作对象,主要针对于UserTask, 对于业务方来说,最重要的就是用户任务,可以对用户任务进行增删改查的管理.可以对相关流程的控制.也可以设置一些用 ...