首页/新闻资讯/正文详情

C语言端口扫描器课程设计:TCP握手与多线程select实现

发布时间:2026/9/23 3:32:56 来源:云帆数科 栏目:资讯中心
C语言端口扫描器课程设计:TCP握手与多线程select实现
简介面向计算机网络课程设计场景这份资源整合了端口扫描工具从原理分析到编码实现再到答辩展示的完整过程。项目采用C与Go双语言分别完成两种语言均实现TCP连接、SYN、FIN、UDP四种扫描方式并通过多线程与协程配合生产者消费者模型提升扫描效率能够帮助学习者快速对比不同并发模型在网络探测中的实际应用。压缩包共25个文件总大小约6.35MB主要包含课程设计报告、答辩PPT、C语言源码与头文件、Go源码、技术报告、构建脚本等类型构建脚本同时提供Makefile与CMakeLists.txt便于在不同环境下编译运行整体结构清晰便于按模块阅读与复现。目前已有1432人学习下载适合正在开展端口扫描类课程设计或想熟悉网络编程与并发编程的学习者可作为项目参考、报告模板以及答辩演示的直接素材。1. C语言端口扫描工具课程设计从三次握手到端口状态机端口扫描在 C 语言课程设计里一直是“看着简单、写透很难”的题目。多数人第一反应是connect()串行探测几行代码跑起来就交差但等到汇报时一问“超时为什么是 200ms、线程多了为什么误报、TIME_WAIT 堆积怎么处理”就接不上话。这篇文章把一个能提交的 C 语言端口扫描工具拆开讲从 TCP 三次握手的状态机说起落到非阻塞 connect、select 多路复用、多线程任务队列再给出一套可抄作业的实现、参数调整方式和排错清单。新手可以直接照着写有五年以上经验的人重点看第四章的 fd 边界和第五章的结果一致性校验。2. TCP connect 扫描原理超时判定、RST 响应与端口状态机2.1 为什么课程设计首选 connect() 扫描端口扫描器的核心不是“发数据”而是“探测对端 TCP 状态机的响应”。常见方案有三种TCP connect() 扫描、SYN 半开扫描、TCP 发送特定数据包观察响应。其中 connect() 扫描是把三次握手完整交给内核协议栈处理应用层只关心connect()返回值这样可以把开发重心放在任务调度、超时控制和结果记录上而不是底层包头构造。SYN 半开扫描需要自己构造 IP/TCP 头并监听对端响应Linux 下需要 raw socket 权限Windows 上还要安装 Npcap/WinPcap实验环境普遍不具备这些条件。课程设计如果用 raw socket大部分时间会耗在权限申请和环境配置上原理讲不透反而容易被扣分。另一个原因是 connect() 的成功结果更可靠内核已经完成了完整的握手不需要靠自己解析返回包判断。int fd socket(AF_INET, SOCK_STREAM, 0); int flags fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); int rc connect(fd, (struct sockaddr *)addr, sizeof(addr)); if (rc 0) { /* 极端情况下 connect 立即成功说明对端端口开放 */ record_port(port, PORT_OPEN); close(fd); return; } if (errno ! EINPROGRESS) { /* 非 EINPROGRESS 的错误说明握手尚未开始就被拒绝 */ if (errno ECONNREFUSED) record_port(port, PORT_CLOSED); else if (errno ETIMEDOUT) record_port(port, PORT_FILTERED); close(fd); return; } /* 走到这里表示连接已进入后台需要配合 select 等待结果 */这段代码把connect()设置为非阻塞模式EINPROGRESS是一个关键信号它表示 TCP 握手已经开始但结果还没返回必须交给 select/poll 继续等待。立即返回 0 的情况在远程扫描中很罕见但本机回环端口可能碰到。ECONNREFUSED是对端协议栈回应了 RST说明端口关闭ETIMEDOUT说明 SYN 包发出后没有任何响应通常代表防火墙丢弃或网络路径不通。2.2 端口判定不是只有“通/不通”两种状态很多人写扫描器只记录“能连”和“不能连”但实际排错时还差一个“被过滤”的中间态。connect() 返回的错误信息中ECONNREFUSED表示端口明确关闭ETIMEDOUT表示数据包被丢掉EHOSTUNREACH或ENETUNREACH表示路由层面不可达。我一般会把三种情况分开存便于在课程设计报告里展示网络环境分析。errno 或返回值发生场景端口状态判定connect 返回 0三次握手完成开放ECONNREFUSED对端协议栈返回 RST关闭ETIMEDOUTSYN 无任何响应被过滤或丢包EHOSTUNREACH路由不可达网络不可达EACCES / EPERM本地权限限制本地错误不计入扫描课程设计答辩时能够解释清楚“为什么关闭端口会回 RST、而防火墙过滤后不发任何包”是一个加分点。RST 由目标主机的传输层在收到发往未监听端口的 SYN 时生成属于内核自动行为不需要目标机器上运行任何服务。2.3 超时时间不是拍脑袋定的select 超时值的设置直接影响扫描准确率。内网同一网段时对端响应通常在 1ms 级把超时设成 200ms 已经足够宽松跨网段或公网环境建议设成 500ms 到 1000ms否则正常响应会被误判为超时。另一个细节是 select 的超时时间用完一次后会被内核修改多线程循环里必须在每次调用前重新设置struct timeval。实现上我一般把超时统一收敛到一个宏或外部参数例如#define SCAN_TIMEOUT_MS 300方便课程设计报告里做不同超时值下的对比实验。需要单独记录“超时端口”还有一个原因如果对端处于一个防火墙后connect()会反复重传 SYN默认情况下 Linux 重传 6 次每次等待时间翻倍如果不用非阻塞方式单个端口可能耗掉几十秒。3. 多线程扫描器实现任务队列、并发 fd 管理与结果回收3.1 线程模型选型为什么不用“一端口一线程”扫描器最直接的做法是按顺序遍历 65535 个端口但串行的速度完全不可用。改成“每个端口一个线程”会带来更明显的 fd 管理问题程序会同时持有大量 socket 文件描述符超过系统 fd 上限后socket()直接返回 EMFILE。课程设计的合理方案是固定 N 个工作线程每个线程从共享任务队列里取任务一次只处理一个端口用 select 等待结果。任务队列可以用互斥锁加条件变量实现队列里存的是端口号而不是 socket fdsocket 创建放到 worker 内部。这样控制最大 fd 数量等于线程数不容易触发系统的 ulimit 限制。结果记录用写锁保护的结构体数组避免多个线程同时 printf 导致输出交错。typedef struct { short port; int status; /* 0: 未扫描, 1: 开放, 2: 关闭, 3: 超时 */ } scan_result; typedef struct { int *ports; int head; int tail; int count; pthread_mutex_t lock; pthread_cond_t not_empty; } task_queue; void *probe_worker(void *arg) { while (1) { int port dequeue_task(); /* 取不到任务则阻塞 */ if (port -1) break; int fd socket(AF_INET, SOCK_STREAM, 0); if (fd 0) { record_port(port, PORT_ERROR); continue; } set_nonblocking(fd); struct sockaddr_in addr; addr.sin_family AF_INET; addr.sin_port htons(port); addr.sin_addr.s_addr target_ip; int rc connect(fd, (struct sockaddr *)addr, sizeof(addr)); if (rc 0) { record_port(port, PORT_OPEN); close(fd); continue; } if (errno ! EINPROGRESS) { record_port(port, (errno ECONNREFUSED) ? PORT_CLOSED : PORT_FILTERED); close(fd); continue; } fd_set wfds; struct timeval tv; tv.tv_sec SCAN_TIMEOUT_MS / 1000; tv.tv_usec (SCAN_TIMEOUT_MS % 1000) * 1000; FD_ZERO(wfds); FD_SET(fd, wfds); int sel select(fd 1, NULL, wfds, NULL, tv); if (sel 0 FD_ISSET(fd, wfds)) { int soerr 0; socklen_t slen sizeof(soerr); getsockopt(fd, SOL_SOCKET, SO_ERROR, soerr, slen); if (soerr 0) record_port(port, PORT_OPEN); else if (soerr ECONNREFUSED) record_port(port, PORT_CLOSED); else record_port(port, PORT_FILTERED); } else if (sel 0) { record_port(port, PORT_TIMEOUT); } close(fd); } return NULL; }这段代码是扫描器的核心循环worker 从队列取一个端口创建 socket非阻塞 connect交给 select 等待写事件。select 返回写事件只代表“本机 socket 可以往管道里写数据”或者“连接已建立”不一定等于握手成功所以必须用getsockopt(SO_ERROR)做二次确认。这是很多人忽略的地方直接导致大量误报。3.2 结果数组与输出顺序控制课程设计经常被问“扫描结果为什么是乱序的”。多线程完成后按端口顺序重新排序输出即可排序可以用 C 标准库qsort()也可以扫描前就存一份有序数组扫描时只标记结果索引。结果量不大时直接用结构体数组比链表好用因为不需要频繁 malloc内存连续性也好。如果要演示 C 语言链表能力可以在输出阶段把结果转成单链表但扫描阶段用数组更可控。参数推荐值说明worker 线程数816超过 CPU 核数收益不明显反而增加上下文切换SCAN_TIMEOUT_MS内网 200公网 800超时太大拖慢整体速度太小产生误报结果数组长度65536固定分配避免扫描大网段时扩容fd 上限ulimit -n必须大于线程数的两倍线程数设为 CPU 核数的 2 倍左右即可课程设计环境一般 8 线程足够跑满千兆内网。扫描 11024 端口时8 线程加 300ms 超时大约在 3 秒内完成如果去扫 B 段网络每台机器一个线程池速度瓶颈会变成目标机的 SYN 队列而非本机性能。3.3 struct timeval 的坑复用前必须重新赋值select 函数会修改 timeval 的值把剩余等待时间写回。多线程共享同一个 timeval 会被并发修改形成数据竞争。每个 worker 内部定义局部 timeval 是安全的但如果把超时值放在全局或任务里复用就会发生扫描时间被随机缩短或延长的怪问题。另一个注意点是tv_usec范围是 0999999毫秒转微秒不要直接拿毫秒数乘 1000300 * 1000 300000没问题超过 1000ms 就要拆成秒和微秒两部分否则 select 报 EINVAL。我一般在代码里写一个set_timeval_ms(struct timeval *tv, int ms)辅助函数统一处理秒和微秒的拆分避免在 worker 里反复写错。4. 编译运行与排错fd 泄漏、TIME_WAIT堆积、误报的定位方法4.1 编译命令与运行参数编译时必须链接 pthread 库命令是gcc -O2 -Wall -o scanner scanner.c -lpthread。-Wall会提示隐式声明和未使用变量这些在交代码前都应该清理掉。程序运行参数建议设计成./scanner IP start_port end_port threads [timeout_ms]默认线程数和超时在代码里给出。这里的[timeout_ms]不传时用 300ms便于老师修改参数做对比实验。运行时如果遇到socket: Too many open files先执行ulimit -n看当前 fd 上限临时调大可以是ulimit -n 65535。线程数在代码里写死也没关系但如果用命令行参数传入要校验不能超过系统 fd 上限的一半否则并发建 socket 会触发 EMFILE。4.2 扫描结果和实际不一致的定位思路第一类问题是“端口明明开着扫描器报超时”。先检查是否把超时设置得比防火墙的 RST 响应还短再在目标机上分别执行ss -tan | grep 端口号和lsof -i :端口号确定服务是否真的在监听。如果服务只监听 127.0.0.1外部扫描肯定失败这不是扫描器 bug。第二类问题是“扫描器报开放但 nc 连不上”。这种常见于本机连接数过多导致 TIME_WAIT 堆满。扫描完成后立刻执行ss -tan | awk {print $1} | sort | uniq -c | head -20如果 TIME_WAIT 数量上千说明每个 worker 的 socket 都进入了连接关闭状态。大量短连接会在本机留下大量 TIME_WAIT影响下一次扫描。可以在关闭 socket 前设置SO_LINGER强制 RST 释放连接struct linger ling {1, 0}; setsockopt(fd, SOL_SOCKET, SO_LINGER, ling, sizeof(ling));这样 close 时立即发送 RST跳过 TIME_WAIT 状态但要注意 tomcat 等服务器处理 RST 时会产生已记录连接在不同行仅适合课程设计场景。生产环境的主动扫描不建议使用。4.3 验证单端口正确性nc、telnet、tcpdump 三方对比扫描器运行后先用系统工具验证目标端口nc -vz -w 3 192.168.1.100 22 nc -vz -w 3 192.168.1.100 8080 telnet 192.168.1.100 22nc -vz本质也是做一次 TCP 连接成功退出码为 0失败非 0。如果 nc 结果和扫描器不一致再用 tcpdump 在目标机抓包确认tcpdump -i eth0 tcp port 8080 -nn观察 SYN 包到达后是否回了 SYN-ACK。如果 tcpdump 能看到 SYN-ACK 但客户端 select 超时说明回包被客户端防火墙丢弃如果只有 SYN 没有 SYN-ACK问题在目标机的防火墙策略或端口未监听。课程设计报告中把这三方对比写进去能有效证明扫描器不是只靠猜测输出结果。5. 进阶校验技巧用 SO_ERROR 优先级过滤半连接与伪开放端口5.1 写一个单端口重试函数验证开放结果稳定性多线程快速扫描中偶尔会出现“端口开放但握手未完成”的情况。getsockopt(SO_ERROR)返回 0 时代表本机 socket 最终没有错误但从严格意义上讲select 返回后可读可写不能完全代替对端应用层已 accept。一个实用的验证方式是对结果中标记为开放的端口再做一次非阻塞重连并统计两次结果是否一致。int verify_port_again(struct sockaddr_in *addr, int timeout_ms) { int fd socket(AF_INET, SOCK_STREAM, 0); if (fd 0) return -1; set_nonblocking(fd); int rc connect(fd, (struct sockaddr *)addr, sizeof(*addr)); if (rc 0) { close(fd); return 1; } if (errno ! EINPROGRESS) { close(fd); return 0; } fd_set wfds; struct timeval tv; set_timeval_ms(tv, timeout_ms); FD_ZERO(wfds); FD_SET(fd, wfds); int sel select(fd 1, NULL, wfds, NULL, tv); if (sel 0) { int soerr 0; socklen_t slen sizeof(soerr); getsockopt(fd, SOL_SOCKET, SO_ERROR, soerr, slen); close(fd); return (soerr 0) ? 1 : 0; } close(fd); return 0; }这个函数把超时设短比如 100ms只验证之前确认的开放端口。相比主扫描逻辑它不需要照顾关闭和过滤状态逻辑更纯粹。连续两次都确认开放才进入最终输出可以过滤掉对端端口扫描过程中的间歇性丢包。5.2 对比 CONNECT 时延识别内网防火墙静默丢弃把每次握手的耗时记下来比只记状态更有说服力。具体做法是在setsockopt之后、select 之前记录 tickselect 返回后取差值。开放端口通常 110ms 返回被防火墙规则慢速丢弃的端口耗时接近超时上限。课程设计里输出一个表格列出端口号、状态、耗时能直接看出哪些端口处于“可疑”状态。printf(%5d %-10s %6.2f ms\n, port, status_text(port), roundtrip_ms);我习惯把耗时单位统一成毫秒保留两位小数。clock_gettime(CLOCK_MONOTONIC)比clock()更适合测量跨线程间隔因为clock()返回的是 CPU 时间不是墙上时间。5.3 用脚本做回归对比防止改代码后引入新误报修改超时或线程逻辑之前先跑一遍旧版本把开放端口列表存成文件再跑新版本diff对比差异./scanner 192.168.1.100 1 1024 8 300 before.txt # 修改代码后重新编译 ./scanner 192.168.1.100 1 1024 8 300 after.txt diff before.txt after.txtdiff 输出只应包含扫描顺序调整或因网络抖动出现的边界端口如果大量端口状态发生变化说明改动影响了核心逻辑而不是网络波动。这个小习惯能在课程设计联调阶段省下大量时间也是把问题从“猜测”变成“可复现”的最短路径。本文还有配套的精品资源点击获取

