Linux网络编程 -- 网络套接字预备与udp

news2024/10/10 7:12:51

本文主要介绍网络编程的相关知识,在正式介绍网络编程之前,我们得先了解一些前置的知识。

1、端口号

我们上网其实就是两种动作,一个是将远处的数据拉取到本地,另一个是把我们的数据发送给远端。其实大部分的网络通信行为都是用户触发的,而在计算机中,谁表示用户呢?答案是进程。当用户接受到数据后,OS要将对应的数据给特定的服务进程。一台机器上有许多的服务进程,而我们用特定的端口号(port)来标识这个服务进程。

所以当我们将IP与端口号进行绑定,就可以得出互联网中的唯一一个进程了。这种方式也叫做套接字通信,服务器端和客户端的通信一般都采用这种方式。

为什么我们要用端口号来标识一个进程呢?不是有进程的pid吗?首先是因为进程的pid是不断变化的,其次如果使用pid的话,网络服务就和操作系统进行强关联了。此时如果我们修改一下操作系统对进程的标识方式,那么对上层的网络服务也需要进行大幅修改。另外OS中并不是每个进程都有端口号的,一般只有网络进程才有端口号。端口号和进程pid在OS会通过特定数据结构关联起来,例如哈希表。

2、TCP和UDP协议

在正式开始介绍网络编程之前,我们先简单了解一下UDP和TCP协议(后面具体介绍)

TCP(传输控制协议)和UDP(用户数据报协议)是互联网上用于数据传输的两种重要协议,它们的主要区别在于以下几个方面:

  1. 连接性

    • TCP 是面向连接的协议,意味着在数据传输之前,需要建立一个连接,在传输完成后需要断开连接。
    • UDP 是无连接的,它发送数据之前不需要建立连接,数据可以直接发送给接收方。
  2. 可靠性

    • TCP 提供了可靠的服务。它确保数据包的顺序传输,并且通过确认和重传机制保证数据的完整性。
    • UDP 不保证数据的可靠传输,它只负责发送数据,不保证数据包的顺序或是否到达。
  3. 速度与效率

    • TCP 由于其可靠性机制,速度相对较慢,因为它需要时间来建立连接、确认数据包和进行重传。
    • UDP 由于没有这些机制,通常更快,适用于对实时性要求较高的应用,如视频会议和在线游戏。
  4. 数据流控制

    • TCP 有流量控制和拥塞控制机制,可以根据网络状况调整数据传输的速度。
    • UDP 没有这样的控制机制,发送速率不会因网络状况而改变。
  5. 用途

    • TCP 常用于要求高可靠性的应用,如网页浏览、电子邮件和文件传输。
    • UDP 常用于实时应用,如流媒体、VoIP(网络电话)和在线游戏,这些应用对速度的要求高于数据完整性。
  6. 头部开销

    • TCP 头部较大,因为它需要包含更多的信息来保证数据的可靠传输。
    • UDP 头部较小,处理起来更快,开销更小

这里只是简单介绍,看不懂没有关系,有个基础概念即可。

 3、网络字节序

在我们机器中,分为大端机和小端机,当我们在网络进行传输时,可能会面临大端机向小端机传输数据的情况。此时接受方读取数据时就可能会出现异常,所以这里统一将网络的数据定为大端。但是每次都要我们手动对数据进行大小端的转换未免太过麻烦,所以系统为我们提供特定的接口统一转换,后续会对其进行介绍。

4、接口预备知识

socket编程,是有很多的种类,有的是专门用来本地通信的(Unix socket),有的是用来跨网络进行通信的(inet socket),有的是用来进行网络管理的(raw socket )。这些套接字类型非常多,为了减少学习的成本,linux编写者就决定让这些套接字使用统一的接口。而OS是由C语言进行编写的,涉及到统一类型的问题就必须和结构体相关联。其中有关套接字的结构体常见有三种。

 我们常用的类型是struct sockaddr,但实际上我们用于存储数据的结构体是struct sockadd_in和struct sockaddr_un。与网络编程相关的接口都使用的是sockaddr结构体,所以我们需要先用sockaddr_in结构体存储数据,然后强转成sockaddr结构。这种特性和C++多态非常类似。这里我们着重关注sockaddr结构即可,当我们将sockaddr_in 结构强转成sockaddr后,相关的接口依然能够识别原来sockaddr_in结构体内的数据。(sockaddr_un同理)

