Justin知识库
type: channel
link: https://www.just1n.cn/
description: Working hard.
items: 2025年度总结
items: 告别重复劳动！记录一下VBA脚本
items: iPhone 传输文件
items: STM32利用GPIO复现查理复用
items: 插秧偈
items: Qt：QString 编码格式问题
items: 使用 Socket 建立 IPC 通信
items: 使用 Socket 建立 UDP 连接
items: 使用 Socket 建立多个 TCP 连接
items: Socket 编程

2025年度总结
type: item
date: 2026-01-29T16:11:00+08:00
description: “惆怅闲吟客，无成又一年”，提笔一刻，笔尖在纸上顿住，“2025” 这个数字几乎是不假思索地写出，竟需要 2026 年的一次笔误来提醒。这像极了这一年予我的全部感受：我总是慢了一拍，拙劣的文笔配不上激荡的想法。这份“迟来”的总结，恰恰是 2025 年最真实的注脚。 回顾年初，作为一位在 24 年告别校园，却在 25 年才踏入工作的迟到者，我的年度坐标无法用任何辉煌的成就来标注，它更像一张由错位感绘制而成的地图：理想的工作与现实的岗位之间，是机遇投下的长长影子与脚下目标的短暂泥泞；在庞大而冰冷的技术体系面前，属于个人的微小成就感，需要从石头里挤出水来。 这一年，与其说是“奋斗”，不如说是一场笨拙的折腾与静默的磨砺——在生活的浅水区，学习如何不被淹没，并试图划出第一道属于自己的、哪怕微不足道的水痕。 关于博客 先谈谈博客吧，我觉得它的状态，就是我这一年的缩影——一种废墟般的沉默。今年只留下 3 篇文字印记，平均每 122 日才添一文。相较于去年笔耕不辍的倾吐，笔墨渐疏，近乎蛰伏。不过也可以说是精锐化、特种作战。 此刻回看，或许并非纯粹的荒疏，实则构建了我个人生活的三重防御工事：对抗技术失忆的浪漫防线、解决现实麻烦的实用堡垒，以及从机械劳作中赎回时间的自动化壕沟。笔墨疏淡，是因为心神都用在给这套系统写运维手册了。 至于工作 目前的工作，则是这张错位地图上，面积最大却也最模糊的一块区域。说的好听一点称作水利工作者，而我更喜欢叫做“乡村鉴赏师”。感谢它赋予我的责任心、怜悯心，让我用双脚丈量了许多陌生村镇的名字。在一次次抵达与离开之间，得以让我快速的转变身份，逐渐懂得稳住就已是一种强大。 很难想象这一年不觉已走过湖北省的各个角落，去不了祖国的大西北，那就走走鄂西北！ 在黄冈，顶级领航；在咸宁，精准踩点；在十堰，半路潜逃… 不是在摸鱼就是在摸鱼的路上。 或许真正的“鉴赏”从来不在匆忙中完成。何为“流域”，何为“系统”？那些在路上耽搁的片刻、与村民随口聊起收成的午后、站在堤坝上突然袭来的寂静——它们或许未写入报告，却深深写进了我这一年的底色里。 对于生活 至于工作它不是枷锁，对于生活只是调味… 如果说工作是沿着河流行走，那么生活，便是在自己这片流域里，学会与暗礁共处，并为自己开辟新的航道。 面对皮炎 眼睛周边深受神经性皮炎的折磨，那段日子，生活被简化成一份严格的“操作手册” 漫谈游历 今年一共有两段路线，一次是精心策划的“北伐”，另一次则是工作间隙的“西游”。 "北伐" 这里写下了当时所感。 武汉➡️徐州➡️泰安➡️济南➡️青岛 此次共标记 106 处位置 打卡全国唯二的汉堡店 "西游" 宜昌+恩施，“摸鱼”实践新高度 归于此刻 按“感过祝”的方式一般总结写到这里都要进行概括、讨论得失以及下一年的打算吧，个人觉得都很虚，不如着手眼前，对当下的自己，是满足还是遗憾，是改变还是维持？ "不惊新岁换"，心安归自序。

