应用层简单实现udp / tcp网络通信

news2024/12/29 10:26:41

一、常见网络接口总结

1、创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)

int socket(int domain, int type, int protocol);

domain:AF_INET:网络通信,AF_LOCAL:本地通信

type:UDP:SOCK_DGRAM,TCP:SOCK_STREAM

protocol:协议编号一开始设0

返回值:文件描述符,Linux下一切皆文件

2、绑定端口号 (TCP/UDP, 服务器)

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

socket:创建socket文件描述符的返回值

address:输入型参数,传递一个带有服务器信息(IP, 端口号, AF_INET)的结构体对象,具体详见http://t.csdnimg.cn/aY2O0

address_len:结构体 address 大小

3、开始监听socket (TCP, 服务器)

int listen(int socket, int backlog);

将套接字设为监听状态,随时准备别人链接服务器

backlog:表示大小,设为8

4、接收请求 (TCP, 服务器)

int accept(int socket, struct sockaddr* address, socklen_t* address_len);

服务器监听状态之后,接受客户端链接

address:输出型参数,得到客户端的信息

address_len:输出型参数,address大小

返回值:文件描述符

对比 accept 函数返回值和 socket

就好比一家餐馆,socket 是门口拉客的,一般叫 listen_sockfd 监听套接字。返回值是客人进餐馆接待客人的服务员,为用户提供网络IO的服务套接字。所以之后真正开始服务的函数用的是accept函数返回值

5、建立连接 (TCP, 客户端)

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

客户端在有需要时进行链接服务器

address:输入型参数,传入带有服务器信息的结构体,表示我要链接你这个服务器。

address_len:结构体 address 大小

6、把字符串ip转化成网络序列ip

in_addr_t inet_addr(const char *cp);(不安全)
int inet_pton(int af, const char *src, void *dst);(推荐)

dst:直接填 struct sockaddr_in 中的 sin_addr

7、把网络序列ip转化成字符串ip

char *inet_ntoa(struct in_addr in);(不安全)内部只维护一块静态空间存储IP,会导致覆盖问题

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);(推荐)
af:AF_INET:网络通信,AF_LOCAL:本地通信

src:4字节网络序列IP

dst:传入自己定义的缓冲区存放ip

size:缓冲区大小

8、从网络中收数据

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

用于tcp读取数据

flag:设置0,阻塞读取

由于tcp有链接,且sockfd本质是文件描述符,可以用read函数

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

用于udp读取数据

src_addr:输出型参数,客户端信息

addrlen:输出型参数,src_addr大小

9、在网络中发数据

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

用于tcp发数据

flag:设置0,阻塞发送

由于tcp有链接,且sockfd本质是文件描述符,可以用write函数

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

用于udp发数据

dest_addr:输入型参数,服务器信息

addrlen:dest_addr大小

10、库函数做网络字节序和主机字节序的转换接口

二、udp_echo_server

1、服务器初始化服务

    void InitServer()
    {
        // 1.创建socket文件描述符 一般是3
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            LOG(FATAL, "socket error\n");
            exit(SOCKET_ERROR); 
        }
        LOG(DEBUG, "socket create success, _sockfd = %d\n", _sockfd);

        // 2.先填充跨网络地址信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        // 通信方式
        local.sin_family = AF_INET;
        // 主机转成网络序列
        local.sin_port = htons(_port);
        // 要4字节ip 要网络序列ip
        // local.sin_addr.s_addr = inet_addr(_localip.c_str());

        // 服务器端进行任意IP绑定
        local.sin_addr.s_addr = INADDR_ANY;


        // 3.绑定端口号 把地址信息绑定进套接字
        int n = ::bind(_sockfd, (struct sockaddr*)&local, sizeof local);
        if(n < 0)
        {
            LOG(FATAL, "bind error\n");
            exit(BIND_ERROR); 
        }
        LOG(DEBUG, "bind success\n");
    }

(1)创建sockfd文件描述符

(2)为了绑定函数,初始化 struct socket_in server,并填入服务器信息

注意:服务器一定要绑定 INADDR_ANY 任意IP,这样才能接收到全部IP地址主机的数据

(3)绑定端口号以及一系列服务器信息

