Linux_实现TCP网络通信

news2024/11/14 14:54:05

目录

1、实现服务器的逻辑 

1.1 socket

1.2 bind

1.3 listen

1.4 accept

1.5 read

1.6 write 

1.7 服务器代码 

2、实现客户端的逻辑

2.1 connect 

2.3 客户端代码

3、实现服务器与客户端的通信

结语 


前言:

        在Linux下,实现传输层协议为TCP的套接字进行网络通信,网络层协议为IPv4,需要用到的接口如下:实现服务器的接口有(socket、bind、listen、accept、read、write),实现客户端的接口有(socket、connect、write、read)。

        实现方式:因为网络通信的本质是进程间通信在云服务器上创建一个服务器进程和一个客户端进程,服务器进程先启动,然后让客户端链接到服务器上,至此客户端可以向服务器发送消息,并且服务器收到消息后可以给对方反馈信息。

        TCP通信示意图如下:

1、实现服务器的逻辑 

        将服务器封装成一个类,服务器的端口号IP地址以及网络描述符(一种类似文件描述符的字段)作为类的成员变量,这样做的好处是对软件实现分层,方便维护代码。按照下文的接口顺序调用实现服务器类。

1.1 socket

        实现网络通信的第一步基本都是调用socket接口,目前已经明确传输层协议为TCP,网络层协议为IPv4,因此可以直接调用socket,该函数介绍如下:

#include <sys/types.h>          
#include <sys/socket.h>
 
int socket(int domain, int type, int protocol);
//domain表示网络层协议,填AF_INET表示IPv4,填AF_INET6表示IPv6
//type表示传输层协议,SOCK_STREAM为TCP,SOCK_DGRAM为UDP
//protocol表示指定特定的协议,默认填0即可
 
//调用成功返回一个类型文件描述符的网络描述符,失败返回-1

1.2 bind

        在网络通信中,bind函数与socket函数密不可分,他是用来绑定网络描述符和地址信息的,目的是让后续的通信可以直接通过网络描述符进行。在使用bind函数前,需要先创建一个包含地址信息的结构体,IPv4对应的结构体类型是struct sockaddr_in,该结构体里需要程序员手动填写3个信息,分别是:1、TCP/UDP,2、自定义的端口号,3、该主机的IP地址。


        体现地址信息的伪代码如下:

struct sockaddr_in local;//定义变量
memset(&local, 0, sizeof(local));//对内部内容清零
local.sin_family = AF_INET;//填写ip协议
local.sin_port = htons(port_);//填写端口号
inet_aton(ip_.c_str(), &(local.sin_addr));//填写ip地址

        其中由于网络传输数据规定以大端字节序传输,因此填写端口号和ip时需要对其进行大端字节序的转换,这里可以调用htons函数和inet_aton函数实现转换,这两个函数的介绍如下:

#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
//将主机字节序转换成网络大端字节序,以返回值的形式转换

#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr*addr);
//将字符串形式的ip转换成网络字节序,并直接赋值给addr指向的空间

        在地址信息完善后,紧接着就是调用bind函数进行绑定,bind函数介绍如下:

int bind(int socket, const struct sockaddr *address,
              socklen_t address_len);
//socket表示要绑定的网络描述符
//address表示指向的地址信息变量
//address_len表示地址信息变量的大小
 
//调用成功返回0,失败返回-1并设置错误码

1.3 listen

         与UDP不同的是,UDP绑定之后就可以用网络描述符进行接收和发送消息的操作了,但是TCP不一样,TCP的绑定完成后需要对网络描述符再进行监听操作,监听实际上是一种抽象的连接概念(因为TCP是有连接的,而UDP是无连接的,所以需要监听),即调用listen函数。listen函数的介绍如下:

#include <sys/socket.h>  
  
int listen(int sockfd, int backlog);
//sockfd表示要监听的网络描述符
//backlog表示设置套接字的链接队列的最大数量

//调用成功返回0,失败返回-1并设置错误码

1.4 accept

        待上述3个函数完成基础工作后,接下来就是最重要的连接环节了,accept函数的作用是和客户端进行连接,但是注意链接不意味着直接接收对方发来的数据,连接完成之后才能开始正常通信。accept函数介绍如下:

#include <sys/socket.h>  
  
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd表示服务器的网络描述符,即通过观察该描述符得知是否有客户端链接
//addr是指向地址信息的指针,用来保存客户端的地址信息
//addrlen表示存储客户端地址信息的结构体大小