告别重复劳动！记录一下VBA脚本
type: item
date: 2025-10-30T17:18:00+08:00
description: 这几天每天都在与Excel打交道，重复着相同工作表的拆分、数据导出，今天分享一下这几天工作中常用的VBA脚本，让我从重复劳动中彻底解放，现在每天都能准时摸鱼！ 一键拆分多个工作表保存 使用场景：一个包含多个工作表的工作簿，要求每个表单独保存为文件时 Sub SplitWorkbookToCurrentFolder() Dim ws As Worksheet Dim newWorkbook As Workbook Dim savePath As String Dim fileName As String Dim originalWorkbook As Workbook Set originalWorkbook = ThisWorkbook ' 获取当前工作簿所在的文件夹路径 If originalWorkbook.Path <> "" Then savePath = originalWorkbook.Path & "\" Else ' 如果工作簿未保存，使用桌面路径 savePath = CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\" MsgBox "当前工作簿尚未保存，文件将保存到桌面！", vbInformation End If Application.ScreenUpdating = False Application.DisplayAlerts = False On Error GoTo ErrorHandler For Each ws In originalWorkbook.Worksheets ' 复制工作表到新工作簿 ws.Copy Set newWorkbook = ActiveWorkbook ' 生成文件名：工作表名称 + 比对结果 fileName = savePath & ws.Name & "_比对结果.xlsx" ' 处理文件名重复的情况 If Dir(fileName) <> "" Then Dim counter As Integer counter = 1 Do While Dir(savePath & ws.Name & "_比对结果(" & counter & ").xlsx") <> "" counter = counter + 1 Loop fileName = savePath & ws.Name & "_比对结果(" & counter & ").xlsx" End If ' 保存文件 newWorkbook.SaveAs fileName, FileFormat:=xlOpenXMLWorkbook ' 关闭新工作簿 newWorkbook.Close SaveChanges:=False ' 释放对象 Set newWorkbook = Nothing Next ws Application.ScreenUpdating = True Application.DisplayAlerts = True MsgBox "所有工作表已成功保存为独立文件！" & vbNewLine & _ "保存路径：" & savePath, vbInformation, "完成" Exit Sub ErrorHandler: Application.ScreenUpdating = True Application.DisplayAlerts = True MsgBox "保存过程中出现错误：" & Err.Description, vbCritical, "错误" End Sub 表格一键导出为图片 需要将工作薄里各个工作表里的表格数据以图片形式保存 Sub ExportAllSheetsToImages() Dim ws As Worksheet Dim rng As Range Dim cht As ChartObject Dim savePath As String Dim fileName As String Dim lastRow As Long ' 设置保存路径为当前文件夹 savePath = ThisWorkbook.Path & "\" If savePath = "\" Then MsgBox "请先保存工作簿！", vbExclamation Exit Sub End If Application.ScreenUpdating = False Application.CutCopyMode = False For Each ws In ThisWorkbook.Worksheets If ws.Visible = xlSheetVisible Then ' 动态确定数据范围（从B1开始） With …

iPhone 传输文件
type: item
date: 2025-10-11T15:45:00+08:00
description: 将 iPhone 上的文件传输到 Windows 电脑有多种方法，各有优劣。我采用的是共享方式，顺便来水一篇文章，言归正传，主要是不想再去下载各种软件安装，然后卸载，不如从根本解决问题。 前提条件 iPhone（iOS 13及以上） Windows电脑 确保两者在同一局域网下，不懂百度 操作步骤 1. 在 Windows 下共享文件夹 首先选择一个用来接收文件的文件夹，这里我是新建的一个文件夹名为My_iPhone 其次是右击属性，点击共享栏，选择共享 然后就可以看到设置栏，这里我添加了一个Everyone的权限级别设置为 “读取/写入”（这一步可以不用），然后点击底部的 “共享” 按钮。 共享完成期间可能会弹出专用公用网络，哪里亮了点哪里就行。 共享完成后可以在共享栏里看到共享的网络路径，格式类似为\\YOUR-PC-NAME\iPhoneFiles。 另外需要确认一下自己电脑开机登录用户名和密码，后续在iPhone端需要。 最后就是找到本机当前 IP 地址，连接 WIFI 后一般都是 DHCP 给的随机地址，得自己改成静态 IP 地址，偷懒就自行属性里面找到自己当前 IP 地址，我敲的命令感觉快一点。 2. 在 iPhone 上连接该文件夹 再提醒一遍是在同一局域网下，比如有的时候都连接了WIFI，但不在同一网络下，这是因为目前很多路由器安装时都设置了两个频段（2.4 or 5），还需注意。 如已在同一个网络下，只需打开 iPhone 上的 “文件” App，点击点击右上角的 “…” 三个点图标，然后选择 “连接服务器”。 在 “服务器” 地址栏中，输入正确的网络路径，即在Windows上最后确认的 IP 地址（例如：smb://192.168.1.32），smb 是一个网络协议名，它能被用于 Web 连接和客户端与服务器之间的信息沟通，常用于在计算机间共享文件、打印机、串口等，电脑上的网上邻居就是靠它实现。 输入完成后点击连接会出现连接身份选项，为了安全起见，采用注册用户，然后输入 Windows 电脑开机时的用户名和密码，没有就自己设一个即可，点击连接，成功就会弹到此窗口，可以看到创建的文件夹已被共享。 3. 文件传输 到这里基本就已经成功了，现在只需选择相册中要像传输的文件即可，步骤就不详细赘述了，大概就是选择要上传的文件后选择 分享 -> 存储到“文件”，然后选择这个被共享文件夹（没有就点击浏览->共享->选择文件夹）。 个人感觉速度还是很快的，比某云要便宜，比某盘要实在，比某app要...

