戴老师的博客 - c
type: channel
link: https://www.tushu.info/tag/c/
items: 跟我一起写Makefile:MakeFile介绍
items: signal(SIGPIPE, SIG_IGN.html)解析
items: C/C++框架和第三方库
items: Socket编程（C语言实现）——基于TCP协议，基于UDP协议(多线程，循环监听）(网络间通信AF_INET，典型的TCP/IP四型模型的通信过程.html)

跟我一起写Makefile:MakeFile介绍
type: item
date: 2026-07-07T08:34:37+00:00
description: makefile 介绍make 命令执行时，需要一个 makefile 文件，以告诉 make 命令如何编译和链接程序。首先，我们用一个示例来说明 makefile 的书写规则，以便给大家一个感性认识。这个示例来源于 GNU 的 make 使用手册。在这个示例中，我们的工程有 8 个 C 文件和 3 个头文件，我们要写一个 makefile 来告诉 make 命令如何编译和链接这几个文件。我们的规则是：如果这个工程没有编译过，那么所有 C 文件都要编译并被链接。如果这个工程的某几个 C 文件被修改，那么只编译被修改的 C 文件，并链接目标程序。如果这个工程的头文件被改变了，那么需要编译引用了这几个头文件的 C 文件，并链接目标程序。只要我们的 makefile 写得够好，所有的这一切，我们只用一个 make 命令就可以完成。make 命令会自动智能地根据当前的文件修改情况来确定哪些文件需要重编译，从而自己编译所需要的文件并链接目标程序。makefile 的规则在讲述这个 makefile 之前，还是让我们先来粗略地看一看 makefile 的规则。target ... : prerequisites ... command ... ...target：可以是一个 object file（目标文件），也可以是一个执行文件，还可以是一个标签（label）。对于标签这种特性，在后续的“伪目标”章节中会有叙述。prerequisites：就是要生成那个 target 所需要的文件或是目标。command：也就是 make 需要执行的命令（任意的 Shell 命令）。这是一个文件的依赖关系。也就是说，target 这一个或多个的目标文件依赖于 prerequisites 中的文件，其生成规则定义在 command 中。说白一点就是，prerequisites 中如果有一个以上的文件比 target 文件要新的话，command 所定义的命令就会被执行。这就是 makefile 的规则，也就是 makefile 中最核心的内容。说到底，makefile 的东西就是这样一点，好像我的这篇文档也该结束了。呵呵。还不尽然，这是 makefile 的主线和核心，但要写好一个 makefile 还不够，我会在后面一点一点地结合我的工作经验给你慢慢道来。内容还多着呢。：）一个示例正如前面所说的，如果一个工程有 3 个头文件和 8 个 C 文件，我们为了完成前面所述的那三个规则，我们的 makefile 应该是下面这个样子的。edit : main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o cc -o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o反斜杠（\）是换行符的意思，这样比较便于 makefile 的易读。我们可以把这个内容保存在名字为 makefile 或 Makefile 的文件中，然后在该目录下直接输入命令 make 就可以生成执行文件 edit。如果要删除执行文件和所有的中间目标文件，那么，只要简单地执行一下 make clean 就可以了。在这个 makefile 中，目标文件（target）包含：执行文件 edit 和中间目标文件（*.o），依赖文件（prerequisites）就是冒号后面的那些 .c 文件和 .h 文件。每一个 .o 文件都有一组依赖文件，而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实…

signal(SIGPIPE, SIG_IGN.html)解析
type: item
date: 2026-07-07T08:31:57+00:00
description: 问题背景近期开发了一个服务器程序，在 Linux 环境下测试时，使用 C++ 编写客户端以千万级数量的短连接进行压力测试。测试过程中，服务器进程总是莫名退出，且没有生成 core 文件。经过排查，问题最终定位为：对一个对端已经关闭的 socket 调用两次 write，第二次调用将会生成 SIGPIPE 信号，而该信号的默认处理动作是终止进程。原因分析1. TCP 连接关闭语义具体的分析可以结合 TCP 的“四次握手”关闭过程。TCP 是全双工信道，可以看作两条单工信道，连接两端的端点各负责一条。当对端调用 close 时，虽然本意是关闭整个连接，但本端首先收到的是 FIN 包。按照 TCP 协议语义，这表示对端关闭了其所负责的那一条单工信道（不再发送数据），但本端仍然可以继续接收数据（即本端的发送信道尚未关闭）。受限于 TCP 协议，一个端点无法直接获知对端的 socket 是调用了 close 还是 shutdown。2. SIGPIPE 信号触发流程Read 阶段：对一个已经收到 FIN 包的 socket 调用 read 方法，如果接收缓冲已空，则返回 0。这通常被视为连接关闭的标志。第一次 Write：此时若第一次调用 write 方法，且发送缓冲没问题，内核会返回正确写入（数据进入发送缓冲）。但该报文到达对端后，会因为对端 socket 已完全关闭（既不发送也不接收）而触发对端发送 RST 报文。第二次 Write：当本端收到 RST 报文后，再次调用 write 方法时，内核会生成 SIGPIPE 信号。进程退出：由于 SIGPIPE 信号的默认处理动作是终止进程，导致服务器异常退出且无 core 文件（因为信号默认终止通常不产生 core dump，除非配置了）。解决方案为了避免进程因 SIGPIPE 信号意外退出，可以捕获该信号，或者更简单地——忽略它。通过设置信号处理函数为 SIG_IGN，可以屏蔽该信号：signal(SIGPIPE, SIG_IGN.html);设置忽略后，当再次向已关闭的 socket 调用 write 方法时：不会产生信号。函数返回 -1。errno 被置为 EPIPE（注意：原文明确为 SIGPIPE 有误，实际 errno 值为 EPIPE）。程序便可通过返回值和 errno 知道对端已经关闭，从而进行相应的错误处理，而不是直接崩溃。在 Linux 下编写 socket 程序时，如果尝试 send 到一个 disconnected socket 上，底层就会抛出 SIGPIPE 信号。这个信号的缺省处理方法是退出进程，大多数时候这都不是我们期望的。因此，重载这个信号的处理方法是必要的。信号处理机制详解对于产生信号，我们可以在产生信号前利用方法 signal(int signum, sighandler_t handler.html) 设置信号的处理。如果没有调用此方法，系统就会调用默认处理方法：中止程序，显示提示信息。我们可以调用系统的处理方法，也可以自定义处理方法。系统里主要定义了以下几种处理动作：SIG_DFL（信号专用的默认动作）如果默认动作是暂停线程，则该线程的执行被暂时挂起。当线程暂停期间，发送给线程的任何附加信号都不交付，直到该线程开始执行（SIGKILL 除外）。把挂起信号的信号动作设置成 SIG_DFL，且其默认动作是忽略信号（如 SIGCHLD）。SIG_IGN（忽略信号）该信号的交付对线程没有影响。系统不允许把 SIGKILL 或 SIGSTOP 信号的动作设置为 SIG_IGN 或 SIG_DFL。SIG_ERR这通常不是处理动作，而是 signal() 函数出错时的返回值指示。在本项目中，调用了 signal(SIGPIPE, SIG_IGN.html)，这样产生 SIGPIPE 信号时就不会中止程序，直接把这个信号忽略掉，保证了服务器的稳定性。扩展：僵尸进程处理如果服务器采用了 fork 创建子进程，需要收集垃圾进程，防止僵尸进程的产生。可以类似地处理 SIGCHLD 信号：signal(SIGCHLD, SIG_IGN.html); // 交给系统 init 去回收这里设置忽略 SIGCHLD 后，子进程退出时就不会产生僵尸进程了，系统会自动回收资源。说明：本文基于 Linux/Unix 环境下的标准 POSIX 信号机制。不同系统对 signal() 语义的实现可能存在细微差异（如 BSD 与 System V），但在 SIGPIPE 和 SIG_IGN 的行为上基本一致。errno 在写失败时通常设置为 EPIPE。

C/C++框架和第三方库
type: item
date: 2026-07-07T08:30:05+00:00
description: 值得学习的 C 语言开源项目以下精选了 10 个值得深入学习的 C 语言开源项目，涵盖了 Web 压测、服务器、数据处理、单元测试、事件驱动等多个领域。这些项目代码简洁、结构清晰，非常适合初学者阅读源码以提升编程能力。1. WebbenchWebbench 是一个在 Linux 下使用的非常简单的网站压测工具。它使用 fork() 模拟多个客户端同时访问设定的 URL，测试网站在压力下的工作性能，最多可以模拟 3 万个并发连接以测试网站的负载能力。Webbench 使用 C 语言编写，代码极其简洁，源码总计不到 600 行。下载链接：http://home.tiscali.cz/~cz210552/webbench.html2. TinyhttpdTinyhttpd 是一个超轻量型 HTTP Server，使用 C 语言开发，全部代码只有 502 行（包括注释）。它附带一个简单的 Client，可以通过阅读这段代码理解一个 HTTP Server 的本质。下载链接：http://sourceforge.net/projects/tinyhttpd/3. cJSONcJSON 是 C 语言中的一个 JSON 编解码器，非常轻量级，C 文件只有 500 多行，速度也非常理想。cJSON 也存在几个弱点，虽然功能不是非常强大，但 cJSON 的小身板和速度是最值得赞赏的。其代码被非常好地维护着，结构也简单易懂，可以作为一个非常好的 C 语言项目进行学习。项目主页：http://sourceforge.net/projects/cjson/4. CMockeryCMockery 是 Google 发布的用于 C 单元测试的一个轻量级框架。它很小巧，对其他开源包没有依赖，对被测试代码侵入性小。CMockery 的源代码行数不到 3K，阅读一下 will_return 和 mock 的源代码就一目了然了。主要特点：免费且开源，Google 提供技术支持；轻量级的框架，使测试更加快速简单；避免使用复杂的编译器特性，对老版本的编译器来讲，兼容性好；并不强制要求待测代码必须依赖 C99 标准，这一特性对许多嵌入式系统的开发很有用。下载链接：http://code.google.com/p/cmockery/downloads/list5. LibevLibev 是一个开源的事件驱动库，基于 epoll、kqueue 等 OS 提供的基础设施。其以高效出名，它可以将 IO 事件、定时器和信号统一起来，放在事件处理这一套框架下处理。基于 Reactor 模式，效率较高，并且代码精简（4.15 版本 8000 多行），是学习事件驱动编程的很好的资源。下载链接：http://software.schmorp.de/pkg/libev.html6. MemcachedMemcached 是一个高性能的分布式内存对象缓存系统，用于动态 Web 应用以减轻 数据库 负载。它通过在内存中缓存数据和对象来减少读取数据库的次数，从而提供动态数据库驱动网站的速度。Memcached 基于一个存储键/值对的 hashmap。Memcached-1.4.7 的代码量还是可以接受的，只有 10K 行左右。下载地址：http://memcached.org/7. LuaLua 非常出色。虽然它是由巴西人发明的，但这并不影响其卓越的品质，反而令人羡慕。让人印象深刻的是 Lua 的源代码，百分之一百的 ANSI C，一点都不掺杂。在任何支持 ANSI C 编译器的平台上都可以轻松编译通过。实测证明，代码真是一点废话都没有。Lua 的代码数量足够小，5.1.4 版本仅仅 1.5 万行，去掉空白行和注释估计能到 1 万行。下载地址：http://www.lua.org/8. SQLiteSQLite 是一个开源的嵌入式关系数据库，实现自包容、零配置、支持事务的 SQL 数据库引擎。其特点是高度便携、使用方便、结构紧凑、高效、可靠。足够小，大致 3 万行 C 代码，250K。下载地址：http://www.sqlite.org/9. UNIX v6UNIX V6 的内核源代码包括设备驱动程序在内约有 1 万行，这个数量的源代码，初学者是能够充分理解的。有一种说法是一个人所能理解的代码量上限为 1 万行，UNIX V6 的内核源代码从数量上看正好在这个范围之内。看到这里，大家是不是也有“如果只有 1 万行的话没准儿我也能学会”的想法呢？另一方面，最近的操作系统，例如 Linux 最新版的内核源代码据说超过了 1000 万行。就算不是初学者，想完全理解全部代码基本上也是不可能的。下载地址：http://minnie.tuhs.org/cgi-bin/utree.pl?file=V610.…

Socket编程（C语言实现）——基于TCP协议，基于UDP协议(多线程，循环监听）(网络间通信AF_INET，典型的TCP/IP四型模型的通信过程.html)
type: item
date: 2026-07-07T08:26:51+00:00
description: Socket 编程基础与 C 语言实现目前较为流行的网络编程模型是客户机/服务器（Client/Server）通信模式。客户进程向服务器进程发出请求，服务器进程响应该请求。通常，一个服务器进程会同时为多个客户端进程服务。如下图所示，服务器进程 B1 同时为客户进程 A1、A2 和 B2 提供服务。Socket 概述定义：所谓 Socket 通常也称作“套接字”，用于描述 IP 地址和端口，是一个通信链的句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。端点：Socket 是连接运行在网络上的两个程序间的双向通信端点。本质：网络通讯本质上就是 Socket 间的通讯。传输：通讯的两端都有 Socket，数据在两个 Socket 之间通过 I/O 来进行传输。套接字（Socket）的类型流式套接字（SOCK_STREAM）提供面向连接、可靠的数据传输服务。数据无差错、无重复地发送，且按发送顺序接收。基于 TCP 协议。数据报式套接字（SOCK_DGRAM）提供无连接服务。数据包以独立包形式发送，不提供无错保证，数据可能丢失，并且接收顺序可能混乱。基于 UDP 协议。原始套接字（SOCK_RAW）允许直接访问底层协议（如 IP、ICMP 等），通常用于网络监控或协议开发。Socket 的设计哲学Socket 起源于 Unix，而 Unix/Linux 基本哲学之一就是“一切皆文件”，都可以用“打开（open）→ 读写（write/read）→ 关闭（close）”模式来操作。Socket 就是该模式的一个实现：Socket 即是一种特殊的文件，一些 Socket 函数就是对其进行的操作（读/写 I/O、打开、关闭）。说白了，Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层，它是一组接口。在设计模式中，Socket 其实就是一个门面模式（Facade Pattern），它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面。对用户来说，一组简单的接口就是全部，让 Socket 去组织数据，以符合指定的协议。随着 Unix 的应用推广，套接字被引进了 Windows 等操作系统。套接字通常只与同一区域的套接字交换数据，Windows Socket 只支持一个通信区域：网际域（AF_INET），这个域被使用网际协议簇的通信进程使用。网络通信流程服务器程序将一个套接字绑定到一个特定的端口，并通过此套接字等待和监听客户的连接请求。客户程序根据服务器程序所在的主机和端口号发出连接请求。如果一切正常，服务器接受连接请求，并获得一个新的绑定到不同端口地址的套接字。客户和服务器通过读、写套接字进行通讯。客户机/服务器（C/S）模式在 TCP/IP 网络应用中，通信的两个进程间相互作用的主要模式是客户机/服务器模式（Client/Server）。即客户向服务器提出请求，服务器接收到请求后，提供相应的服务。服务器端流程：启动：服务器方要先启动，打开一个通信通道并告知本机，它愿意在某一地址和端口上接收客户请求。等待：等待客户请求到达该端口。处理：接收服务请求，处理该客户请求。服务完成后，关闭此新进程与客户的通信链路，并终止。循环：返回第二步，等待另一个客户请求。关闭：关闭服务器。客户端流程：连接：打开一个通信通道，并连接到服务器所在的主机特定的端口。交互：向服务器发送请求，等待并接收应答，继续提出请求。终止：请求结束后关闭通信信道并终止。基于 TCP 协议的 Socket 编程流式传输（客户端）：socket() 函数；bind() 函数可有可无，加上指定传输端口，不加随机分配端口；connect() 函数，填写服务端的地址与端口【网络间通信 AF_INET】；send() 函数；recv() 函数。流式传输（服务端）：socket() 函数；bind() 函数，必须加上指定传输端口【网络间通信 AF_INET】；listen() 函数，使用 iSocketFD；accept() 函数，生成新的 fd，new_fd；send() 函数，new_fd；recv() 函数，new_fd。服务器端先初始化 Socket，然后与端口绑定（bind），对端口进行监听（listen），调用 accept 阻塞，等待客户端连接。在这时如果有个客户端初始化一个 Socket，然后连接服务器（connect），如果连接成功，这时客户端与服务器端的连接就建立了。客户端发送数据请求，服务器端接收请求并处理请求，然后把回应数据发送给客户端，客户端读取数据，最后关闭连接，一次交互结束。基于 TCP 的 Socket 编程实现（SOCK_STREAM）网络间通信 AF_INET，典型的 TCP/IP 四层模型的通信过程。服务器端代码：#incl…