2、服务器启动服务

    // 绑定成功就开始服务
    void Start()
    {
        _isrunning = true;
        char inbuffer[1024];
        // 死循环
        while(_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof peer;
            // 服务器收客户端消息
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof inbuffer - 1, 0, (struct sockaddr*)&peer, &len);
            if(n > 0)
            {
                // 获取哪一个客户端的端口号和IP地址
                InetAddr addr(peer);

                inbuffer[n] = 0;
                cout << "[" << addr.Ip() << ":" << addr.Port() << "]#" << inbuffer << endl;
                string echo_string = "[udp_server echo] #";
                echo_string += inbuffer;
                // 服务器发给客户端消息
                sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, len);
            }
        }
    }

至少服务器要一直死循环工作

3、客户端初始化

    // 1.获取服务器IP和端口号
    string server_ip = argv[1];
    uint16_t server_port = stoi(argv[2]);
    // 2.绑定服务器
    // 绑定
    // 客户端端口号一般不让用户自己设定,让客户端OS随机选择端口
    // 客户端一定需要绑定自己的IP地址和端口号,但不是显示绑定
    // 客户端在首次向服务器发送数据时,OS自动给客户端绑定IP和端口号
    struct sockaddr_in server;
    memset(&server, 0, sizeof server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        cerr << "create socket error" << endl;
        exit(1);
    }

(1)获取要连接的服务器信息

(2)初始化 struct socket_in server,并填入服务器信息

(3)创建套接字

(4)重点

客户端不需要显示绑定端口号,也不能自己绑定。但是不代表客户端不要绑定,在第一次客户端给服务器发数据时让客户端的OS随机绑定。

为什么不让用户自己绑定?
服务器可以自己绑定是因为服务器一般只运行一个进程服务,端口号不会冲突,但是客户端不一样,用户的操作系统上会有多个客户端,如果让用户自己设置就很可能端口号冲突,导致用户不能同时启动两个端口号一样的客户端。

4、客户端链接服务器

    while(1)
    {
        //发送的数据
        string line;
        cout << "Please Enter# : ";
        getline(cin, line);


        int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof server);
        if(n > 0)
        {
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            char buffer[1024];
            int m = recvfrom(sockfd, buffer, sizeof buffer - 1, 0, (struct sockaddr*)&temp, &len);
            if(m > 0)
            {
                buffer[m] = 0;
                cout << buffer << endl;
            }
            else
                break;
        }
        else
            break;
    }

三、tcp_echo_server