5、相关接口的介绍与认识

1、socket

该函数用于创建套接字,第一个参数用于指定套接字的域,第二个参数是套接字的类型,第三个参数在前两个参数确定的情况下填零即可。该接口成功调用的返回值是一个文件描述符,失败就返回-1,网络套接字创建以后相当于绑定了该文件描述符。这里我们一般就使用AF_INET。

第一个参数列表

第二个参数列表(使用udp协议时,我们就需要将该参数设置成SOCK_DGRAM)

2、bind

在创建完套接字后,我们必须要将创建的套接字与端口号和ip地址进行绑定,也就是将网络服务与本地的文件描述符绑定(先这样理解)。所以这里我们需要引入一个接口bind

第一个参数表示创建套接字的文件描述符,第二个是网络套接字的相关结构体(就是我们上文所提到的sockaddr,不过实际上我们使用的是sockaddr_in),第三个参数表示第二个参数代表的结构体大小。

在使用struct sockaddr_in之前,我们首先要将其定义出来,初始化后再对各个成员进行初始化。

sin_family 我们一般初始化成AF_INET即可,而sin_port和sin_addr的初始化需要特别注意的是,这两个成员再初始化之前,我们都需要对其作主机序列转成网络序列的操作,也就是转成大端,除此之外,由于IP地址是点分十进制风格的字符串来表示的,所以在网络传输前,还要将其变成4字节的IP。在初始化IP地址时,我们发现sockaddr_in中,表示IP地址的成员是一个结构体,而这个结构体内只有一个成员,在初始化的时候需要注意以下。上述这些操作都不需要我们手动的实现,OS已经提供了相关的接口。

端口的主机序列转网络序列的相关接口(这里我们使用第二个接口,因为我们一般将端口设成16位)

当前IP的主机序列转网络序列并将其转成4字节IP的相关接口(这里我们一般使用第二个接口)

3、recvfrom

该接口用于接收网络中数据,第一个参数是服务端或客户端绑定的文件描述符,第二个参数是一个缓冲区的指针,用于存放接收的数据,第三个参数用于表示接收数据的长度,第四个参数是位掩码,用于控制该函数,我们通常填零即可(因情况而定),第五个参数也是一个输出型的参数,用于接收发送方的信息,第六个参数用于表示第五个参数的大小。返回值表示实际读取到的数据长度。

4、sendto

该接口用于向特定主机发送数据,第一个参数服务端或客户端绑定的文件描述符,第二个参数是一个缓冲区的指针,表示要发送的数据。第三个参数表示缓冲区的大小,第四个参数表示位掩码,表示对该函数的控制,一般设置为零即可,第五个参数表示目标主机的相关信息,第六个参数表示第五个参数的大小。返回值表示发送出去的数据长度。

示例代码(udp)

Main.cc(服务端)

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

void Usage()
{
    std::cout << "./Main.cc  server_port\n"<< std::endl;
}
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage();
        return 0;
    } 
    EnableScreen();
    //std::string server_ip = argv[1];
    int server_port = std::stoi(argv[1]);
    std::unique_ptr<Udpserver> ptr = std::make_unique<Udpserver>(server_port);
    ptr->InitServer();
    ptr->Start();
    return 0;
}

Udpserver.hpp

#pragma once
#include <iostream>
#include "Log.hpp"
#include <strings.h>
#include "Inetaddr.hpp"
#include <cstring>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum
{
    SOCKET = 1,
    BIND
};

class Udpserver
{
public:
    Udpserver(uint16_t port) : _port(port), _isrunning(false)
    {
    }
    void InitServer()
    {
        _fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_fd < 0)
        {
            LOG(INFO, "socket fail")
            exit(SOCKET);
        }

