16 IO分离的其他说明

本文介绍了TCP编程中IO流分离的两种方法:通过fork区分文件描述符和使用fdopen创建FILE指针。讨论了流分离带来的好处,如降低实现难度和提高缓冲性能,并解决了因关闭任一流而导致连接终止的问题。提供了复制文件描述符实现半关闭状态的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. IO流分离方式:

a. 第十章TCP IO routine分离,通过fork文件描述符区分输入输出,虽然文件描述符不会根据输入、输出进行区分,但是分开了两个文件描述符的用途;

b. 第十五章 调用fdopen创建FILE指针,分离输入工具和输出工具。


2. 分离流的好处:

//第十章

a. 通过分开输入过程和输出过程降低实现难度;

b. 与输入无关的输出操作可以提高速度;

//第十五章

a. 将FILE指针按读模式、写模式加以区分;

b. 可以通过区分读写模式降低难度;

c. 通过区分IO缓冲提高缓冲性能;

3. 第十五章流分离的问题

读模式FILE指针、写模式FILE指针都是基于同一个文件描述符创建的,fclose关闭任意一个,都将关闭文件描述符,套接字终止。


4. 解决如上问题,只需在创建FILE指针前复制文件描述符。


复制文件描述符的方法dup/dup2:

#include <unistd.h>

int dup(int fildes)
int dup2(int fildes, int fildes2)

成功时返回复制的文件描述符,失败返回-1

fildes:需要复制的文件描述符
filders2:明确指定的文件描述符整数值


5. 复制文件描述符进行半关闭示例

server.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024