//重要的是调用成功时返回一个新的网络描述符,失败返回-1

         accept的重要之处在于若连接成功,则返回一个新的网络描述符,而该网络描述符才是后续通信的”端口“,意味着现在服务器有两个网络描述符了,一个是socket返回的,一个是accept返回的。可以这样理解他们:socket返回的描述符表示该服务器的总描述符,类似二叉树的根结点,而accept返回的描述符就是用于服务多个客户端的次描述符,即给每个来连接的客户端都分配一个次描述符,类似二叉树的叶子结点。(但是当前服务器的实现只能服务单客户端,因为他不是多线程的)

        题外话:UDP通信只有一个网络描述符,所有的客户端都是通过这一个描述符来进行和服务器的通信

1.5 read

        链接完毕之后,说明此时客户端和服务器双方建立了联系,服务器的下一步就是读取客户端发送的信息,因为TCP是面向字节流的,因此可以使用文件操作的方式进行流读取,即使用read函数读取客户端发送的数据,read函数介绍如下:

#include <unistd.h>  
  
ssize_t read(int fd, void *buf, size_t count);
//fd表示要读取的文件描述符
//buf表示将读取数据存放至缓冲区的缓冲区指针
//count表示读取的字节数

1.6 write 

        服务器不仅仅可以接收数据,还可以向客户端反馈数据,因为accept让服务器和客户端之间有了唯一的网络描述符,因此服务器可以通过该描述符对客户端反馈信息,而无需像UDP需要记录对方的地址信息才可以反馈信息。write函数介绍如下:

#include <unistd.h>  
  
ssize_t write(int fd, const void *buf, size_t count);
//fd表示要写入的文件描述符
//buf表示指向写入的内容
//count表示要写入的字节

1.7 服务器代码 

        实现服务器类的代码如下:

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";//云服务器默认通信ip
const int backlog = 10; // 但是一般不要设置的太大

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip) 
    : listensock_(defaultfd), port_(port), ip_(ip)
    {
    }
    void InitServer()
    {
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        printf("创建套接字成功, listensock_: %d\n", listensock_);

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));

        bind(listensock_, (struct sockaddr *)&local, sizeof(local));
        printf("绑定套接字成功, listensock_: %d\n", listensock_);

        // Tcp是面向连接的,服务器一般是比较“被动的”,
        //服务器一直处于一种,一直在等待连接到来的状态
        listen(listensock_, backlog);
        printf("监听套接字成功, listensock_: %d\n", listensock_);
    }

    void Start()
    {
        printf("TCP服务器正在运行....");
        for (;;)
        {
            // 1. 获取新连接
            struct sockaddr_in client;//保存客户端的地址信息
            socklen_t len = sizeof(client);
            //类似recvfrom
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
            
            uint16_t clientport = ntohs(client.sin_port);//将客户端端口号拿出来
            char clientip[32];
            //将ip从网络字节序转换成主机序列并写进缓冲区clientip内
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            printf("链接到一个客户端, sockfd: %d, client port: %d\n", 
            sockfd, clientport);

            // 2. 根据新连接来进行通信
            Service(sockfd);
            close(sockfd);//关闭文件描述符
        }
    }
    void Service(int sockfd)
    {
        // 测试代码
        char buffer[4096];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "客户端说# " << buffer << std::endl;
                std::string echo_string = "服务器回答# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if(n==0)
            {
                std::cout<<"读到文件末尾"<<std::endl;
                sleep(1);
                break;
            }
            else
            {
                std::cout<<"读到出错"<<std::endl;
                sleep(1);
                break;
            }
        }
    }
    ~TcpServer() 
    {
        if(listensock_>0) close(listensock_);
    }

private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

2、实现客户端的逻辑

        与服务器相比,客户端的实现逻辑要简单许多,因为服务器要将底层的基础设施建设好,而客户端只需要完成连接这一个动作即可,然后就是发送数据和接收反馈,当然,客户端的第一步也是调用socket拿到属于客户端的网络描述符,因为后续的通信工作要通过该描述符进行。

2.1 connect 

        客户端只需要做连接动作即可,而连接动作是依靠connect函数实现的,connect函数介绍如下:

#include <sys/types.h>  
#include <sys/socket.h>  
  
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//sockfd表示要与对方建立连接的客户端网络描述符
//addr指向地址信息的地址,该地址信息即服务器的地址信息
//addrlen表示addr指向的地址信息的大小

        所以调用connect前必须知道服务器的地址信息:即ip地址和端口号,可以通过命令行参数的形式传给客户端代码,并且拿到地址信息后也是要构造struct sockaddr_in变量将信息填写进去。