        struct sockaddr_in infor;
        bzero(&infor, sizeof(infor));//清空数据
        infor.sin_family = AF_INET;
        // 主机序列转网络序列
        infor.sin_port = htons(_port);
        infor.sin_addr.s_addr = htonl(INADDR_ANY);
        // 绑定
        socklen_t len = (socklen_t)sizeof(infor);
        int count = bind(_fd, (struct sockaddr *)&infor, len);
        if (count < 0)
        {
            LOG(ERROR, "bind fail ...")
            exit(BIND);
        }
        LOG(INFO, "bind success")
    }
    void Start()
    {
        _isrunning = true;
        LOG(INFO,"begin server...")

        while (1)
        {
            char buffer[1024];
            memset(buffer, 0, sizeof(buffer));
            struct sockaddr_in src;
            socklen_t len = (socklen_t)sizeof(src);
            ssize_t rnum = recvfrom(_fd, buffer, sizeof(buffer), 0, (struct sockaddr *)&src, &len);
            if (rnum > 0)
            {
                buffer[1023] = 0;
                Inetaddr addr(&src);
                LOG(INFO, "receive informaiton success")
                printf("#[%s:%d]: %s\n",addr.IP().c_str(),addr.Port(),buffer);
                ssize_t snum = sendto(_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&src, len);
            }
        }
        _isrunning = false;
    }

    ~Udpserver()
    {

    }
private:
    int _fd;
    bool _isrunning;
    uint16_t _port;
    //std::string _IP;
};

client.cc(客户端)

#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include "Log.hpp"
#include <strings.h>
#include <arpa/inet.h>
#include <cstring>
#include <netinet/in.h>
using namespace std;
void Usage()
{
    std::cout << "./Main.cc server_ip  server_port\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage();
        return 0;
    }
    // 创建套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0)
    {
        LOG(FATAL, "socket fail...")
        exit(-1);
    }
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons(stoi(argv[2]));
    client.sin_addr.s_addr = inet_addr(argv[1]);
    // client 不需要显示地绑定客户端。OS会在client发送数据时,随机绑定一个端口号
    // 通信
    std::string message;
    while (1)
    {
        std::cout << "Please Enter: ";
        std::getline(std::cin, message);
        sendto(fd, message.c_str(), message.size(), 0, (struct sockaddr *)&client, sizeof(client));

        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        char buffer[1024];
        memset(buffer, 0 , sizeof(buffer));
        ssize_t n = recvfrom(fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (n > 0)
        {
            std::cout << buffer << std::endl;
        }
    }
    return 0;
}

Ineraddr.hpp

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


class Inetaddr
{
private:
    void Init()
    {
        port = ntohs(_src->sin_port);
        ip = inet_ntoa(_src->sin_addr);
    }
public:
    Inetaddr(struct sockaddr_in* src):_src(src)
    {
        Init();
    }
    std::string IP()
    {
        return ip;
    }
    uint16_t Port()
    {
        return port;
    }
    ~Inetaddr()
    {
    }
private:
    struct sockaddr_in* _src;
    std::string ip;
    uint16_t port;
};

Log.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <pthread.h>
#include <fstream>
enum Level
{
    INFO = 0,
    DEBUG,
    WARNING,
    ERROR,
    FATAL

};
std::string Level_tostring(int level)
{
    switch (level)
    {
    case INFO:
        return "INFO";
    case DEBUG:
        return "DEBUG";
    case WARNING:
        return "ERROR";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return "Unkown";
    }
}

pthread_mutex_t _glock = PTHREAD_MUTEX_INITIALIZER;
bool _is_save = false;
const std::string filename = "log.txt";

void SaveLog(const std::string context)
{
    std::ofstream infile;
    infile.open(filename,std::ios::app);
    if(!infile.is_open())
    {
        std::cout << "open file failed" << std::endl;
    }
    else
    {
        infile << context;
    }
    infile.close();
}
std::string Gettime()
{
    time_t cur_time = time(NULL);
    struct tm *time_data = localtime(&cur_time);
    if (time_data == nullptr)
    {
        return "None";
    }
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d",
             time_data->tm_year + 1900,
             time_data->tm_mon + 1,
             time_data->tm_mday,
             time_data->tm_hour,
             time_data->tm_min,
             time_data->tm_sec);
    return buffer;
}
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{
    std::string levelstr = Level_tostring(level);
    std::string time = Gettime();
    // 可变参数
    char buffer[1024];
    va_list args;
    va_start(args, format);
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);

    std::string context = "[" + levelstr + "]" + "[" + time + "]" + "[" + "line : " + std::to_string(line) + "]" + "[" + filename + "]" + ": " + buffer;
    pthread_mutex_lock(&_glock);
    if(!issave)
    {
        std::cout << context << std::endl;
    }
    else{
        SaveLog(context);
    }
    pthread_mutex_unlock(&_glock);
}

