局域网内探测在线好友是如何做到的?

news2024/10/8 10:32:19

一、前言

随着局域网(LAN)应用的广泛使用,网络通信已经成为软件设计中不可或缺的一部分。局域网聊天软件作为一种常见的网络应用,可以实现多个用户之间的实时通信,广泛应用于企业内部沟通和小型网络环境中。本项目设计并实现一个基于C语言的局域网群聊程序,通过UDP广播搜索在线用户,并在发现其他在线应用程序后,自动建立TCP连接,实现消息的收发。本程序展示了如何在Windows环境下使用Winsock API进行网络编程,提供了对UDP和TCP协议的实际应用,体现了网络通信中的多线程处理、广播通信和实时消息传递的关键技术点。

image-20240813114353554

二、好友探测功能

在局域网内探测并发现其他在线用户是局域网聊天软件最主要的核心功能。该过程涉及到局域网广播(UDP广播)和TCP连接两个关键步骤。

下面将详细介绍实现这一功能的方法和设计思路。

2.1 使用UDP广播探测在线用户

1.1 UDP广播的概念

UDP(用户数据报协议)是一种无连接的、轻量级的传输协议,适用于发送小数据包。UDP广播允许将数据包发送到局域网内的所有设备,所有在监听特定端口的设备都能够接收到广播消息。这种特性使得UDP广播非常适合用于探测和发现局域网内的在线设备。

1.2 探测思路

在程序启动时,客户端会通过UDP广播发送一个上线通知消息,表示自己已在线。其他监听同一端口的客户端接收到这一消息后,可以获知该客户端的IP地址,并识别出它在线。具体的实现步骤如下:

  1. 创建UDP套接字:为UDP通信创建一个套接字,并配置为允许广播。
  2. 发送广播消息:程序向局域网内的广播地址(通常为255.255.255.255)发送一个消息,例如"HELLO, I’M ONLINE"。这个消息会被局域网内所有监听相同端口的设备接收。
  3. 监听UDP消息:每个客户端都持续监听来自局域网内的UDP消息。一旦接收到广播消息,客户端会记录发送方的IP地址和端口,以确认该客户端在线。

2.2 建立TCP连接实现通信

2.1 TCP连接的必要性

UDP广播虽然可以有效地发现在线用户,但由于其无连接的特点,不适合用于长时间的可靠通信。因此,在发现其他在线用户后,程序需要通过TCP(传输控制协议)建立可靠的点对点连接。TCP是一种面向连接的协议,能够确保数据的完整性和顺序传输,非常适合用于聊天消息的传递。

2.2 连接建立的流程

  1. 接收广播后尝试连接:当客户端接收到来自其他用户的UDP广播后,会通过TCP连接到该用户。客户端会使用从UDP消息中获取的IP地址和预定义的TCP端口号,发起连接请求。
  2. 接受连接请求:已经在线的客户端会开启一个TCP监听套接字,等待来自其他客户端的连接请求。一旦有请求到达,程序将接受连接,并启动一个独立的线程处理与该客户端之间的消息通信。
  3. 消息收发:通过建立的TCP连接,用户可以实时发送和接收聊天消息,确保消息在网络不稳定的情况下仍能可靠传输。

2.3 多线程处理的必要性

由于UDP广播接收、TCP连接监听和消息收发等操作需要同时进行,程序采用了多线程的设计。每个功能模块都运行在独立的线程中,确保它们可以并行处理,互不干扰。这样不仅提高了程序的响应速度,还增强了用户体验,确保通信的实时性。

2.4 总结

通过UDP广播发现局域网内的在线用户,然后利用TCP协议建立可靠的通信连接,这是局域网聊天软件的核心设计思路。UDP广播的轻量和广泛性使得在线用户的探测变得高效,而TCP连接则保证了后续通信的可靠性。多线程的引入进一步优化了程序的性能,使得该局域网聊天软件在实际应用中表现出色。

三、代码实现

下面是完整的代码。在VS2022里运行测试。

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define UDP_PORT 8888
#define TCP_PORT 8889
#define BROADCAST_ADDR "255.255.255.255"
#define BUFFER_SIZE 1024

typedef struct ClientInfo {
    SOCKET socket;
    struct sockaddr_in address;
} ClientInfo;