2.3 客户端代码

        实现客户端代码如下(注意客户端直接在main函数中实现,无需在对其进行封装,因为客户端的代码简单):

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    //从命令行参数获取服务器的端口和ip
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    // 初始化地址信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    // 将ip转换成网络字节序
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr)); 

    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字

    // 客户端发起connect的时候,进行自动随机bind
    //链接服务器
    int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        std::cout << "链接失败, reconnect: " << std::endl;
        sleep(2);
    }

    while (true)
    {
        std::string message;
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);

        int n = write(sockfd, message.c_str(), message.size());//发送数据
        if (n < 0)
        {
            std::cout << "write出错" << std::endl;
            // break;
        }

        char inbuffer[4096];
        n = read(sockfd, inbuffer, sizeof(inbuffer));//接收反馈
        if (n > 0)
        {
            inbuffer[n] = 0;
            std::cout << inbuffer << std::endl;
        }
    }
    close(sockfd);

    return 0;
}

3、实现服务器与客户端的通信

        实现双方通信前,必须满足双方都为进程,但是服务器目前还只是一个类,因此要把服务器类实例化在main函数中,让服务器也变成进程。实现服务器进程的代码如下: 

#include "TCPser.hpp"
#include <iostream>
#include <memory>

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));
    tcp_svr->InitServer();
    tcp_svr->Start();

    return 0;
}

        运行结果:

        特别注意:通信时,必须保证服务器先运行起来,若客户端先运行起来,则会在connect处连接失败,因为找不到对应的服务器端口号。 

结语 

        以上就是关于在Linux下实现TCP网络通信的讲解,实现TCP的主要思路是了解套接字的作用,以及理清对套接字相关函数的调用顺序,并且其次TCP通信的原理。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!! 

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

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

相关文章

Oracle配置TCPS加密协议测试

文章目录 一、环境信息二、配置过程1.创建证书2.监听配置2.1.配置sqlnet.ora2.2.配置listener.ora文件2.3.配置tnsnames.ora文件2.4.重载监听 3.数据库本地测试3.1. tcps登录测试3.2.日志监控 一、环境信息 操作系统&#xff1a;Linux 版本信息&#xff1a;Oracle 19c 参考文档…

威联通启用SFTP并安装内网穿透工具实现远程管理家中NAS中的资源

文章目录 前言1. 威联通NAS启用SFTP2. 测试局域网访问3. 内网穿透安装配置3.1 威联通安装cpolar内网穿透3.2 创建隧道3.3 测试公网远程访问 4. 配置固定公网TCP端口地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址4.3 测试使用固定TCP端口地址远程连接威联通SFTP 前言…

怎么使用github上传XXX内所有文件

要将 目录中的所有文件上传到 GitHub&#xff0c;你可以按照以下步骤进行&#xff1a; 创建一个新的 GitHub 仓库 登录到你的 GitHub 账户。 点击右上角的加号&#xff08;&#xff09;&#xff0c;选择 “New repository”。 输入仓库名称&#xff08;例如&#xff1a;202407…

【Python】Facebook开源时间序列数据预测模型Prophet

文章目录 一、简介二、项目的文件解读三、Prophet类主要方法和参数3.1 主要参数3.2 主要方法 四、用法示例 一、简介 Prophet 是由 Facebook 开发的一个开源工具&#xff0c;用于时间序列数据的预测。它特别适用于处理具有强季节性和趋势的时间序列数据&#xff0c;并且对节假…

代码审计 | .NET SqlSugar框架注入漏洞

01阅读须知 此文所节选自小报童《.NET 代码审计》专栏&#xff0c;主要内容有涉及的.NET目录和文件操作、SQL注入方向的敏感函数、还有不安全的配置导致的漏洞挖掘思路&#xff0c;对.NET代码审计感兴趣的朋友们可以解锁该电子报刊&#xff0c;解锁更多的报刊内容。 02基本介…

央国企改革关键年!契约锁电子签章助力业务全程数字化场景落地

契约锁作为行业领先的电子签及印控厂商已经服务了400多家大型央企、国企单位&#xff0c;其中国资委下属的98家一级央企&#xff0c;近三分之一选择使用契约锁。 央国企改革新三年计划已实施过半&#xff0c;改革进入关键之年&#xff0c;全国各地央国企全面发力、加快数字化建…

技术成神之路:设计模式(九)观察者模式

介绍 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为设计模式。它允许一个对象&#xff08;称为主题或可观察者&#xff09;来监视并通知一组依赖于这个对象的其他对象&#xff08;称为观察者&#xff09;&#xff0c;以便在主题状态发生变化时自动更新观察者的…

python机器学习8--自然语言处理(1)

1.基本定义&#xff1a; 语义&#xff1a;就是一句话的重点是什么。 自定词汇&#xff1a;因为语言、文字太多&#xff0c;自定和处理你所关心的重点词汇。 简体转繁体代码 from opencc import OpenCCtext1 "我去过清华大学" openCC OpenCC(s2t) line openCC.…

