文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

OpenHarmony TCP 通信编程实战

2024-11-29 19:46

关注

想了解更多关于开源的内容,请访问:

51CTO 鸿蒙开发者社区

https://ost.51cto.com

前言

本人是一名大一学生,有幸被选拔进了深圳技术大学第一届开源鸿蒙菁英班,并在暑期培训进行线上分享,故将讲解的内容也制作成帖子发上来作为学习笔记。在准备分享的过程中,我基于学长们的先前成果,结合开源鸿蒙源码的最新版本进行了相应的调整和优化,帮助大家更好地理解和应用开源鸿蒙技术。本文旨在探讨TCP(Transmission Control Protocol,传输控制协议)通讯的相关知识。通过本文,您将了解TCP协议的工作原理,以及如何运用这一协议进行通讯程序设计与实现。

环境

一、TCP 通信介绍

1.概念

传输控制协议(TCP,Transmission Control Protocol)是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。

2.特性

3.两个重要概念:客户端与服务端

4.指令认识

ifconfig		# 用于Linux和OpenHarmony,常用于查看IP
ipconfig		# 用于Windows,常用于查看IP
ping 		# 测试网络连通性

5.本节课使用工具 – NetAssist

二、Socket 编程(套接字编程)

1.socket()

socket() 函数是用于创建一个新的套接字(socket)的系统调用函数。套接字是一种通信机制,允许进程通过网络进行通信。在网络编程中,socket() 函数是一种创建套接字的标准方法,它通常在客户端和服务器端代码中都会用到。

2.close()

3.sockaddr_in