int main(int argc, char * argv[]){
	int serv_sock,clnt_sock;
	FILE *readfp;
	FILE *writefp;
	struct sockaddr_in serv_adr,clnt_adr;
	socklen_t clnt_adr_sz;
	char buf[BUF_SIZE];

	serv_sock = socket(PF_INET,SOCK_STREAM,0);

	memset(&serv_adr,0,sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port = htons(atoi(argv[1]));

	bind(serv_sock,(struct sockaddr *)&serv_adr,sizeof(serv_adr));

	listen(serv_sock,5);

	clnt_adr_sz = sizeof(clnt_adr);

	clnt_sock = accept(serv_sock,(struct sockaddr *)&clnt_adr,&clnt_adr_sz);

	readfp = fdopen(clnt_sock,"r");
	writefp = fdopen(dup(clnt_sock),"w");

	fputs("from server: Hi~ client? \n",writefp);
	fputs("I love all the world! \n",writefp);
	fflush(writefp);

	shutdown(fileno(writefp),SHUT_WR);

	fclose(writefp);
	fgets(buf,sizeof(buf),readfp);
	fputs(buf,stdout);

	fclose(readfp);

	return 0;
}

执行结果:

无论复制出多少文件描述符,均应调用shutdown函数发送EOF并进入半关闭状态。

alex@alex-virtual-machine:/extra/tcpip/16$ ./client 127.0.0.1 9190
from server: Hi~ client?
I love all the world!

client.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024

int main(int argc, char * argv[]){
	int sock;
	FILE *readfp;
	FILE *writefp;
	struct sockaddr_in serv_adr;
	socklen_t clnt_adr_sz;
	char buf[BUF_SIZE];

	sock = socket(PF_INET,SOCK_STREAM,0);

	memset(&serv_adr,0,sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_adr.sin_port = htons(atoi(argv[2]));

	connect(sock,(struct sockaddr *)&serv_adr,sizeof(serv_adr));

	readfp = fdopen(sock,"r");
	writefp = fdopen(sock,"w");

	while(1){
	    if(fgets(buf,sizeof(buf),readfp) == NULL){
		break;
	    }

	    fputs(buf,stdout);
    	    fflush(stdout);
	}

	fputs("from client: thank you\n",writefp);
	fflush(writefp);
	fclose(readfp);
	fclose(writefp);

	return 0;
}

执行结果:

alex@alex-virtual-machine:/extra/tcpip/16$ ./server 9190
from client: thank you

### 若依框架前后端分离时集成 `socket.io-client` 的方法 #### 背景介绍 若依(RuoYi)是一款基于 Spring Boot 和 Vue.js 开发的企业级管理后台系统,支持前后端分离架构。为了实现前端与后端之间的实时通信功能,可以通过引入 WebSocket 技术来完成数据的双向传输。而 `socket.io-client` 是一种流行的 WebSocket 实现库,能够简化客户端和服务端之间复杂的连接逻辑。 以下是关于如何在若依框架中集成并使用 `socket.io-client` 的具体说明: --- #### 后端配置 (Spring Boot) 1. **依赖引入** 在项目的 Maven 或 Gradle 中添加 `spring-boot-starter-websocket` 及 `netty-socketio` 作为基础依赖项。 ```xml <!-- pom.xml --> <dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>netty-socketio</artifactId> <version>1.7.20</version> </dependency> ``` 2. **Socket.IO Server 初始化** 创建一个自定义类用于初始化 Socket.IO server 并绑定到指定端口上运行。 ```java @Configuration public class SocketIOConfig { private static final Logger logger = LoggerFactory.getLogger(SocketIOConfig.class); @Bean(initMethod="start", destroyMethod="stop") public SocketIOServer socketIOServer() { Configuration config = new Configuration(); config.setHostname("localhost"); config.setPort(8081); // 设置监听端口号 SocketIOServer server = new SocketIOServer(config); server.addEventListener("chat message", String.class, args -> { String data = (String)args.getSingleArg(); server.getBroadcastOperations().sendEvent("news", data); }); return server; } } ``` 3. **跨域设置** 如果前後端分離部署於不同域名下,则需處理 CORS 問題,在服務器啟動時進行相關設置。 ```java @Component public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request= (HttpServletRequest)req; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With"); if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(req, res); } } @Override public void init(FilterConfig filterConfig) {} @Override public void destroy() {} } ``` --- #### 前端配置 (Vue.js) 1. **安装依赖包** 安裝 `socket.io-client` 到項目當中以便於前端能夠與後端建立連接。 ```bash npm install --save socket.io-client ``` 2. **创建全局实例对象** 将 `socket.io-client` 连接封装为单例模式供整个应用调用。 ```javascript import { io } from 'socket.io-client'; const SOCKET_SERVER_URL = process.env.VUE_APP_SOCKET_IO_SERVER || 'http://localhost:8081'; let socketInstance = null; export default function getSocketIoClient() { if (!socketInstance) { socketInstance = io(SOCKET_SERVER_URL, { transports: ['websocket'], // 强制只使用 websocket 协议 reconnectionAttempts: Infinity, timeout: 10000, autoConnect: true }); socketInstance.on('connect', () => console.log('[SOCKET] Connected')); socketInstance.on('disconnect', reason => console.warn(`[SOCKET] Disconnected due to ${reason}`)); socketInstance.on('error', error => console.error(`[SOCKET ERROR]:`, error)); window.addEventListener('beforeunload', () => { if(socketInstance && socketInstance.connected){ socketInstance.disconnect(); } }) } return socketInstance; } ``` 3. **组件内使用示例** 下面展示了一个简单的聊天室场景下的消息发送和接收处理流程。 ```vue <template> <div id="app"> <input v-model="messageText"/> <button @click="sendMessage">Send Message</button> <ul> <li v-for="(msg,index) in messages" :key="index">{{ msg }}</li> </ul> </div> </template> <script> import getSocketIoClient from '@/utils/socket-io-client'; export default { name: 'App', data(){ return{ messageText:'', messages:[] }; }, mounted(){ this.initSocketListener(); }, methods:{ sendMessage(){ const textValue=this.messageText.trim(); if(textValue.length===0)return ; try{ const s=getSocketIoClient(); s.emit('chat message',textValue,[^2]); this.messages.push(`You said:${textValue}`); this.messageText=''; }catch(e){ alert('Failed to send message'); } }, initSocketListener(){ const s=getSocketIoClient(); s.on('news',(data)=>{ this.messages.push(data); },); } } } </script> ``` --- #### iOS Native App 集成 (Objective-C/C++) 对于移动端开发而言,如果需要通过原生代码接入 WebRTC 功能并与服务端保持同步状态更新的话,可以考虑采用 C++ 版本的 `socket.io-client-cpp` 库来进行交互操作。 ##### 步骤概述: 1. 添加头文件路径至 XCode 工程; 2. 导入必要的模块声明; ```objc #import <socket.io-client-cpp/sio_client.h> ``` 3. 编写实际业务逻辑代码片段如下所示: ```cpp #include <iostream> #include "../include/sio_client.h" using namespace std; using namespace sio; class my_listener : public client::listener { public: virtual void on_connect() override { cout << "[C++] Connected!" << endl; } virtual void on_event(const string& event_name, shared_ptr<event_args> ev, bool is_ack, int ack_id) override { cout << "[C++] Received Event:" << event_name << ", Data:" << ev->get_string() << endl; if(is_ack){ vector<string> resp={"ACK"}; cli->socket()->acknowledge(ack_id,&resp); } } virtual void on_disconnect() override { cout << "[C++] Disconnected." << endl; } }; int main(int argc,char* argv[]) { listener* l=new my_listener(); client cli(l); cli.connect("ws://localhost:8081",[l](const connection_response& r)->void{ (*l).on_connect(); }); while(true){sleep(1);} } ``` --- ### 总结 上述内容涵盖了从环境搭建、基本概念理解以及实践案例等多个方面介绍了如何利用 `socket.io-client` 来增强若依系统的实时通讯能力。希望这些资料能帮助开发者快速掌握相关技能!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值