通俗易懂说多路复用(4)fcntl


多路复用系列:
通俗易懂说多路复用(1)select
https://blog.csdn.net/lqy971966/article/details/89173936
通俗易懂说多路复用(2)epoll
https://blog.csdn.net/lqy971966/article/details/89217648
通俗易懂说多路复用(3)eventfd 事件通知
https://blog.csdn.net/lqy971966/article/details/104751751
通俗易懂说多路复用(4)fcntl
https://blog.csdn.net/lqy971966/article/details/105390106

1. fcntl()

1.1 功能:

根据文件描述词来操作文件的特性。

1.2 fcntl 的三个接口,三种操作

#include <unistd.h>
#include <fcntl.h> 
int fcntl(int fd, int cmd); 
int fcntl(int fd, int cmd, long arg); 
int fcntl(int fd, int cmd, struct flock *lock);

1.3 返回值

fcntl()的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD , F_GETFD , F_GETFL以及F_GETOWN。
F_DUPFD 返回新的文件描述符
F_GETFD 返回相应标志
F_GETFL , F_GETOWN 返回一个正的进程ID或负的进程组ID

1.4 cntl函数有5种功能:

1.4.1. 复制一个现有的描述符(cmd=F_DUPFD).

1.4.2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 及代码使用

F_GETFD 取得与文件描述符fd联合的close-on-exec标志,类似FD_CLOEXEC。如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg 被忽略)
F_SETFD 设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定,应当了解很多现存的涉及文件描述符标志的程序并不使用常数 FD_CLOEXEC,而是将此标志设置为0(系统默认,在exec时不关闭)或1(在exec时关闭)

1.4.3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

1.4.4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).

1.4.5. 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).

F_GETLK 通过第三个参数arg(一个指向flock的结构体)取得第一个阻塞lock description指向的锁。取得的信息将覆盖传到fcntl()的flock结构的信息。如果没有发现能够阻止本次锁(flock)生成的锁,这个结构将不被改变,除非锁的类型被设置成F_UNLCK
F_SETLK 按照指向结构体flock的指针的第三个参数arg所描述的锁的信息设置或者清除一个文件的segment锁。F_SETLK被用来实现共享(或读)锁(F_RDLCK)或独占(写)锁(F_WRLCK),同样可以去掉这两种锁(F_UNLCK)。如果共享锁或独占锁不能被设置,fcntl()将立即返回EAGAIN

结构体flock的指针

struct flcok 
{ 
	short int l_type; /* 锁定的状态*/
	//以下的三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET, l_start=0, l_len=0
	short int l_whence; /*决定l_start位置*/ 
	off_t l_start; /*锁定区域的开头位置*/ 
	off_t l_len; /*锁定区域的大小*/
	pid_t l_pid; /*锁定动作的进程*/ 
};
  1. l_type 有三种状态:
    F_RDLCK 建立一个供读取用的锁定
    F_WRLCK 建立一个供写入用的锁定
    F_UNLCK 删除之前建立的锁定

  2. l_whence 也有三种方式:
    SEEK_SET 以文件开头为锁定的起始位置
    SEEK_CUR 以目前文件读写位置为锁定的起始位置
    SEEK_END 以文件结尾为锁定的起始位置

fcntl文件锁有两种类型:建议性锁和强制性锁
系统默认fcntl都是建议性锁,强制性锁是非POSIX标准的。

2. 代码例子

2.1 代码例子 F_GETFD 和 F_SETFD

	int iEpfd = epoll_create(1);
	if(0>iEpfd)
	{ return error;}
	iFlags = fcntl(iEpfd,F_GETFD);
	if(iFlags >= 0)
	{
		iFlags |= FD_CLOEXEC; 	// 这个句柄我在fork子进程后执行exec时就关闭
		fcntl(iEpfd,F_SETFD,iFlags);
	}