void udp_broadcast_listener(void* param);
void tcp_connection_listener(void* param);
void tcp_message_listener(void* param);

int main() {
    WSADATA wsaData;
    SOCKET udp_socket, tcp_socket;
    struct sockaddr_in udp_addr, tcp_addr;
    char buffer[BUFFER_SIZE];
    struct sockaddr_in client_addr;
    int addr_len = sizeof(client_addr);

    // 初始化 Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed\n");
        return 1;
    }

    // 创建 UDP 套接字
    udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (udp_socket == INVALID_SOCKET) {
        printf("UDP socket creation failed\n");
        WSACleanup();
        return 1;
    }

    // 配置 UDP 广播地址
    memset(&udp_addr, 0, sizeof(udp_addr));
    udp_addr.sin_family = AF_INET;
    udp_addr.sin_port = htons(UDP_PORT);
    udp_addr.sin_addr.s_addr = inet_addr(BROADCAST_ADDR);

    // 启动 UDP 广播监听线程
    _beginthread(udp_broadcast_listener, 0, NULL);

    // 启动 TCP 连接监听线程
    _beginthread(tcp_connection_listener, 0, NULL);

    // 向局域网内广播自己上线
    strcpy(buffer, "HELLO, I'M ONLINE");
    sendto(udp_socket, buffer, strlen(buffer), 0, (struct sockaddr*)&udp_addr, sizeof(udp_addr));

    while (1) {
        fgets(buffer, BUFFER_SIZE, stdin);
        buffer[strcspn(buffer, "\n")] = 0;  // 移除换行符
        sendto(udp_socket, buffer, strlen(buffer), 0, (struct sockaddr*)&udp_addr, sizeof(udp_addr));
    }

    closesocket(udp_socket);
    WSACleanup();
    return 0;
}

void udp_broadcast_listener(void* param) {
    SOCKET udp_socket;
    struct sockaddr_in udp_addr, sender_addr;
    char buffer[BUFFER_SIZE];
    int addr_len = sizeof(sender_addr);

    // 创建 UDP 套接字
    udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (udp_socket == INVALID_SOCKET) {
        printf("UDP socket creation failed in listener\n");
        return;
    }

    // 配置 UDP 地址
    memset(&udp_addr, 0, sizeof(udp_addr));
    udp_addr.sin_family = AF_INET;
    udp_addr.sin_port = htons(UDP_PORT);
    udp_addr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字
    if (bind(udp_socket, (struct sockaddr*)&udp_addr, sizeof(udp_addr)) == SOCKET_ERROR) {
        printf("UDP socket binding failed\n");
        closesocket(udp_socket);
        return;
    }

    while (1) {
        int recv_len = recvfrom(udp_socket, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&sender_addr, &addr_len);
        if (recv_len > 0) {
            buffer[recv_len] = '\0';
            printf("Received UDP broadcast from %s: %s\n", inet_ntoa(sender_addr.sin_addr), buffer);

            // 如果接收到"HELLO, I'M ONLINE",尝试建立TCP连接
            if (strcmp(buffer, "HELLO, I'M ONLINE") == 0) {
                SOCKET tcp_socket;
                struct sockaddr_in tcp_addr;

                tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
                if (tcp_socket == INVALID_SOCKET) {
                    printf("TCP socket creation failed\n");
                    continue;
                }

                // 配置 TCP 地址
                memset(&tcp_addr, 0, sizeof(tcp_addr));
                tcp_addr.sin_family = AF_INET;
                tcp_addr.sin_port = htons(TCP_PORT);
                tcp_addr.sin_addr.s_addr = sender_addr.sin_addr.s_addr;

                if (connect(tcp_socket, (struct sockaddr*)&tcp_addr, sizeof(tcp_addr)) == SOCKET_ERROR) {
                    printf("TCP connection failed to %s\n", inet_ntoa(tcp_addr.sin_addr));
                    closesocket(tcp_socket);
                }
                else {
                    printf("Connected to %s\n", inet_ntoa(tcp_addr.sin_addr));

                    // 启动 TCP 消息监听线程
                    ClientInfo* client = (ClientInfo*)malloc(sizeof(ClientInfo));
                    client->socket = tcp_socket;
                    client->address = tcp_addr;
                    _beginthread(tcp_message_listener, 0, client);
                }
            }
        }
    }

    closesocket(udp_socket);
}

