当前位置: 首页 > news >正文

20242817-李臻-课下测试:网络编程高级I/O(AI)

20242817-李臻-课下测试:网络编程高级I/O(AI)

一、实验任务

  1. 在 Ubuntu 或 openEuler 中完成任务(推荐openEuler)
  2. 借助AI研究Socket 阻塞和非阻塞方式,给出非阻塞Socket的例子,编译与运行过程(4分)
  3. 借助AI研究Socket与多路复用,给出相关的例子,编译与运行过程(9分)
  4. 提交代码链接(github 或者 gitee)和 git log 截图(1分)

二、实验过程

任务一:Socket阻塞和非阻塞

Socket 阻塞与非阻塞方式的分析

在网络编程中,Socket支持不同的I/O模式,其中阻塞模式和非阻塞模式是最常用的两种模式。理解这两种模式的区别有助于选择合适的方式。

阻塞模式(Blocking Mode)
  • 特点
    • 连接、读、写操作会阻塞,直到操作完成。
  • 优点
    • 简单易懂,代码直观。
  • 缺点
    • 性能问题,效率低下,不适合多连接处理。
非阻塞模式(Non-Blocking Mode)
  • 特点
    • 操作不阻塞,立即返回,若操作未完成返回错误码。
  • 优点
    • 高效处理多个连接,灵活性高,节省资源。
  • 缺点
    • 编程复杂,需要处理异常,可能需要多线程/多进程。
阻塞模式与非阻塞模式的对比
特性阻塞模式非阻塞模式
默认行为等待操作完成立即返回,可能返回错误码
I/O 操作阻塞程序不阻塞,返回错误码
适用场景简单应用,单一连接高并发,多连接
程序复杂度简单,直观复杂,需要事件轮询
性能可能浪费资源提高并发能力,节省资源
典型错误处理无需处理特定错误需要处理EAGAIN或EWOULDBLOCK
多线程/多进程支持不需要经常需要
何时选择阻塞模式与非阻塞模式?
  • 阻塞模式适合

    • 单线程或低并发应用。
    • 网络请求较少,对延迟要求不高的场景。
  • 非阻塞模式适合

    • 高并发场景,如Web服务器。
    • 需要高效事件驱动处理多个连接时。

任务二:非阻塞Socket实践

在非阻塞模式下,系统调用(如 recv()、send())不会导致进程阻塞等待数据。如果没有数据可读或可写,函数会立即返回,并给出一个特定的错误码(例如,EAGAIN 或 EWOULDBLOCK)。

nonblock_socket.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>#define SERVER_PORT 8080
#define SERVER_IP "127.0.0.1"int main() {// 创建一个 Socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("socket");exit(EXIT_FAILURE);}// 设置非阻塞模式int flags = fcntl(sockfd, F_GETFL, 0);if (flags == -1) {perror("fcntl F_GETFL");close(sockfd);exit(EXIT_FAILURE);}flags |= O_NONBLOCK;  // 设置非阻塞if (fcntl(sockfd, F_SETFL, flags) == -1) {perror("fcntl F_SETFL");close(sockfd);exit(EXIT_FAILURE);}// 设置服务器地址struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);// 尝试连接服务器int connect_status = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));if (connect_status == -1 && errno != EINPROGRESS) {perror("connect");close(sockfd);exit(EXIT_FAILURE);}printf("Socket is now in non-blocking mode. Trying to connect...\n");// 等待连接完成fd_set write_fds;FD_ZERO(&write_fds);FD_SET(sockfd, &write_fds);struct timeval timeout;timeout.tv_sec = 5;  // 设置超时时间为 5 秒timeout.tv_usec = 0;int select_result = select(sockfd + 1, NULL, &write_fds, NULL, &timeout);if (select_result == -1) {perror("select");close(sockfd);exit(EXIT_FAILURE);} else if (select_result == 0) {printf("Connection timeout.\n");close(sockfd);exit(EXIT_FAILURE);} else if (FD_ISSET(sockfd, &write_fds)) {printf("Connection established.\n");}// 在非阻塞模式下进行读写操作char send_buffer[] = "Hello, server!";int bytes_sent = send(sockfd, send_buffer, strlen(send_buffer), 0);if (bytes_sent == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {perror("send");close(sockfd);exit(EXIT_FAILURE);}// 关闭连接close(sockfd);return 0;
}

运行程序前,确保有一个服务器在 127.0.0.1:8080 上监听。可以使用如下命令启动一个简单的服务器

python3 -m http.server 8080

编译运行刚才的程序

./non_blocking_socket

在这里插入图片描述
返回服务器查看结果
在这里插入图片描述

  • 非阻塞模式设置: 通过 fcntl() 函数来修改 Socket 文件描述符,使其工作在非阻塞模式。
  • select() 系统调用: 用来检查 Socket 是否可以写入(连接是否完成)。

任务三:Socket与多路复用


Socket与多路复用分析

多路复用是网络编程中提高效率的关键技术之一,它允许单个线程或进程同时处理多个Socket连接。以下是几种常见的多路复用技术及其分析。

多路复用技术
1. select()
  • 特点
    • 检查多个文件描述符(包括Socket),看它们是否有活动的I/O。
  • 优点
    • 实现简单,跨平台支持。
  • 缺点
    • 可监控的文件描述符数量有限(FD_SETSIZE)。
    • 每次调用都需要复制文件描述符集合,效率较低。
2. poll()
  • 特点
    • 类似于select,但不使用位图,而是使用链表维护文件描述符。
  • 优点
    • 没有文件描述符数量限制。
  • 缺点
    • 同样在大量文件描述符时效率较低。