2.2 解释

  1. 背景:
    fork子进程时,子进程以写时复制(COW,Copy-On-Write)
    方式获得父进程的数据空间、堆和栈副本,这其中也包括文件描述符

  2. 问题:
    当子进程调用exec执行另一个程序,此时会用全新的程序替换子进程的正文,数据,堆和栈等。
    此时保存文件描述符的变量当然也不存在了,我们就无法关闭无用的文件描述符了。
    不能关闭子进程继承过来的父进程的文件描述符

  3. 解决:
    通常我们会fork子进程后在子进程中直接执行close关掉无用的文件描述符,然后再执行exec

    但是在复杂系统中,有时我们fork子进程时已经不知道打开了多少个文件描述符(包括socket句柄等)
    这此时进行逐一清理确实有很大难度。

  4. 结果:
    在fork子进程前打开某个文件句柄时就指定好:“这个句柄我在fork子进程后执行exec时就关闭”。
    其实时有这样的方法的:即所谓的 close-on-exec
    close-on-exec 的实现只需要调用系统的fcntl就能实现,很简单几句代码就能实现。
    这样,当fork子进程后,仍然可以使用fd。但执行exec后系统就会字段关闭子进程中的fd了。

参考:

https://www.cnblogs.com/embedded-linux/p/6753617.html
https://www.cnblogs.com/alantu2018/p/8492206.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
socket编程中的多路复用是指通过一种机制,使一个进程可以监视多个文件描述符,一旦某个文件描述符就绪(一般是读写操作准备就绪),能够通知程序进行相应的读写操作。在Linux中,常用的多路复用机制有select、poll和epoll。其中,select是最古老的多路复用机制,poll是select的改进版,而epoll是最新、最高效的多路复用机制。多路复用机制可以大大提高程序的并发性能,使得程序可以同时处理多个客户端请求。 下面是一个简单的使用select实现多路复用的流程图和代码示例: 流程图: ``` 1. 创建socket并绑定端口 2. 将socket设置为非阻塞模式 3. 创建fd_set集合,并将socket加入集合 4. 进入循环,调用select函数,等待文件描述符就绪 5. 如果socket就绪,表示有新的客户端连接请求,调用accept函数接受连接 6. 如果其他文件描述符就绪,表示有客户端发送数据,调用recv函数接收数据并处理 7. 回到步骤4,继续等待文件描述符就绪 ``` 代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h> #define MAX_CLIENTS 10 #define BUFFER_SIZE 1024 int main(int argc, char *argv[]) { int server_fd, client_fd, max_fd, activity, i, valread, sd; struct sockaddr_in address; char buffer[BUFFER_SIZE] = {0}; fd_set readfds; // 创建socket并绑定端口 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(atoi(argv[1])); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } if (listen(server_fd, MAX_CLIENTS) < 0) { perror("listen failed"); exit(EXIT_FAILURE); } // 将socket设置为非阻塞模式 int flags = fcntl(server_fd, F_GETFL, 0); fcntl(server_fd, F_SETFL, flags | O_NONBLOCK); // 创建fd_set集合,并将socket加入集合 FD_ZERO(&readfds); FD_SET(server_fd, &readfds); max_fd = server_fd; // 进入循环,调用select函数,等待文件描述符就绪 while (1) { activity = select(max_fd + 1, &readfds, NULL, NULL, NULL); if (activity < 0) { perror("select error"); exit(EXIT_FAILURE); } // 如果socket就绪,表示有新的客户端连接请求,调用accept函数接受连接 if (FD_ISSET(server_fd, &readfds)) { if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("accept error"); exit(EXIT_FAILURE); } printf("New connection, socket fd is %d, ip is : %s, port : %d\n", client_fd, inet_ntoa(address.sin_addr), ntohs(address.sin_port)); // 将新的客户端socket加入集合 FD_SET(client_fd, &readfds); if (client_fd > max_fd) { max_fd = client_fd; } } // 如果其他文件描述符就绪,表示有客户端发送数据,调用recv函数接收数据并处理 for (i = server_fd + 1; i <= max_fd; i++) { sd = i; if (FD_ISSET(sd, &readfds)) { if ((valread = recv(sd, buffer, BUFFER_SIZE, 0)) == 0) { // 客户端关闭连接 printf("Client disconnected, socket fd is %d\n", sd); close(sd); FD_CLR(sd, &readfds); } else { // 处理客户端发送的数据 printf("Received message from client, socket fd is %d, message is %s\n", sd, buffer); memset(buffer, 0, BUFFER_SIZE); } } } } return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值