void tcp_connection_listener(void* param) {
    SOCKET tcp_socket, client_socket;
    struct sockaddr_in tcp_addr, client_addr;
    int addr_len = sizeof(client_addr);

    // 创建 TCP 套接字
    tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (tcp_socket == INVALID_SOCKET) {
        printf("TCP socket creation failed\n");
        return;
    }

    // 配置 TCP 地址
    memset(&tcp_addr, 0, sizeof(tcp_addr));
    tcp_addr.sin_family = AF_INET;
    tcp_addr.sin_port = htons(TCP_PORT);
    tcp_addr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字
    if (bind(tcp_socket, (struct sockaddr*)&tcp_addr, sizeof(tcp_addr)) == SOCKET_ERROR) {
        printf("TCP socket binding failed\n");
        closesocket(tcp_socket);
        return;
    }

    // 开始监听
    if (listen(tcp_socket, 5) == SOCKET_ERROR) {
        printf("TCP socket listen failed\n");
        closesocket(tcp_socket);
        return;
    }

    printf("TCP connection listener started...\n");

    while (1) {
        client_socket = accept(tcp_socket, (struct sockaddr*)&client_addr, &addr_len);
        if (client_socket == INVALID_SOCKET) {
            printf("TCP accept failed\n");
            continue;
        }

        printf("Accepted connection from %s\n", inet_ntoa(client_addr.sin_addr));

        // 启动 TCP 消息监听线程
        ClientInfo* client = (ClientInfo*)malloc(sizeof(ClientInfo));
        client->socket = client_socket;
        client->address = client_addr;
        _beginthread(tcp_message_listener, 0, client);
    }

    closesocket(tcp_socket);
}

void tcp_message_listener(void* param) {
    ClientInfo* client = (ClientInfo*)param;
    char buffer[BUFFER_SIZE];
    int recv_len;

    while ((recv_len = recv(client->socket, buffer, BUFFER_SIZE, 0)) > 0) {
        buffer[recv_len] = '\0';
        printf("Message from %s: %s\n", inet_ntoa(client->address.sin_addr), buffer);
    }

    printf("Connection closed by %s\n", inet_ntoa(client->address.sin_addr));
    closesocket(client->socket);
    free(client);
}

image-20240813114216952

程序在主函数里通过 WSAStartup 函数初始化Winsock库,这是一种Windows平台上的网络编程库,提供了网络通信所需的API。初始化成功后,程序可以使用Winsock提供的各种网络功能。

创建了两个主要的套接字:

  • UDP套接字:用于广播消息和接收其他设备的广播。
  • TCP套接字:用于建立点对点的通信连接。

在程序启动时,通过UDP广播向局域网内所有设备发送一个“HELLO, I’M ONLINE”的消息。这一消息用来告知局域网内的其他用户自己的存在,从而实现在线用户的探测。

为了接收其他用户的广播消息,程序创建了一个UDP套接字并绑定到特定的端口上(UDP_PORT)。程序通过这个套接字监听局域网内的所有广播消息,提取发送者的IP地址,并处理接收到的消息。

一旦接收到来自其他在线用户的UDP广播消息,程序会尝试通过TCP建立连接。步骤包括:

  • 从UDP消息中提取发送者的IP地址。
  • 使用提取的IP地址和预定义的TCP端口号发起TCP连接请求。
  • 如果连接成功,程序将建立一个可靠的点对点通信通道,用于后续的聊天消息传递。

程序创建一个TCP套接字,并在特定端口上进行监听,等待其他用户的连接请求。当有新的连接请求到达时,程序接受该连接,并为每个连接创建一个新的线程,以处理与该连接相关的消息通信。

用户在键盘上输入消息后,程序通过UDP套接字广播该消息到局域网内的所有在线用户。此功能确保所有在线的用户都能看到发送的消息。

程序通过TCP连接接收来自其他用户的消息。接收到的消息将被显示在终端上,提供实时的聊天功能。每个TCP连接使用一个独立的线程进行处理,确保消息的及时传递和处理。