struct sockaddr_in{
	sa_family_t sin_family; 
	in_port_t sin_port; 
	struct in_addr sin_addr; 
	unint8_t sin_zero[8]; /* 指定套接字的通信地址,从而确立通信的目标。通常未使用
};

4.bind()

if (bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1) 
{
	perror("Error binding socket");
	close(sockfd);
}

5.listen()

if (listen(server_sock, MAX_CLIENTS) == -1)
{
	perror("Failed to listen");
	close(server_sock);
}

6.accept()

if (accept(sockfd,(struct sockaddr *)&client_addr,&client_len) == -1) 
{
	perror("Error accepting connection");
	close(sockfd);
}

7.connect()

if (connect(client_sock,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0) 
{
	perror("Failed to connect to server");
}

8.recv() 与 read()

在TCP通信中,recv和read函数都是用来从socket接收数据的,但它们在不同的编程语言和平台上有一些细微的区别。

(1)recv()

(2)read()

9.send() 与 write()

在TCP通信中,send()和write()函数在TCP通信中都起着发送数据的作用。

(1)send()

(2)write()

10.网络编程中可能用到的几个函数

三、TCP通信实例

本文文件结构如下:

在OpenHarmony源码根目录下创建文件夹Mysample,下创文件夹tcp_demo:

1.服务端实例

(1)实现流程

(2)代码实现

"Mysample\tcp_demo\src\tcp_demo.cpp"

#include  // 引入标准输入输出流库
#include   // 引入标准输入输出库
#include  // 引入字符串处理库
#include  // 引入IP网络库
#include  // 引入地址转换库
#include  // 引入套接字库
#include     // 引入Unix系统调用库

// 服务器端口号
#define SERVER_PORT 4567
// 最大客户端数量
#define MAX_CLIENTS 5
// 缓冲区大小
#define TCP_BUFFER_SIZE 1024

int main()
{
    // 创建服务器套接字和客户端套接字
    int server_sock, client_sock;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    // 创建一个服务端TCP套接字
    server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sock == -1)
    {
        perror("Failed to create socket"); // 创建套接字失败,打印错误信息
        exit(EXIT_FAILURE); // 退出程序
    }

    // 设置服务端地址
    server_addr.sin_family = AF_INET; // 设置地址族为IPv4
    server_addr.sin_port = htons(SERVER_PORT); // 设置端口号,htons确保端口号为网络字节序
    server_addr.sin_addr.s_addr = INADDR_ANY; // 设置IP地址为INADDR_ANY,表示接受任何接口的连接

    // 绑定套接字
    if (bind(server_sock, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    {
        perror("Failed to bind socket"); // 绑定套接字失败,打印错误信息
        close(server_sock); // 关闭套接字
        exit(EXIT_FAILURE); // 退出程序
    }

    // 开始监听客户端连接请求
    if (listen(server_sock, MAX_CLIENTS) == -1)
    {
        perror("Failed to listen"); // 监听失败,打印错误信息
        close(server_sock); // 关闭套接字
        exit(EXIT_FAILURE); // 退出程序
    }
    std::cout << "Server is listening on port " << SERVER_PORT << std::endl; // 打印服务器监听端口信息

    // 主循环,等待客户端连接
    while(true)
    {
        // 接受客户端连接请求
        client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);
        if (client_sock == -1)
        {
            perror("Failed to accept connection"); // 接受连接失败,打印错误信息
            continue; // 继续下一次循环
        }

        // 打印出连接成功的客户端的IP地址和端口号
        std::cout << "Accepted connection from " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << std::endl;

        // 发送数据给客户端
        char request[] = "Hello, here is server!"; // 定义要发送的字符串
        size_t bytes_write = write(client_sock, request, strlen(request)); // 发送数据,并返回发送的字节数
        if (bytes_write == -1)
        {
            perror("Failed to write data"); // 发送数据失败,打印错误信息
            close(client_sock); // 关闭客户端套接字
            exit(EXIT_FAILURE); // 退出程序
        }

        // 接受客户端发送的数据
        char buffer[TCP_BUFFER_SIZE]; // 定义缓冲区用于接收数据
        size_t bytes_read = read(client_sock, buffer, TCP_BUFFER_SIZE); // 从客户端读取数据,并返回读取的字节数
        if (bytes_read == -1)
        {
            perror("Failed to read data"); // 读取数据失败,打印错误信息
            close(client_sock); // 关闭客户端套接字
            exit(EXIT_FAILURE); // 退出程序
        }
        else
        {
            std::cout << "Received data from client: " << buffer << std::endl; // 打印接收到的数据
            break; // 退出循环
        }
    }

    // 关闭服务器套接字
    close(server_sock);

    return 0;
}

2.客户端实例

(1)实现流程

(2)代码实现

"Mysample\tcp_demo\src\tcp_demo.cpp"

#include           // 引入输入输出流库
#include            // 引入标准输入输出库
#include           // 引入字符串处理库
#include            // 引入C风格字符串处理库
#include       // 引入socket编程库
#include       // 引入网络地址结构定义库
#include        // 引入网络地址转换库
#include           // 引入Unix标准库,提供close函数

#define TCP_BUFFER_SIZE 1024 // 定义TCP缓冲区大小为1024字节

int main()
{
    int client_sock = socket(AF_INET, SOCK_STREAM, 0); // 创建一个IPv4的TCP socket
    if (client_sock == -1) // 如果socket创建失败
    {
        perror("Failed to create socket"); // 输出错误信息
        exit(EXIT_FAILURE); // 退出程序
    }

    struct sockaddr_in server_addr; // 创建一个服务器地址结构体

    int SERVER_PORT; // 服务器端口变量
    std::string SERVER_ADDR; // 服务器地址变量
    std::cout << "Input server address: "; // 输出提示信息
    std::cin >> SERVER_ADDR; // 从标准输入读取服务器地址
    std::cout << "Input server port: "; // 输出提示信息
    std::cin >> SERVER_PORT; // 从标准输入读取服务器端口

    server_addr.sin_family = AF_INET; // 设置地址族为IPv4
    server_addr.sin_port = htons(SERVER_PORT); // 设置端口,网络字节序
    if (inet_pton(AF_INET, SERVER_ADDR.c_str(), &server_addr.sin_addr) <= 0) // 如果地址转换失败
    {
        std::cerr << "Invalid address/ Address not supported" << std::endl; // 输出错误信息
        return -1; // 返回错误
    }

    while (true) // 无限循环尝试连接
    {
        if (connect(client_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) // 如果连接失败
        {
            perror("Failed to connect to server"); // 输出错误信息
            close(client_sock); // 关闭socket
            exit(EXIT_FAILURE); // 退出程序
        }
        std::cout << "Connected to server " << inet_ntoa(server_addr.sin_addr) << ":" << ntohs(server_addr.sin_port) << std::endl; // 输出连接成功信息

        // 向服务器发送数据
        char request[] = "Hello, here is client!"; // 创建请求字符串
        size_t bytes_write = write(client_sock, request, strlen(request)); // 发送数据
        if (bytes_write == -1) // 如果发送失败
        {
            perror("Failed to write data"); // 输出错误信息
            close(client_sock); // 关闭socket
            exit(EXIT_FAILURE); // 退出程序
        }

        // 从服务器接收数据
        char buffer[TCP_BUFFER_SIZE]; // 创建缓冲区
        size_t bytes_read = read(client_sock, buffer, TCP_BUFFER_SIZE); // 读取数据
        if (bytes_read == -1) // 如果接收失败
        {
            perror("Failed to read data"); // 输出错误信息
            close(client_sock); // 关闭socket
            exit(EXIT_FAILURE); // 退出程序
        }
        else // 如果接收成功
        {
            std::cout << "Received data from server: " << buffer << std::endl; // 输出接收到的数据
            break; // 退出循环
        }
    }

    // 关闭
    close(client_sock); // 关闭socket

    return 0; // 程序成功结束
}

3.编译构建文件

"C:\Users\LIGANG\Desktop\Mysample\tcp_demo\BUILD.gn"

import("//build/ohos.gni") # 导入编译模板
    ohos_executable("tcp") { # 可执行模块
    sources = [ # 模块源码
        "src/tcp_demo.cpp"
    ]
    cflags = []
    cflags_c = []
    cflags_cc = []
    ldflags = []
    configs = []
    deps =[] # 部件内部依赖
    part_name = "tcp_demo" # 所属部件名称,必选
    install_enable = true # 是否默认安装(缺省默认不安装),可选
}

"C:\Users\LIGANG\Desktop\Mysample\tcp_demo\bundle.json"

{
    "name": "@ohos/tcp_demo",
    "description": "",
    "version": "3.1",
    "license": "Apache License 2.0",
    "publishAs": "code-segment",
    "segment": {
        "destPath": "Mysample/tcp_demo"
    },
    "dirs": {},
    "scripts": {},
    "component": {
        "name": "tcp_demo",
        "subsystem": "Mysample",
        "syscap": [],
        "features": [],
        "adapted_system_type": [
            "standard"
        ],
        "rom": "10KB",
        "ram": "10KB",
        "deps": {
            "components": [],
            "third_party": []
        },
        "build": {
            "sub_component": [
                "//Mysample/tcp_demo:tcp"
            ],
            "inner_kits": [],
            "test": []
        }
    }
}

4.编译

./build.sh --product-name {product_name} #全量编译
./build.sh --product-name {product_name}  --build-target {target_name} #单独编译部件
./build.sh --product-name {product_name}  --build-target {target_name} --fast-rebuild #快速重建
hb set #设置编译参数
hb build #全量编译
hb build -T {target_name} #单独编译部件
hb build -T {target_name} --fast-rebuild #快速重建

5.烧录

6.测试并执行

将开发板连接上网络,通过hdc.exe工具执行命令ifconfig查看开发板IP地址:

客户端程序测试:

7.番外

此程序意在说明一台设备既可以作为客户端也可以作为服务端,程序实现了本机先作为服务端与PC主机进行TCP通信,后两者交换身份,本机作为客户端。笔者测试过,程序无误可以正常运行,在此不做赘述。有兴趣的读者可以作尝试,

"C:\Users\LIGANG\Desktop\Mysample\tcp_demo\src\tcp_demo.cpp"

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SERVER_PORT1 4567 // 服务器端口号
#define SERVER_PORT2 7654 // 服务器端口号
#define MAX_CLIENTS 5 // 最大客户端数量
#define TCP_BUFFER_SIZE 1024 // 缓冲区大小

int main()
{
    // ------------------------------------本机作为服务端--------------------------------------------


    int server_sock, client_sock; // 服务器套接字和客户端套接字
    struct sockaddr_in server_addr, client_addr; // 服务器地址和客户端地址
    socklen_t client_addr_len = sizeof(client_addr); // 客户端地址长度

    // 创建一个服务端TCP套接字
    server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sock == -1)
    {
        perror("Failed to create socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务端地址
    server_addr.sin_family = AF_INET; // 使用IPv4协议
    server_addr.sin_port = htons(SERVER_PORT1); // 端口号
    server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的IP地址

    // 绑定套接字
    if (bind(server_sock, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    {
        perror("Failed to bind socket");
        close(server_sock);
        exit(EXIT_FAILURE);
    }

    // 开始监听客户端连接请求
    if (listen(server_sock, MAX_CLIENTS) == -1)
    {
        perror("Failed to listen");
        close(server_sock);
        exit(EXIT_FAILURE);
    }
    std::cout << "Server is listening on port " << SERVER_PORT1 << std::endl;

    while(true)
    {
        // 接受客户端连接请求
        client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);
        if (client_sock == -1)
        {
            perror("Failed to accept connection");
            continue;
        }

        // 打印出连接成功的客户端的IP地址和端口号
        std::cout<<"Accepted connection from "<

想了解更多关于开源的内容,请访问:

51CTO 鸿蒙开发者社区

https://ost.51cto.com

来源:51CTO 鸿蒙开发者社区内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