相关推荐

routersploit 实战:Billion 路由器 Telnet 默认凭据字典攻击模块解析与使用指南
routersploit 实战:Billion 路由器 Telnet 默认凭据字典攻击模块解析与使用指南

网络安全嵌入式 【免费下载链接】routersploit Exploitation Framework for Embedded Devices 项目地址: https://gitcode.com/gh_mirrors/ro/routersploit 点击查看 免费下载 本篇文章围绕 routersploit 中的 creds/routers/billion/telnet_default_creds 模块展开… · 2026/9/23 3:32:56

大模型3D空间推理能力实测:鹈鹕骑车场景生成评测
大模型3D空间推理能力实测:鹈鹕骑车场景生成评测

1. 从“鹈鹕骑车”说起:为什么一个3D小场景能测出大模型的真实水平第一次看到“鹈鹕骑车”这个测试题,我差点笑出声。一只鹈鹕,蹬着一辆自行车,在3D空间里摇摇晃晃地前进——这画面本身就带着一种荒诞的喜感。但笑完之后我意识到&… · 2026/9/23 3:32:56

降噪耳机选购指南:三重物理防线与学生党实测避坑
降噪耳机选购指南:三重物理防线与学生党实测避坑

1. 为什么“降噪耳机选购”这件事,正在变得越来越难?2026年春天,我在大学城周边的数码店蹲点做了一周实地观察:平均每天有17个学生模样的顾客走进来,开口第一句几乎全是“有没有便宜点的降噪耳机?”——但真… · 2026/9/23 3:32:50