程序采用了多线程技术来并行处理不同的任务,确保系统的响应性和效率。主要线程包括:

  • UDP广播监听线程:处理UDP广播消息的接收和处理。
  • TCP连接监听线程:接受来自其他用户的TCP连接请求。
  • TCP消息处理线程:处理与每个已连接用户之间的消息交换。

在程序退出时,所有打开的套接字都会被关闭,资源得到释放。程序通过调用 closesocket 函数关闭套接字,并调用 WSACleanup 进行Winsock库的清理,确保程序在退出时不会泄漏资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2196128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

‌视频尺寸修改与批量剪辑技巧

在当今这个数字化时代&#xff0c;视频内容已成为人们获取信息、娱乐和社交的重要方式。然而&#xff0c;面对海量的视频素材&#xff0c;如何高效地管理和编辑它们成为了一个挑战。 1打开视频剪辑高手软件&#xff0c;切换功能到“批量剪辑视频” 2把需要剪辑的视频导入到表格…

希尔排序和直接插入排序

因为排序这些比较复杂点我就分几期给大家来讲~~~ 直接插入排序 直接插入排序是一种简单的排序算法&#xff0c;主要用于对少量数据进行排序。其基本思想是将待排序的元素逐个插入到已经排好序的部分中&#xff0c;从而形成一个有序序列。 具体步骤如下&#xff1a; 初始化&…

基于32单片机的博物馆安全监控系统设计

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 检测 分别是温湿度 光照 PM2.5、烟雾、红外&#xff0c;然后用OLED屏幕显示&#xff0c; 红外超过阈值则蜂鸣器报警&#xff0c;这是防盗报警&#xff1b;温度或烟雾超过阈值&#xff0c;则蜂鸣器…

【目标检测】木制地板缺陷破损数据集338张6类VOC+YOLO格式

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3383 标注数量(xml文件个数)&#xff1a;3383 标注数量(txt文件个数)&#xff1a;3383 标注…

fiddler抓包21_(性能测试)耗时分析

课程大纲 使用场景 1. 查看请求耗时。 2. &#xff08;更多&#xff09;前端性能分析、优化。 1. 查看单个请求耗时 耗时详情&#xff1a;选择请求 - 右侧标签菜单选择Statistics“请求统计”。 传输时间线&#xff1a;选择请求 - 右侧选择“TimeLine”。 2. 前端性能分析&a…

MYSQL 常见锁机制详解,常见锁问题排查及分析

1&#xff0c;锁分类 锁冲突是影响数据库性能的重要指标&#xff0c;本章节介绍MYSQL常见锁&#xff0c;及各种说的常用示例&#xff0c;mysql锁的分类如下&#xff1a; 从操作类型分类&#xff1a;读锁、写锁&#xff1b; 从操作粒度分类&#xff1a;表锁、页锁、行锁&#x…

【文献阅读】Transfer Learning For Text Classification Via Model Risk Analysis

前言&#xff1a;本文是以文本分类的迁移学习任务为例&#xff0c;对风险分析模型的整体框架流程做梳理。 目录 1. LearnRisk1.1 motivatio1.2 overall 2. LearnRisk-TC2.1 构造风险特征2.1.1 risk metric2.1.2 risk feature 2.2 构建风险模型2.3 训练风险模型2.4 微调base mod…

线性代数入门

线性代数入门 线性代数&#xff08;Linear Algebra&#xff09;是数学的重要分支之一&#xff0c;广泛应用于工程、计算机科学、物理学、经济学等领域。它主要研究向量、矩阵及其在空间中的变换。对于程序员来说&#xff0c;掌握线性代数的基础知识能够帮助更好地理解数据处理…

边缘人工智能(Edge Intelligence)

边缘人工智能&#xff08;Edge AI&#xff09;是指在边缘设备上直接运行人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;算法的技术。机器学习是一个广泛的领域&#xff0c;近年来取得了巨大的进步。它所基于的原则是&#xff0c;计算机可以通过从数据…

Qt-QGridLayout布局类控件(43)

目录 描述 属性 使用 常规使用 垂直布局 水平布局 布局是按照相对大小进行的 拉伸系数 使用 设置水平拉伸系数 设置垂直拉伸系数 描述 Qt 中还提供了 QGridLayout ⽤来实现⽹格布局的效果.可以达到 M * N的这种⽹格的效果 属性 layoutLeftMargin左侧边距layoutRig…