3. epoll()
  • 特点
    • Linux特有的高效多路复用接口,只监控活跃的文件描述符。
  • 优点
    • 高效,支持大量文件描述符。
    • 仅在状态改变时通知,减少不必要的系统调用。
  • 缺点
    • 仅限Linux平台。
多路复用技术对比
特性select()poll()epoll()
平台支持跨平台跨平台Linux专用
文件描述符限制有(FD_SETSIZE)
效率低(大量描述符时)低(大量描述符时)
实现复杂度中等中等
适用场景少量文件描述符大量文件描述符高效处理大量连接
何时选择多路复用技术?
  • select()适合

    • 跨平台应用,文件描述符数量较少的场景。
  • poll()适合

    • 需要无文件描述符数量限制的场景。
  • epoll()适合

    • Linux平台,需要高效处理大量并发连接的场景。

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket, client_sockets[MAX_CLIENTS] = {0};struct sockaddr_in address;int addrlen = sizeof(address);fd_set readfds;char buffer[BUFFER_SIZE] = {0};// 1. 创建TCP套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 2. 绑定地址和端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 3. 开始监听if (listen(server_fd, 3) < 0) {perror("listen failed");exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 4. 主循环处理连接while (1) {FD_ZERO(&readfds);FD_SET(server_fd, &readfds);int max_fd = server_fd;// 将客户端套接字加入监控集合for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] > 0) {FD_SET(client_sockets[i], &readfds);if (client_sockets[i] > max_fd)max_fd = client_sockets[i];}}// 5. 使用select监控可读事件if (select(max_fd + 1, &readfds, NULL, NULL, NULL) < 0) {perror("select error");exit(EXIT_FAILURE);}// 6. 处理新连接if (FD_ISSET(server_fd, &readfds)) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept error");continue;}// 将新套接字加入客户端数组for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] == 0) {client_sockets[i] = new_socket;printf("New connection: socket fd %d\n", new_socket);break;}}}// 7. 处理客户端数据for (int i = 0; i < MAX_CLIENTS; i++) {int client_fd = client_sockets[i];if (client_fd > 0 && FD_ISSET(client_fd, &readfds)) {int valread = read(client_fd, buffer, BUFFER_SIZE);if (valread == 0) {  // 连接关闭getpeername(client_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);printf("Client disconnected\n");close(client_fd);client_sockets[i] = 0;} else {  // 回显数据buffer[valread] = '\0';printf("Received: %s\n", buffer);send(client_fd, buffer, valread, 0);}}}}return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sock = 0;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("Socket creation error");exit(EXIT_FAILURE);}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("Invalid address");exit(EXIT_FAILURE);}if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("Connection Failed");exit(EXIT_FAILURE);}while (1) {printf("Enter message: ");fgets(buffer, BUFFER_SIZE, stdin);send(sock, buffer, strlen(buffer), 0);read(sock, buffer, BUFFER_SIZE);printf("Server response: %s\n", buffer);}return 0;
}
编译运行

测试流程

  1. 客户端输入消息后,服务端会显示接收内容并返回响应
  2. 可同时打开多个客户端终端进行并发测试
  3. 输入Ctrl+C关闭连接

运行服务器程序
在这里插入图片描述
运行客户端1
在这里插入图片描述
运行客户端2

在这里插入图片描述

运行发现服务器能够接收到客户发送的数据。

任务四:Gitee托管

代码仓库链接:https://gitee.com/li-zhen1215/homework/tree/master/Week9

在这里插入图片描述
在这里插入图片描述

http://www.xdnf.cn/news/147241.html

相关文章:

  • AWX配置持久化 Playbook 目录
  • 【Linux】什么是完全限定域名
  • 基于ssm的音乐播放平台管理系统(源码+数据库)
  • ✨ Apifox:这玩意儿是接口界的“瑞士军刀”吧![特殊字符][特殊字符]
  • AI超级智能体项目教程(二)---后端项目初始化(设计knif4j接口文档的使用)
  • 操作指南:vLLM 部署开源大语言模型(LLM)
  • 居然智家启动“2025北居奥森健跑活动“以运动诠释企业健康共生理念
  • MySQL 表结构及日志文件详解
  • 美颜SDK动态贴纸实战教程:从选型、开发到上线的完整流程
  • Kafka与Spark-Streaming:大数据处理的黄金搭档
  • PPO 强化学习机械臂 IK 训练过程可视化利器 Tensorboard
  • netcore8.0项目发布到centos,利用nginx反向代理(宝塔面板篇)
  • C++初阶----模板初阶
  • C语言编程--17.有效的括号
  • 氢气泄漏应急预案应包括哪些内容?
  • 【资料推荐】LVDS Owner’s Manual
  • contenthash 持久化缓存
  • MODBUS转ProfiNet边缘计算网关驱动霍尼韦尔HPT温湿度仪表的动态控制闭环方案
  • Shell、Bash 执行方式及./ 执行对比详解
  • 网络通信的字节序
  • Postman-win64-7.2.2 安装教程(Windows 64位详细步骤)
  • API性能瓶颈分析与优化方法
  • QQ音乐安卓版歌曲版权覆盖范围与曲库完整度评测
  • Kubernet查找pods不断重启原因
  • 【Nova UI】十、打造组件库第一个组件-图标组件(下):从.svg 到 SVG Vue 组件的高效蜕变✨
  • gerbera文件转PCB文件-Altium Designer
  • GitHub 趋势日报 (2025年04月24日)
  • 赛灵思 XCKU115-2FLVB2104I Xilinx Kintex UltraScale FPGA
  • Parasoft C++Test软件单元测试_对函数打桩的详细介绍
  • AKM旭化成微电子全新推出能量收集IC“AP4413系列”