1、服务器初始化服务

    void InitServer()
    {
        // 1.创建socket
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(_listensockfd < 0)
        {
            LOG(FATAL, "socket create error\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "socket create success, sockfd: %d\n", _listensockfd);
    
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);

        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;


        // 2.绑定
        if(::bind(_listensockfd, (struct sockaddr*)&local, sizeof local) < 0)
        {
            LOG(FATAL, "socket bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO, "bind success\n");

    
        // 3.因为tcp是面向连接的,tcp需要不断能获取链接状态,设置为listen状态
        if(::listen(_listensockfd, gblcklog) < 0)
        {
            LOG(FATAL, "listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO, "listen success\n");
    }

(1)创建sockfd文件描述符

(2)为了绑定函数,初始化 struct socket_in server,并填入服务器信息

注意:服务器一定要绑定 INADDR_ANY 任意IP,这样才能接收到全部IP地址主机的数据

(3)绑定端口号以及一系列服务器信息

(4)开始监听

2、服务器启动服务

    // 内部类 为了拿到新线程要用的sockfd和this指针调用Service函数
    class ThreadData
    {
    public:
        int _sockfd;
        TcpServer* _self;
        InetAddr _addr;
    public:
        ThreadData(int sockfd, TcpServer* p, const InetAddr &addr)
            :_sockfd(sockfd)
            ,_self(p)
            ,_addr(addr)
        {}
    };

    void Loop()
    {
        // 推荐做法 忽略子进程退出信息
        // signal(SIGCHLD, SIG_IGN);
        _isrunning = true;
        struct sockaddr_in client;
        socklen_t len = sizeof client;

        while(_isrunning)
        {
            // 4.获取链接
            int sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len);
            // listensockfd获取失败
            if(sockfd < 0)
            {
                LOG(WARNING, "accept error\n");
                continue;
            }


            // 获取成功
            InetAddr addr(client);
            LOG(INFO, "get a new link, client info: %s\n", addr.AddStr().c_str());

            // 版本一 无法并发访问服务器
            // Service(sockfd, addr);

            // 版本二 多进程版并发访问服务器
            // pid_t id = fork();

            // if(id == 0)
            // {
            //     // 继承下来的父进程的_listensockfd(3)需要在子进程关闭,子进程使用sockfd(4)
            //     ::close(_listensockfd);

            //     // 创建孙子进程后,子进程退出,此时孙子进程成为孤儿进程,执行服务,死活只由OS关心
            //     if(fork() > 0)
            //     {
            //         exit(0);
            //     }

            //     Service(sockfd, addr);
            //     exit(0);
            // }

            // // 同理父进程也要关闭sockfd(4)因为那是属于子进程的,用_listensockfd(3)
            // // 必须要释放子进程的sockfd, 防止文件描述符泄漏 
            // int n = waitpid(id, nullptr, 0);
            // if(n > 0)
            // {
            //     LOG(INFO, "wait chid success\n");
            // }

            // 版本三 多线程版并发访问服务器
            // pthread_t tid;
            // ThreadData* td = new ThreadData(sockfd, this, addr);
            // // 新线程内部分离线程
            // pthread_create(&tid, nullptr, Excute, td);

            // 版本四 线程池版本
            task_t t = bind(&TcpServer::Service, this, sockfd, addr);
            ThreadPool<task_t>::GetInstance()->Equeue(t);
        }
        _isrunning = false;
    }

    // 类里面方法带有this指针,标为静态
    // 多线程因为共用同一个文件描述符表,所以不能关闭
    static void* Excute(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        td->_self->Service(td->_sockfd, td->_addr);
        delete(td);
        return nullptr;
    }


    void Service(int sockfd, InetAddr addr)
    {
        while(1)
        {
            char inbuffer[1024];
            ssize_t n = read(sockfd, inbuffer, sizeof inbuffer - 1);
            if(n > 0)
            {
                inbuffer[n] = 0;
                string echo_string = "[server echo] #";
                echo_string += inbuffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if(n == 0)
            {
                LOG(INFO, "client %s quit\n", addr.AddStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read error: %s\n", addr.AddStr().c_str());
                break;
            }
        }
        ::close(sockfd);
    }

(1)accpet 函数循环获取新连接,获取成功填入客户端信息

(2)服务端提供服务的四种方式

a、直接调用服务函数

不能并发访问服务器,不使用

b、多进程调用服务函数

创建子进程后,父进程关闭子进程套接字 sockfd,子进程关闭父进程套接字 _listen_sockfd,让父子进程不能相互影响。

signal(SIGCHLD, SIG_IGN); 忽略子进程退出,父进程不用阻塞等待。

也可以在子进程中创建孙子进程,子进程退出,孙子进程变成孤儿进程执行服务函数,函数结束不用父进程管,让OS管。还是推荐忽略子进程退出的方式。

c、多线程调用服务函数

为了线程创建函数中的参数线程调用函数不能有this指针,把调用函数设 static

多线程使用同一张文件描述符表,不能关闭任何套接字

要定义内部类,给线程执行函数传入 sockfd, this指针(调用Service函数), addr

新线程在执行函数中要 pthread_detach(pthread_self()) 进行线程分离

d、线程池调用服务函数

using task_t = function<void()>;

task_t t = bind(&TcpServer::Service, this, sockfd, addr);

ThreadPool<task_t>::GetInstance()->Equeue(t);

3、客户端初始化

    if (argc != 3)
    {
        cerr << "Usage: " << argv[0] << " server_ip server_port" << endl;
        exit(0);
    }

    string server_ip = argv[1];
    uint16_t server_port = stoi(argv[2]);

    // 1.创建socket
    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        cerr << "create socket error" << endl;
        exit(1);
    }

    // 注意:客户端不需要显示绑定,但一定要有自己的ip和port,OS会自动绑定sockfd, 用自己的ip和随机端口号

    // 向服务器发送链接(第一次发送链接时,OS自动绑定)
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    ::inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);

(1)获取要连接的服务器信息

(2)初始化 struct socket_in server,并填入服务器信息

(3)创建套接字

4、客户端链接服务器

    int n = ::connect(sockfd, (struct sockaddr*)&server, sizeof server);
    if(n < 0)
    {
        cerr << "connect error" << endl;
        exit(2);
    }

    while(1)
    {
        string message;
        cout << "Enter #";
        getline(cin, message);
        write(sockfd, message.c_str(), message.size());

        char echo_buffer[1024];
        n = read(sockfd, echo_buffer, sizeof echo_buffer);
        if(n > 0)
        {
            echo_buffer[n] = 0;
            cout << echo_buffer << endl;
        }
        else
        {
            break; 
        }
    }

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

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

相关文章

AI是不是真的能落地应用,未来看看这三个行业

大家好&#xff0c;我是凡人&#xff0c;最近也被GPT-4o刷爆了朋友圈&#xff0c;各种对OpenAI新布局的猜想也是层出不穷。GPT-4o再次将多模态交互拉到了新高度&#xff0c;一放出就颠覆了原有 AI 给传统行业带来新冲击。 还有不了解GPT-4o 的同学&#xff0c;可以查看我上一篇…

Java异常体系----深入讲解

JAVA异常体系 1.error 错误 程序无法处理的异常&#xff0c; 它是由JVM产生和抛出的,比如OutOfMemoryError.ThreadDeath等 示例&#xff1a; public class Test {public static void main(String[] args) {run();}public static void run(){run();} }堆栈溢出&#xff0c;…

828华为云征文|基于华为云Flexus云服务器X实例部搭建Halo博客平台

华为云征文&#xff5c;基于华为云Flexus云服务器X实例部搭建Halo博客平台 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、Halo介绍2.1 Halo 简介2.2 Halo 特点 三、本次实践介绍3.1 本次实践简…

Linux日志-journal日志

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux 系统中的日志是记录系统活动和事件的重要工具&#xff0c;它们可以帮助管理员监视系统状态、调查问题以及了解系统运行…

Linux Debian12安装原生版微信

1.原生版微信下载地址&#xff1a; https://archive.ubuntukylin.com/software/pool/partner/找到weixin&#xff0c;2022年05月23日最新版本&#xff0c;weixin_2.1.4_amd64.deb&#xff0c;下载。 2.微信安装&#xff1a; sudo dpkg -i weixin_2.1.4_amd64.deb3.登陆即可。…

如何在3DMAX中实现大规模项目的地形建模?

在房地产开发项目的环境建模过程中&#xff0c;我们对斜坡和不平坦地形进行建模是一项具有挑战性的任务。 我们已经制定了两种方法来纠正这一点。首先&#xff0c;让我告诉你&#xff0c;我们并没有想过如何使用NURBS来实现这一点&#xff0c;我们通常坚持使用多边形&#xff…

ueditor视频上传

修改video.js中uploadBeforeSend方法 位置: 修改为: uploader.on(uploadBeforeSend, function (file, data, header) {//这里可以通过data对象添加POST参数const token =******//根据各自项目情况,获取到存储的tokenheader[X_Requested_With] = XMLHttpRequestheader[Aut…

气膜场馆:乡村振兴中的健康与经济新引擎—轻空间

随着乡村振兴战略的深入推进&#xff0c;气膜场馆作为新兴建筑形式&#xff0c;正在为农村地区带来全新的发展机遇。它不仅是乡村百姓锻炼身体的好去处&#xff0c;更是带动当地经济发展的强劲动力。 首先&#xff0c;气膜场馆为农村地区的居民提供了更多运动健身的机会。与传统…

Redis 缓存深度解析:穿透、击穿、雪崩与预热的全面解读

Redis 缓存深度解析&#xff1a;穿透、击穿、雪崩与预热的全面解读 一 . 什么是缓存 ?二 . 使用 Redis 作为缓存三 . 缓存的更新策略3.1 定期生成3.2 实时生成 四 . 缓存预热、缓存穿透、缓存雪崩、缓存击穿4.1 缓存预热4.2 缓存穿透4.3 缓存雪崩4.4 缓存击穿 Hello , 大家好 …

2025届计算机毕业设计:如何构建Java SpringBoot+Vue个人健康档案管理系统?

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

全能AI神器!工作效率提升80倍!Zmo.ai带你玩转AI做图!

今天&#xff0c;我要给大家介绍一款神器&#xff1a;Zmo.ai。 这个平台简直是做图神器&#xff0c;集多种功能于一身&#xff0c;让你像专业人士一样轻松创建和编辑图像&#xff0c;不需要任何美术与设计基础&#xff0c;真的非常适合我们这些“手残党”&#xff01; 我们只需…

香港服务器机房托管:优化全球访问体验的最佳选择

在当今全球化的商业环境中&#xff0c;出海企业面临着前所未有的挑战与机遇。为了在全球范围内高效运营&#xff0c;确保公司内部系统、网站及应用的顺畅访问成为了企业成功的关键。尤其是当企业需要同时服务国内管理团队与海外客户时&#xff0c;如何选择一个既能降低国内访问…

数字化平台跨界融合增值:新起点与新机遇

亲爱的朋友们&#xff0c;在这个快速发展的数字化时代&#xff0c;有一个重要的趋势正站在新起点上&#xff0c;那就是数字化平台跨界融合增值。 新起点的基石技术基础夯实&#xff1a;如今&#xff0c;5G 网络已经全面普及&#xff0c;物联网技术也在深度发展&#xff0c;再加…

AI编程辅助工具先锋:豆包MarsCode

在科技日新月异的今天&#xff0c;AI正以不可阻挡之势渗透到各行各业&#xff0c;其中编程辅助领域也正经历着一场前所未有的变革。随着AI技术的不断成熟与应用深化&#xff0c;编程辅助工具正逐步从简单的代码补全、语法检查&#xff0c;迈向更加智能化、个性化的全方位辅助&a…

【Python 学习】Numpy的基础和应用

目录 1 数组基础1.1 Numpy简介1.2 Numpy数组基础1.3 创建数组1.3.1 使用np.array()函数生成数组1.3.2 利用内置函数产生特定形式的数组1.3.2.1 简单内置函数1.3.2.2 特殊内置函数 1.3.3 生成随机数组 1.4 数组的数据类型1.5 数组的迭代1.6数组的索引和切片1.6.1 一维数组的索引…

NULL 輸出情況

NULL值 輸出情況區分 匯總圖片 select 空 param1 -> param1&#xff1a;null select param1 from 空 —> param1&#xff1a;空 NULL 值出現在查詢結果中 只能由 SELELCT 和 聚合函數 產生 而不是 LIMIT WHERE HAVING FROM 語句 REFERENCE Leetcode 解析

[QT] QT事件与事件重写

一.事件 事件(event)是由系统或者 Qt本身在不同的场景下发出的。当用户按下鼠标、敲下键盘&#xff0c;或者是窗口关闭等都会发出一个相应的事件。 一些事件在用户操作时发出(如鼠标/键盘事件); 另一些事件则是由系统自动发出(如计时器事件)。 Qt窗口中对于产生的一系列事件都…

jmeter之setUP、tearDown线程组

setUp线程组&#xff1a; 用于在执行常规线程组之前执行一些必要的操作 ------会在普通线程组执行前被触发 应用场景举例&#xff1a; A、测试数据库操作功能时&#xff0c;用于执行打开数据库连接操作 B、测试用户购物功能时&#xff0c;用于执行用户的注册、登录等操作 tea…

C++入门(02)简单了解C++应用程序的开发部署

文章目录 1. 开发C应用程序2. 简单示例计算器程序3. 需求分析4. 设计5. 编码6. 编译7. 调试8. 测试9. 部署10. 部署示例10.1 使用Visual Studio Installer Projects创建安装程序10.2 安装VisualStudio Installer Projects扩展10.3 在calculator解决方案中创建安装项目10.3.1 添…

揭秘网络安全:从零基础到高手的必由之路

一、网络安全的定义 网络安全&#xff0c;是指通过采取必要措施&#xff0c;防范对网络的攻击、侵入、干扰、破坏和非法使用以及意外事故&#xff0c;使网络处于稳定可靠运行的状态&#xff0c;以及保障网络数据的完整性、保密性、可用性的能力 。其涵盖信息保密性、完整性、可…