五十九、linux 编程—— I/O 多路复用 fcntl
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的更多相关文章
- 六十、linux 编程—— I/O 多路复用 select
60.1 介绍 60.2 例子 echo_tcp_server_select.c #include <netdb.h> #include <netinet/in.h> #inc ...
- 第三百五十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)介绍以及安装
第三百五十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)介绍以及安装 elasticsearch(搜索引擎)介绍 ElasticSearch是一个基于 ...
- “全栈2019”Java第五十九章:抽象类与抽象方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- SpringBoot进阶教程(五十九)整合Codis
上一篇博文<详解Codis安装与部署>中,详细介绍了codis的安装与部署,这篇文章主要介绍介绍springboot整合codis.如果之前看过<SpringBoot进阶教程(五十二 ...
- JAVA学习第五十九课 — 网络编程概述
网络模型 OSI(Open System Interconnection)开放系统互连:參考模型 TCP/IP 网络通讯要素 IP地址 port号 传输协议 网络參考模型 七层OSI模型的基本概念要了 ...
- C#编程(五十九)----------集合的性能
各种集合的性能 许多集合类提供了相同的功能,例如,SortedList类与SortedDictionary类的功能几乎完全相同.但是,其性能常常有很大的区别.SortedList集合使用的内存少,So ...
- Linux学习之CentOS(十九)------linux 下压缩与解压之 tar、gzip、bzip2、zip、rar
将文件存储到归档文件中或者从归档文件中获取原始文件,以及为文件创建归档文件 tar [option] [modifiers] [file-list] 参数 file-list是tar进行归档和提取的文 ...
- 五十三、linux 编程——TCP 编程基本介绍
53.1 socket 套接字 53.1.1 介绍 Socket(套接字)是一种通讯机制,它包含一整套的调用接口和数据结构的定义,它给应用进程提供了使用如 TCP/UDP 灯网络协议进行网络通讯的手段 ...
- Linux学习之十九-Linux磁盘管理
Linux磁盘管理 1.相关知识 磁盘,是计算机硬件中不可或缺的部分磁盘,是计算机的外部存储器中类似磁带的装置,将圆形的磁性盘片装在一个方的密封盒子里,这样做的目的是为了防止磁盘表面划伤,导致数据丢失 ...
随机推荐
- 做优化的数据库工程师请参考!CynosDB的计算层设计优化揭秘
本文由云+社区发表 本文作者:孙旭,腾讯数据库开发工程师,9年数据库内核开发经验:熟悉数据库查询处理,并发控制,日志以及存储系统:熟悉PostgreSQL(Greenplum,PGXC等).Terad ...
- July 11th, 2018. Wednesday, Week 28th.
It is during our darkest moments that we must focus to see the light. 越是在艰难的时候就越要着眼于光明. From Aristol ...
- Install Docker Compose
https://docs.docker.com/compose/install/ sudo curl -L "https://github.com/docker/compose/releas ...
- completable 用法
CompletableFuture 前面我们使用过jdk5 提出future的用法,但是在获取结果上并不是那么友好 在Java8中,CompletableFuture提供了非常强大的Future的扩展 ...
- 【刷题】【LeetCode】007-整数反转-easy
[刷题][LeetCode]总 用动画的形式呈现解LeetCode题目的思路 参考链接-空 007-整数反转 方法: 弹出和推入数字 & 溢出前进行检查 思路: 我们可以一次构建反转整数的一位 ...
- 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 ...
- FJUTOJ-周赛2016-12-16
注:fjutoj基本每周都有一次周赛,欢迎大家都来参加! 网址:http://59.77.139.92/index.jsp A题:来源 POJ 2773 题意:给两个数m和k,问第k 个和m 互素的数 ...
- 微信支付之01------获取订单微信支付二维码的接口------Java实现
[ 前言:以前写过一个获取微信二维码支付的接口,发现最近公司新开的项目会经常用到,现在我又翻出代码看了一遍,觉得还是把整个代码流程记下来的好 ] 借鉴博客: 他这篇博客写得不错,挺全的:https:/ ...
- 如何实现Echart不刷新页面,多语言切换下的地图数据重新加载,api请求数据加载,soketed数据实时加载
可视化项目中经常用到ecahrt,各种异步加载,连接socket,多语言切换等问题,现在汇总一下: Ecahrt初始化,全局统一init,可以初始化为0,等待后续数据操作 1.如果是api重新请求,数 ...
- Activiti6-TaskService(学习笔记)重要
任务管理服务: 可以看出来,TaskService操作对象,主要针对于UserTask, 对于业务方来说,最重要的就是用户任务,可以对用户任务进行增删改查的管理.可以对相关流程的控制.也可以设置一些用 ...