Windows 安装 Maven 并配置环境变量

一、简介 Maven 是一款基于 Java 平台的项目管理和整合工具&#xff0c;用来构建项目的。也就是清理、编译、测试、运行、打包、安装整个过程都交给 Maven 管理&#xff0c;整个过程就是构建。 二、安装 Java JDK Maven 依赖 Java JDK&#xff0c;如果本机没有安装过 Java 的…

NASA:第三版大气痕量分子光谱(ATMOS)2 级产品,包含垂直高度(千米)网格上的痕量气体

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 ATMOS L2 Trace Gases on Altitude Grid, Fixed Field Format V3 (ATMOSL2AF) at GES DISC 简介 高度网格上的 ATMOS L2 跟踪气体&#xff0c;固定字段格式 V3 (ATMOSL2AF) 这是第三版大气痕量分子…

CMake学习笔记:项目的导出和安装-install命令

一、基本语法与概念 1.文件的安装 install(FILES) 用Poco库 usr/local/poco/CMakeLists.txt中内容 举例如下&#xff1a; 2.目标安装 install(TARGETS) 3.导出目标与导出配置文件 将目标mylib导出名为MyModules的目标。MyModules导出目标包含了关于mylib的所有信息&#xf…

【初阶数据结构】冒泡排序和选择排序(用C语言实现,主要讲思维)

文章目录 前言1. 冒泡排序1.1 算法思想1.2 冒泡排序的代码实现1.3 冒泡排序算法的改进 2. 选择排序2.1 算法思想2.2 选择排序的代码实现 3. 写排序算法的小技巧 前言 讲到排序相信大家一定对一种排序很熟悉&#xff0c;它的名字就叫做冒泡排序。这个排序大家在学习各种语言时&…

【优选算法】(第二十六篇)

目录 两数相加&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 两两交换链表中的节点&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 两数相加&#xff08;medium&#xff09; 题目解析 1.题目链接&#xff1a;. - 力扣&#xff08;…

随着硬件水平的提升,LabVIEW有哪些过去的编程方法被淘汰掉了

随着硬件水平的不断提升&#xff0c;尤其是处理器性能、存储能力、通信速度等方面的飞跃&#xff0c;LabVIEW的一些早期编程方法逐渐被更高效、现代的编程技术所取代。以下是一些随着硬件升级而逐步淘汰的LabVIEW编程方法和技术&#xff1a; 1. 低效的数据流传输方式 过去由于…

SQL执行顺序是如何工作的,为什么它如此重要?

&#x1f3af;SQL执行顺序是如何工作的&#xff0c;为什么它如此重要&#xff1f; SQL查询按照以下顺序执行其语句&#xff1a; 1️⃣FROM / JOIN 2️⃣WHERE 3️⃣GROUP BY 4️⃣HAVING 5️⃣SELECT 6️⃣DISTINCT 7️⃣ORDER BY 8️⃣LIMIT / OFFSET 你在每个步骤中实现的技…

特定类型的图与应用 - 离散数学系列(六)

目录 1. 树和生成树 树的定义 生成树与最小生成树 2. 二分图 二分图的定义 示例&#xff1a;最大匹配问题 3. 欧拉图与哈密顿图 欧拉图 哈密顿图 4. 实际应用场景 1. 文件系统中的树结构 2. 网络优化中的最小生成树 3. 社交网络分析 5. 例题与练习 例题1&#xf…

sv标准研读第十三章-task和function

书接上回&#xff1a; sv标准研读第一章-综述 sv标准研读第二章-标准引用 sv标准研读第三章-设计和验证的building block sv标准研读第四章-时间调度机制 sv标准研读第五章-词法 sv标准研读第六章-数据类型 sv标准研读第七章-聚合数据类型 sv标准研读第八章-class sv标…

【直接原地起飞】3DMAX2025热门插件合集来啦!

强烈推荐8款3DMAX2025热门插件&#xff01; 在3DMAX的广阔世界里&#xff0c;插件如同魔法工具&#xff0c;为设计师们打开了无限创意的大门。今天&#xff0c;我们精心挑选了8款热门插件&#xff0c;它们不仅功能强大&#xff0c;而且易于上手&#xff0c;定能让你的3D设计之旅…