#define LOG(level, format, ...)                                          \
    do                                                                   \
    {                                                                    \
        LogMessage(__FILE__, __LINE__, _is_save, level, format, ##__VA_ARGS__); \
    } while (0);
#define EnableFile()    \
    do                  \
    {                   \
        _is_save = true; \
    } while (0);
#define EnableScreen()   \
    do                   \
    {                    \
        _is_save = false;\
    } while (0);

代码编写中及测试过程中的注意事项

<1>在客户端中,我们是不需要显示地绑定一个端口号的,操作系统会第一次连接时自动帮我们绑定一个随机端口号。如果帮显示绑定一个端口号,那么可能就会造成以下情况,当你的主机上需要同时启动两款App,但是这两款App绑定了同一个端口号,此时就会造成一个App启动后,另一个个App启动失败。

<2>当我们接收到sockadd_in结构体后,如果需要打印结构体内的信息,需要对对其进行从网络序列转成主机序列的操作,这也就是Inetaddr文件存在的原因(这里我封装了)。

<3>服务端不推荐绑定固定的ip(我们一般就绑定为0,表示能够处理任何ip发送的服务)。在云服务器上也不允许绑定公网的ip,如果需要在云服务器上绑定ip,则需要在对应的云服务的安全组上添加对应的端口。

以上就是所有内容

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

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

相关文章

基于springboot vue3 工商局商家管理系统设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm springcloud等开发框架&#xff09; vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…

【公共祖先】二叉树专题

里面涉及多个plus题 前言1.二叉树的最近公共祖先2.二叉搜索树的最近公共祖先3.二叉树的最近公共祖先II4.二叉树的最近公共祖先III5.二叉树的最近公共祖先IV 前言 公共祖先这一类题目&#xff0c;难度不大&#xff0c;但是非常实用&#xff0c;也是面试问到概率比较大的一类题目…

夜间数据库IO负载飙升?MySQL批量删除操作引发的问题排查

目录 问题现象 问题分析 修改建议 总结 问题现象 近日&#xff0c;某用户反馈他们的MySQL数据库实例在凌晨时段会频繁出现IO负载急剧上升的情况&#xff0c;这种状态会持续一段时间&#xff0c;随后自行恢复正常。为了查明原因&#xff0c;该用户通过DBdoctor工具收集了相…

DLL中函数导出时的注意事项

1.使用.def文件导出函数 1.1示例代码:使用stdcall 关键字 和 extern "C" 关键字修饰 dll中函数 BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:case DLL_THREAD_ATT…

sahi密集检测的推理技巧

最近在做一些计数的项目&#xff0c;样本中存在一些非常密集的目标&#xff0c;如果混杂一起训练指标很难达到要求&#xff0c;所以考虑在训练时不加入密集目标&#xff0c;训练使用正常的样本&#xff0c;在推理时使用密集检测方案。 在高分辨率图像中检测小目标一直是一个技…

【Qt+Python项目构建】- 02 Qt creator 14.0 + PySide6 如何让图像控件的尺寸变化和窗口一致

前言&#xff1a;【这是个AI不会回答的问题】 Qt Creator 新的版本又发出了&#xff0c;Pyside6 有很多新功能。但是&#xff0c;一些传统的方法要被淘汰了。 一个经典的例子是&#xff1a; 我有个一个图像要显示在Form里面的图像控件上&#xff0c;OK&#xff0c; 我现在拖…

Unity实现自定义图集(一)

以下内容是根据Unity 2020.1.0f1版本进行编写的   Unity自带有图集工具,包括旧版的图集(设置PackingTag),以及新版的图集(生成SpriteAtlas)。一般来说,unity自带的图集系统已经够用了,但是实际使用上还是存在一些可优化的地方,例如加载到Canvas上的资源,打图集不能…

JVM(学习预热 - 走进Java)(持续更新迭代)

目录 一、彻底认识Java虚拟机 开创世纪&#xff1a;Sun Classic 开创世纪&#xff1a;Exact VM 武林霸主&#xff1a;HotSpot VM 移动端虚拟机&#xff1a;Mobile/Embedded VM “三大”其二&#xff1a;BEA JRockit/IBM J9 VM 软硬结合&#xff1a;BEA Liquid VM/Azul VM…

更新子节点的优化策略1:目标old节点的位置预测

更新子节点的优化策略1&#xff1a;目标old节点的位置预测&#xff1a; 如果 oldStartVnode 和 newStartVnode 是同一个节点&#xff0c;直接 patchVnode&#xff0c;同时 oldStartIdx、newStartIdx 索引都加 1&#xff08;向右移动&#xff09;如果 oldEndVnode 和 newEndVno…

PE结构之 重定位表

那么,我们找到了某个 需要修改的绝对地址 的RVA, 将这个RVA转换成FOA后,这个绝对地址是读DWORD ,还是QWORD? 就是说,32位和64位是否有区别? 实验: 找到重定位表的数据,并观察在内存中和文件中的区别 将引用dll的exe文件,设置一下基址 同时DLL文件的基址和EXE文件设置一样,并…

肝了4天,我用ChatTTS和LLM让deeplearning.ai课程说上流畅中文

以下是「 豆包MarsCode 体验官」优秀文章&#xff0c;作者X2046。 我们都知道外网上有很多优秀的视频教程平台&#xff0c;比如 Coursera 和 deeplearning.ai。尤其是后者&#xff0c;由吴恩达老师与OpenAI、Langchain、LlamaIndex、AutoGen等公司和作者合作&#xff0c;推出了…

Spring Cloud Netflix Hystrix 熔断器讲解和案例示范

在分布式微服务架构中&#xff0c;每个服务之间相互依赖&#xff0c;当某个服务出现故障或延迟时&#xff0c;如果没有有效的故障隔离机制&#xff0c;可能导致整个系统雪崩式的失败。Netflix Hystrix 作为一种熔断器模式&#xff0c;旨在通过隔离服务之间的调用&#xff0c;提…

通过移动访问控制增强数据中心安全性

在当今数据驱动的世界里&#xff0c;信息是新的黄金标准&#xff0c;数据中心安全已成为每个 IT 部门的首要任务。数据隐私和道德管理不再仅仅是最佳实践&#xff0c;而是法律要求。因此&#xff0c;风险比以往任何时候都要高。 然后是内部威胁问题。根据 IBM 的 《2024 年数据…

Python案例--copy复制

在Python编程中&#xff0c;数据的复制是一个常见且重要的操作&#xff0c;它涉及到赋值、浅拷贝和深拷贝三种不同的概念。正确理解这三种操作对于编写高效且正确的程序至关重要。本文将通过一个简单的Python示例&#xff0c;探讨这三种数据复制方式的区别及其应用场景&#xf…

数据结构 ——— 单链表oj题:环状链表(求出环的入口节点)

目录 题目要求 手搓一个简易带环链表 代码实现 题目要求 给定一个链表的头节点 head&#xff0c;返回链表开始入环的第一个节点&#xff0c;如果链表无环&#xff0c;则返回NULL 手搓一个简易带环链表 代码演示&#xff1a; struct ListNode* n1 (struct ListNode*)mal…

深度学习:循环神经网络—RNN的原理

传统神经网络存在的问题&#xff1f; 无法训练出具有顺序的数据。模型搭建时没有考虑数据上下之间的关系。 RNN神经网络 RNN&#xff08;Recurrent Neural Network&#xff0c;循环神经网络&#xff09;是一种专门用于处理序列数据的神经网络。在处理序列输入时具有记忆性…

Mac上强大的菜单栏管理工具

想要Mac用的好&#xff0c;各种工具少不了&#xff0c;一款好用的软件对于提高使用效率和使用舒适度来说非常必要&#xff0c;iBar-强大的菜单栏图标管理工具 随着 Mac 运行的软件增加&#xff0c;状态栏中的图标也越来越多&#xff0c;不仅看得眼花缭乱&#xff0c;而且刘海屏…

基于SpringBoot+Vue的农场管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

ORM框架简介

什么是ORM&#xff1f; ORM&#xff08;Object-Relational Mapping&#xff0c;对象关系映射&#xff09;是一种编程技术&#xff0c;用于在关系数据库和对象程序语言之间转换数据。ORM框架允许开发者以面向对象的方式来操作数据库&#xff0c;而不需要编写复杂的SQL语句。简单…

CMake 属性之目录属性

【写在前面】 CMake 的目录属性是指在特定目录&#xff08;及其子目录&#xff09;范围内有效的设置。 这些属性不同于全局变量或目标&#xff08;Target&#xff09;属性&#xff0c;它们提供了一种机制&#xff0c;允许开发者为项目中的不同部分定义不同的构建行为。 通过目录…