STM32利用GPIO复现查理复用
type: item
date: 2025-06-05T21:52:00+08:00
description: 最近沉迷抖音，无意看到点灯大师的点灯操作，发现了一种有趣的LED控制技术——查理复用(Charlieplexing)。通过这种技术，可以用n个GPIO引脚控制n×(n-1)个LED。本文将详细记录我的学习过程、实现方案以及遇到的问题和解决方法。 查理复用原理 这里不赘述，就想更新一下😎 这里已经足够详细，简单来讲即将LED反并联连接。这里更加详细。 在本文中我们将利用STM32的GPIO引脚三态特性（输出高、输出低、高阻输入）来控制多个LED的技术。其核心思想是： 每个LED连接在两个不同的GPIO引脚之间 通过控制引脚的电平状态决定LED是否导通 任何时候只允许一个电流路径存在 硬件设计 电路原理图 以下电路连接方式以及真值表： 连接方式 Num GPIO LED1 PA0(+) → PA1(-) LED2 PA1(+) → PA0(-) LED3 PA0(+) → PA2(-) LED4 PA2(+) → PA0(-) LED5 PA1(+) → PA2(-) LED6 PA2(+) → PA1(-) 真值表 驱动代码部分 已模块封装，便于扩展更多LED。代码需在最后一行留空，否则Keil编译会有警告。 CharlieLED.h #ifndef __CHARLIELED_H #define __CHARLIELED_H #include "stm32f10x.h" // Device header typedef enum { LED_1, // PA0 + PA1 LED_2, // PA1 + PA0 LED_3, // PA0 + PA2 LED_4, // PA2 + PA0 LED_5, // PA1 + PA2 LED_6 // PA2 + PA1 } CharlieLED; void Set_CharlieLED(CharlieLED led, FunctionalState state); #endif CharlieLED.c #include "CharlieLED.h" // FunctionalState 是一个枚举类型，用于表示功能状态的开启或关闭 void Set_CharlieLED(CharlieLED led, FunctionalState state) { static const struct { uint16_t anode_pin; uint16_t cathode_pin; } led_map[6] = { {GPIO_Pin_0, GPIO_Pin_1}, // LED1 {GPIO_Pin_1, GPIO_Pin_0}, // LED2 {GPIO_Pin_0, GPIO_Pin_2}, // LED3 {GPIO_Pin_2, GPIO_Pin_0}, // LED4 {GPIO_Pin_1, GPIO_Pin_2}, // LED5 {GPIO_Pin_2, GPIO_Pin_1} // LED6 }; // 初始化 1. 先全部设为高阻态（安全隔离） RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 默认保持彻底高阻态 GPIO_Init(GPIOA, &GPIO_InitStructure); // 判断是否点亮 if (state == ENABLE) { // 2. 配置阳极（推挽输出高） GPIO_SetBits(GPIOA, led_map[led].anode_pin); // 高电平 GPIO_InitStructure.GPIO_Pin = led_map[led].anode_pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 3. 配置阴极（推挽输出低） GPIO_ResetBits(GPIOA, led_map[led].cathode_pin); // 低电平 GPIO_InitStructure.GPIO_Pin = led_map[led].cathode_pin; GPIO_Init(GPIOA, &GPIO_InitStruc…