【系统架构设计师】计算机组成与体系结构 ⑯ ( 奇偶校验码 | CRC 循环冗余码 | 海明码 | 模 2 除法 )

文章目录 一、校验码1、校验码由来2、奇偶校验码3、CRC 循环冗余码 ( 重点考点 )4、海明码校验 ( 软考不经常考到 ) 二、CRC 循环冗余码 ( 重点考点 )1、模 2 除法概念2、模 2 除法步骤3、模 2 除法示例4、CRC 循环冗余码示例 15、CRC 循环冗余码示例 2 参考之前的博客 : 【计…

基于微信小程序的自习室选座系统/基于Java的自习室选座系统/自习室管理系统的设计与实现

获取源码联系方式请查看文章结尾&#x1f345; 摘要 自习室选座是学校针对用户必不可少的一个部分。在学校的整个过程中&#xff0c;学生担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类微信小程序自习室选座也在不断改进。本课题所设计的小程序自习室选座系…

npm下载pnpm

一、提供node_global和node_cache的文件夹 若不存在&#xff0c;可自行新建文件夹 二、配置环境变量 配置NODE_PATH变量&#xff1a; 配置Path变量&#xff1a; 三、执行cmd指令 npm config set prefix "D:\Configure\nodejs\node_global" npm config set cache &…

cdga|数据治理难题破解:策略与实施路径

随着信息技术的飞速发展&#xff0c;数据已成为企业最宝贵的资产之一。然而&#xff0c;数据治理的复杂性也随之增加&#xff0c;如何有效管理、保护和利用数据成为摆在企业面前的一大难题。本文将从数据治理的挑战入手&#xff0c;探讨其破解策略与实施路径。 数据治理的挑战 …

华为IoTDA平台下发MQTT消息

前一篇博文介绍了如何使用MQTTX连接华为IoTDA平台并上报消息&#xff0c;本文介绍一下如何下发消息。 IoT设备接入平台支持MQTT协议设备进行命令下发&#xff0c;属性设置&#xff0c;属性查询以及消息下发等操作。 在进入指定设备的页面后&#xff0c;选择“云端下发”&#…

洛谷 P9854 [CCC 2008 J1] Body Mass Index

这题让我们计算出 BMI 值&#xff0c;随后判断属于哪个等级。 BMI 值计算公式&#xff1a; ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​。 BMI 范围 对应信息 …

element-plus时间组件el-date-picker只能选择当前及之前日期

<el-date-picker v-model"timeVal" type"daterange" value-format"YYYY-MM-DD" range-separator"To" start-placeholder"开始时间" end-placeholder"结束时间" />默认是这样的&#xff0c;需要绑定disabled…

安卓手机怎么恢复出厂设置?之后如何恢复数据?3个技巧解决

随着时间的推移&#xff0c;手机可能会遇到性能下降、系统崩溃或其他问题。在这种情况下&#xff0c;恢复出厂设置成为了一个常见的解决方案。安卓手机怎么恢复出厂设置&#xff1f;如何在恢复出厂设置后恢复数据呢&#xff1f;本文将详细解答这些问题&#xff0c;并分享3个数据…

JavaWeb-Servlet(1)-Servlet程序、请求处理、继承体系

目录 什么是Servlet 手动实现Servlet程序 ​编辑url地址如何定位到Servlet程序去访问 Servlet的生命周期 ​编辑GET和POST请求的分发处理 通过继承HttpServlet类实现Servlet程序 IDEA菜单生成Servlet程序 Servlet类的继承体系 ServletConfig类 ServletContext类 什么…

Elasticsearch 7.x入门学习-Java API操作

1 创建项目 在idea开发工具中创建Maven项目 修改 pom 文件&#xff0c;增加 Maven 依赖关系 <dependencies><dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.8.0</versi…

2024恶意样本提交激励计划:年中特别回馈惊喜来袭

恶意样本提交激励计划 “恶意样本提交激励计划”由CACTER邮件安全发起&#xff0c;主要目的是更好地帮助客户排查潜在风险问题&#xff0c;同时丰富Coremail邮件安全大数据中心威胁邮件云端特征库&#xff0c;共同提升Coremail邮件安全大数据中心的识别能力&#xff0c;共同建…

别错过!2024年苹果iPhone AI革命:揭秘技术突破与未来蓝图

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 2024年&#xff0c;苹果公司的iPhone手机在技术创新和系统升级方面展现出一系列亮点&#xff0c;预示着苹果在智能手机领域的持续领导地位。以下…