Spring Boot集成Minio:MinioUtil封装实战指南
Spring Boot集成Minio:MinioUtil封装实战指南

做后端这几年,文件上传下载功能几乎是每个项目躲不掉的。最开始拿Minio当文件服务器,直接在每个Service里new MinioClient,代码又脏又难复用,后来干脆抽了一个MinioUtil工具类,把上传、下载、删除、生成预览URL这些操作… · 2026/9/23 4:56:49

2026最新estc选型指南:面试被问原理别慌,这3种方案对比看完就懂
2026最新estc选型指南:面试被问原理别慌,这3种方案对比看完就懂

2026最新estc选型指南:面试被问原理别慌,这3种方案对比看完就懂 面试被问“ESTC原理是什么”答不上来?别慌,2026年最新的技术栈里,ESTC(Event-Driven State Transition… · 2026/9/23 4:56:48

C++访问者模式实战:从双分派原理到std::variant替代方案
C++访问者模式实战:从双分派原理到std::variant替代方案

1. 从一段反复重写的代码说起说起来有点尴尬,我第一次真正意识到访问者模式的价值,是在一个图形编辑器项目里改需求改到想摔键盘的时候。那会儿系统里有一批形状类,Circle、Rectangle、Line,全部继承自一个抽象基类Shape。需求是给… · 2026/9/23 4:56:42

低代码平台的技术内核:构建能力与运行治理双层结构
低代码平台的技术内核:构建能力与运行治理双层结构

搞过低代码平台的人都知道一句话:外行看是拖拉拽,内行看全是坑。业务部门看到的是三分钟搭一个表单,IT负责人看到的是审批流、权限、数据一致性、发布上线、日志追溯……每一项都是工程问题。我这些年参与过自研低代码平台,也深度… · 2026/9/23 4:56:42

Tauri等轻量桌面框架选型指南:从4.7MB包体看交付本质
Tauri等轻量桌面框架选型指南:从4.7MB包体看交付本质

1. 这不是“换框架”的热闹,而是桌面应用交付逻辑的彻底重写你有没有打开过一个桌面软件,点开安装包属性,看到那个刺眼的224MB?点开任务管理器,发现它刚启动就占了300MB内存,CPU持续跑在5%以上?… · 2026/9/23 4:56:42

2026最新CAJ解析避坑指南:3步搞定移动端代码不报错
2026最新CAJ解析避坑指南:3步搞定移动端代码不报错

2026最新CAJ解析避坑指南:3步搞定移动端代码不报错 复制来的代码跑不通,报错信息像天书一样让人头大,这是无数开发者在2026年依然面临的噩梦。你明明照着CSDN热帖里的步骤敲键盘,结果一运行就崩,调试半天发现根本问题不在逻辑,而在环境… · 2026/9/23 4:56:35

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码