插秧偈
type: item
date: 2024-10-29T16:59:47+08:00
description: 手把青秧插满田，低头便见水中天。 心地清净方为道，退步原来是向前。

Qt：QString 编码格式问题
type: item
date: 2024-10-17T19:13:00+08:00
description: QString是 Qt 编程中常用的一个类，用于存储和操作字符串。 问题描述 在学习中因为将Qt默认编码格式给改成了GBK格式，导致在程序再次使用中文时出现乱码。如果把编码格式改回UTF-8，又会导致之前写的注释变为乱码。 解决方法 官方手册的解释如下，大概的意思是说QString存储字符串采用的是Unicode码，每个字符是一个16位的QChar，而不是8位的char。因此，QString能够很好地处理中文字符，一个汉字在QString中算作是一个字符。对于超过65535的Unicode编码，QString则使用两个连续的QChar字符表示。 在构造函数中说用8位字符串str初始化的字符串。使用fromUtf8()函数将给定的const char指针转换为Unicode。所以可以理解为将char*传给QString，要确保编码格式为UTF-8，否则QString在转到Unicode时会出错。 到这里明白了出错原因，因为也是学习于是找找与UTF-8相关的，看到有QString::fromUtf8，于是就以为成功了，但并没有解决该问题，于是继续翻看文档。 发现在我的代码中，"第%1行第%2列" 是一个普通的C++字符串字面量，它包含的是ASCII 字符和一些中文字符，可以理解为没有从UTF-8编码的字节数组开始，而是直接从字符串字面量开始，于是代码改为以下，成功解决乱码。 void Widget::oncursorPositionChanged() { // 获取光标位置 QTextCursor cursor = ui->textEdit->textCursor(); ui->lblPosition->setText(QString::fromLocal8Bit("第%1行第%2列").arg(cursor.blockNumber() + 1).arg(cursor.columnNumber() + 1)); } 下图是来自文心一言的对比：

使用 Socket 建立 IPC 通信
type: item
date: 2024-10-16T10:20:00+08:00
description: IPC（Inter-Process Communication）即进程间通信，是计算机科学中的一个重要概念，是指两个不同进程之间数据交换的过程。 Socket 编程原本是为了网络服务的，之前已介绍。后来逐渐发展成一种进程间通信的方式：Unix Domain Socket IPC。它允许在同一台主机上运行的进程之间进行高效的数据传输，无需经过网络协议栈，因此具有低延迟和高性能的特点。通过文件系统中的特殊文件（通常是一个套接字文件），进程可以通过套接字（socket）来进行通信，实现双向的数据传输。 Socket 编程 Socket 编程是网络通信中的一项基础而强大的技术，它允许两台计算机（或同一台计算机上的两个进程）之间进行双向通信。Socket 可以看作是网络通信中的一个端点，通过它可以发送或接收数据。在 TCP/IP 协议族中， Socke...2024-09-013 IPC通信的原理与机制 IPC 通信的原理是通过特定的机制，使得不同进程之间能够相互传递信息、协作完成任务。这些机制主要包括共享内存、消息传递、管道以及套接字等。这些机制主要包括以下几种： 1. 共享内存： 原理：通过将某一块内存区域映射到多个进程的地址空间中，实现不同进程之间的数据共享。 优点：可以高效地传递大量数据。 缺点：需要解决进程间的同步和互斥问题，以保证数据的正确性。如果多个进程同时访问共享内存区域，可能会导致数据冲突或不一致。 2. 消息传递： 原理：通过发送和接收消息的方式进行进程间通信。发送方将消息发送到一个消息队列或者直接发送给特定的进程，接收方则从消息队列中读取消息或者通过特定的接收机制接收消息。 优点：可以实现异步通信和进程间的解耦。发送方和接收方可以独立地运行，不需要知道对方的存在和状态。 缺点：需要解决消息的传递顺序和消息的可靠性问题。如果消息丢失或顺序错乱，可能会导致通信失败或数据不一致。 3. 管道： 原理：一种半双工的通信方式，用于连接一个读进程和一个写进程。管道可以使得两个进程能够单向地传递数据，其中一个进程充当写端，将数据写入管道，另一个进程充当读端，从管道中读取数据。 优点：适用于父子进程或者具有亲缘关系的进程之间的通信。 缺点：不适用于无关进程间的通信。同时，管道只能进行单向通信，且通信双方的进程必须同时存在。 4. 套接字： 原理：一种在不同主机间进行网络通信的机制，它可以在应用层与传输层之间建立连接，并通过网络传输数据。套接字可以通过TCP或UDP协议进行通信，可以在同一台主机上的不同进程间或者不同主机间进行通信。 优点：具有灵活性和可扩展性。套接字可以支持多种通信协议和数据传输方式，适用于各种复杂的网络通信场景。 缺点：需要注意网络传输的可靠性和安全性。网络传输过程中可能会出现数据丢失、延迟等问题，同时还需要考虑网络安全和身份验证等问题。 套接字实现 使用AF_UNIX通信域，可声明一个使用本地文件的 socket，其他进程可绑定该文件完成进程间通信。 socket_ipc.c： #include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/stat.h> #include <errno.h> #include <string.h> #include <unistd.h> #define SOCKET_PATH "unix_domain.socket" #define SERVER_MODE 1 #define CLIENT_MODE 2 #define BUF_LEN 1024 #define OUTTEXT ("请输入要发送的信息：") #define OUTSIZE sizeof(OUTTEXT) static struct sockaddr_un socket_addr; static char *buf; void handle_error(char *err_msg) { perror(err_msg); unlink(SOCKET_PATH); // 释放 exit(-1); } void server_mode(int sockfd) { struct sockaddr_un client_addr; int client_fd, msg_len; /**** 服务端接收消息，收到 EOF 结束信息 ****/ // 1. 绑定 if (bind(sockfd, (struct sockaddr *)&socket_addr, sizeof(socket_addr)) < 0) { handle_error("bind"); }…

使用 Socket 建立 UDP 连接
type: item
date: 2024-10-09T12:00:00+08:00
description: UDP（用户数据报协议）是一种无连接的协议，相比于TCP（传输控制协议），它不需要建立连接、维护连接状态以及进行错误检查和重传，因此通常用于对实时性要求较高、但对数据完整性要求不高的场景，如视频流、在线游戏等。 本文将通过 UDP 展示如何发送和接收数据，适用于简单的应用场景。对于更复杂的需求，可能需要进一步扩展和优化代码。之前已实现 TCP 的接发数据。 Socket 编程 Socket 编程是网络通信中的一项基础而强大的技术，它允许两台计算机（或同一台计算机上的两个进程）之间进行双向通信。Socket 可以看作是网络通信中的一个端点，通过它可以发送或接收数据。在 TCP/IP 协议族中， Socke...2024-09-013 使用 Socket 建立多个 TCP 连接 使用Socket来建立多个TCP连接是一种常见的网络通信方式，在客户端和服务器之间进行数据交换。需要注意的是，服务器在处理多个客户端时，应该为每个客户端连接创建一个新的线程或进程，或者使用异步IO来避免阻塞。 2024-09-241 UDP 通讯流程 UDP通讯也使用socket，但是接收和发送的函数与 TCP 不一样。由于 UDP 不存在握手这一步骤，所以在绑定地址之后，服务端不需要 listen，客户端也不需要 connect，服务端同样不需要 accept。只要服务端绑定以后，就可以相互发消息了，由于没有握手过程，两端都不能确定对方是否收到消息，这也是 UDP 协议不如 TCP 协议可靠的地方。 编程基本步骤 服务端流程： 创建套接字，选择套接字类型（如 SOCK_DGRAM 表示数据报套接字） 绑定地址和端口 接收数据 处理数据（可选） 发送响应（可选）：如果需要，使用 sendto()发送响应数据 关闭套接字 客户端流程： 创建套接字 指定服务器地址和端口 发送数据，使用 sendto() 函数发送数据 接收响应（可选）：可以使用 recvfrom() 函数接收数据 关闭套接字 函数介绍 UDP 协议接收和发送数据不再使用 send 和 recv 方法，这两个方法一般用于 TCP 通信，UDP 通信使用 sendto 和 recvfrom方法。 需要包含的头文件： #include <sys/types.h> #include <sys/socket.h> 1. sendto ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); /** * @brief 向指定地址发送缓冲区中的数据（一般用于UDP模式） * * @param sockfd 套接字文件描述符 * @param buf 缓冲区指针 * @param len 缓冲区大小 * @param flags 通信标签，详细减send方法说明 * @param dest_addr 目标地址。如果用于连接模式，该参数会被忽略 * @param addrlen 目标地址长度 * @return ssize_t 发送的消息大小。发送失败返回-1 */ 2. recvfrom ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); /** * @brief 将接收到的消息放入缓冲区 buf 中。 * * @param sockfd 套接字文件描述符 * @param buf 缓冲区指针 * @param len 缓冲区大小 * @param flags 通信标签，详见recv方法说明 * @param src_addr 可以填NULL，如果 src_addr 不是 NULL，并且底层协议提供了消 息的源地址，则该源地址将被放置在 src_addr 指向的缓冲区中。 * @param addrlen 如果src_addr不为NULL，它应初始化为与 src_addr 关联的缓冲 区的大小。返回时，addrlen 被更新为包含实际源地址的大小。如果提供的缓冲区太小， 则返回的地址将被截断；在这种情况下，addrlen 将返回一个大于调用时提供的值。 * @return ssize_t 实际收到消息的大小。如果接收失败，返回-1 */ 示例程序 udp_server.c： #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #incl…

使用 Socket 建立多个 TCP 连接
type: item
date: 2024-09-24T21:08:00+08:00
description: 使用Socket来建立多个TCP连接是一种常见的网络通信方式，在客户端和服务器之间进行数据交换。需要注意的是，服务器在处理多个客户端时，应该为每个客户端连接创建一个新的线程或进程，或者使用异步IO来避免阻塞。 本文将以多线程和多进程两种方式实现，可以运行多个客户端实例来测试服务器是否能处理多个TCP连接。可以简单地在命令行中多次运行客户端脚本，或者写一个循环来多次创建和关闭客户端连接。 在C语言中，实现一个基本的 TCP 服务器和客户端涉及使用 socket 编程接口，这通常包括 socket(), bind(), listen(), accept(), send(), recv() 和 close() 等函数，之前有所介绍。Socket 编程 Socket 编程是网络通信中的一项基础而强大的技术，它允许两台计算机（或同一台计算机上的两个进程）之间进行双向通信。Socket 可以看作是网络通信中的一个端点，通过它可以发送或接收数据。在 TCP/IP 协议族中， Socke...2024-09-013 示例程序 以多线程和多进程两种方式实现，支持多个客户端连接服务器。需要注意的是服务器将监听一个特定的端口，并接受来自客户端的连接。一旦连接建立，它将接收来自客户端的消息，并将其回显给客户端，而客户端的端口绑定时自动分配的，当然也可以修改程序成一个指定端口。 基于多线程支持多个 TCP 连接 server.c #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #include <pthread.h> #define PORT 6666 #define BUFFSIZE 1024 #define TYPESIZE (sizeof(char) * BUFFSIZE) #define handle_error(cmd, result) \ if(result < 0) \ { \ perror(cmd); \ return -1; \ } void * read_from_client_then_write(void * arg) { // 读取客户端发送过来的数据 回复收到 int client_fd = *(int *)arg; char * read_buf = NULL; char * write_buf = NULL; ssize_t count = 0, send_count = 0; // 初始化 read_buf = malloc(sizeof(char) * BUFFSIZE); write_buf = malloc(sizeof(char) * BUFFSIZE); if (!read_buf) { printf("服务端读缓存创建异常，断开连接\n"); close(client_fd); perror("read_buf"); return NULL; } if (!write_buf) { printf("服务端写缓存创建异常，断开连接\n"); close(client_fd); perror("read_buf"); return NULL; } while (count = recv(client_fd, read_buf, BUFFSIZE, 0)) { if (count < 0) { perror("recv"); } // 接收数据打印到控制台 printf("从_%d_客户端接收到数据:%s", client_fd, read_buf); // 把收到的消息写到写缓存 strcpy(write_buf, "收到\n"); send_count = send(client_fd, write_buf, BUFFSIZE, 0); if (send_count < 0) { perror("send"); } } // 当客户端输入 ctrl+D 退出循环 // shutdown(client_fd, SHUT_RD); close(client_fd); free(read_buf); free(write_buf); } int main(int argc, char const *argv[]) { struct sockaddr_in server_addr, client_addr; int sock_fd, tmp_res; // 清空 memset(&server_addr, 0, sizeof(…

Socket 编程
type: item
date: 2024-09-01T20:31:00+08:00
description: Socket 编程是网络通信中的一项基础而强大的技术，它允许两台计算机（或同一台计算机上的两个进程）之间进行双向通信。Socket 可以看作是网络通信中的一个端点，通过它可以发送或接收数据。在 TCP/IP 协议族中， Socket 扮演着核心角色，用于实现不同主机之间的进程间通信。 简单来说就是一种编程接口，允许在不同主机上的进程（运行中的程序）通过网络进行数据交换。 Socket 的类型 Socket 主要分为两种类型： 流式 Socket（SOCK_STREAM）：基于 TCP 协议，提供面向连接的、可靠的数据传输服务。 数据传输的顺序与发送的顺序相同，数据不重复，且接收方必须读取了发送方发送的所有数据后才能继续接收后续数据。 数据报 Socket（SOCK_DGRAM）：基于 UDP 协议，提供无连接的、不可靠的数据传输服务。 数据报是独立的，保留数据边界，发送方和接收方之间没有明确的连接，且发送方和接收方都可能不按顺序接收数据。 套接字的组成 一个套接字主要由以下三个属性组成： 网络地址：通常是 IP 地址，用于标识网络上的设备。 端口号：用于标识设备上的特定应用或进程。端口号是一个16位的数字，范围从0到65535。 协议：如 TCP（传输控制协议）或 UDP（用户数据报协议），定义了数据传输的规则和格式。 Socket 编程的基本步骤 Socket 编程的基本步骤大致相同，无论使用的是哪种 Socket 类型（TCP或UDP），但具体细节（如连接管理）会有所不同。以下是一个简化的 TCP Socket 编程步骤： 服务器端 创建Socket：使用 socket() 函数创建一个新的 Socket。 绑定Socket：使用 bind() 函数将Socket与特定的 IP 地址和端口号绑定。 监听连接：使用 listen() 函数使Socket进入被动监听状态，等待客户端的连接请求。 接受连接：使用 accept()函数接受客户端的连接请求，该函数会返回一个新的 Socket，用于与客户端进行通信。 数据收发：使用 read() / write() 或 recv() / send() 函数与客户端进行数据的发送和接收。 关闭连接：数据传输完成后，使用 close() 函数关闭Socket连接。 客户端 创建Socket：使用 socket() 函数创建一个新的 Socket。 连接服务器：使用 connect() 函数与服务器建立连接。 数据收发：使用 read() / write() 或 recv() / send() 函数与服务器进行数据的发送和接收。 关闭连接：数据传输完成后，使用 close() 函数关闭 Socket 连接。 注意事项 在进行 Socket 编程时，需要注意处理网络异常和错误，例如连接失败、数据接收不完整等。 对于 TCP 连接，要注意处理好连接的建立和断开，避免资源泄露。 对于 UDP 连接，虽然不需要建立连接，但也要注意数据包的完整性和顺序性。 在进行跨网络通信时，需要注意防火墙和网络策略的设置，确保数据能够顺利传输。 示例程序 为方便了解连接建立过程，该仅支持同时建立单个 TCP 连接，实现双方可以互相读写。对于相关函数参数并未做详细解释，如需请自行查看相关文档。目前还是很多都在采用 AF_INET（IPv4），使用时需要注意的是网络字节序和主机字节序转化。 server.c： #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #include <pthread.h> #define PORT 6666 #define BUFFSIZE 1024 #define TYPESIZE (sizeof(char) * BUFFSIZE) #define handle_error(cmd, result) \ if(result < 0) \ { \ perror(cmd); \ return -1; \ } void * read_from_client(void * arg) { // 使用recv接收客户端发送的数据 打印到控制台 char * read_buf = NULL; int client_fd = *(int *)arg; // 得到文件描述符 read_buf = malloc(TYPESIZE); ssize_t count = 0; if (!read_buf) { perror…