• 网络套接字(Udp实现简单的网络通信)


    1.源IP和目的IP

    两个主机在进行通信的时候,是通过各自的IP地址来找到对方的。对于一个通信信息来讲,信息发出者的IP称为源IP,信息接受者的IP称为目的IP。
    源IP和目的IP存放在信息的报头中,最大的意义在于:指导一个报文如何进行路径选择

    2.端口号

    (1)套接字

    当数据从一台主机发送到另一台主机上后,就需要进行数据处理,在主机上有多个进程,如何知道是发送给哪一个进程呢?这就需要用到端口号来标识唯一的一个进程(用该进程绑定主机的一个端口号)。网络通信分为两步,先通过IP找到对应主机,再通过端口号找到对应进程。
    IP+端口号就可以唯一的标识互联网中的一个进程。我们将IP+端口号称为一个套接字。

    (2)PIDvs端口号

    每一个进程都有自己的pid,为什么还需要端口号呢?因为这方便解耦。使用PID的话,一旦某一台主机的进程的PID发生了改变,那么整个网络都需要发生变化。为了导致整个网络系统的稳定性,使用端口号来标识进程。

    一个进程可以关联多个端口号。
    一个端口号只能关联一个进程。

    3.TCP和UDP

    目前我们只需要知道,TCP是可靠的,有链接;UDP是不可靠的,无链接。
    注意,这里的可靠和不可靠是中性词,可靠意味着更多的资源消耗。

    4.网络字节序

    当两台主机进行通信的时候,它们可能是大端机或者小端机。但是为了方便通信,网络规定所有发送到网络中的数据必须是以大端的形式。如果是小端机需要自己将数据转化为大端再进行发送。

    5.网络通信的接口

    (1)头文件

    这四个头文件是必须被记住的。

    #include
    #include
    #include
    #include

    (2)常用函数

    创建套接字

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

    第一个参数,代表的是协议族,它的值可以是:在这里插入图片描述
    当我们使用Udp协议的时候,选择AF_INET
    第二个参数代表的是套接字类型,它的值可以是:
    在这里插入图片描述
    在Udp中,套接字类型为SOCK_DGRAM。
    第三个参数表示的是协议类型,当前两个参数确定之后,第三个参数会被自动识别,这里我们填0即可。
    当创建套接字失败,则返回-1,如果成功则返回的是套接字的文件描述符,套接字是一个抽象层,用户可以将其像文件一样打开,读写操作。但是使用的接口和文件操作的完全不同

    套接字绑定本地协议地址

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

    bind函数将一个本地协议地址赋予一个套接字,它的第一个参数是套接字的文件描述符。
    第二个参数表示一个指向特定协议地址结构的指针,第三个参数表示该地址结构的长度。
    理解这个函数之前,我们要了解一下sockaddr这个结构体,它是一个类似多态的可以接收两种具体结构体类型的结构体:
    在这里插入图片描述
    不同的协议对应不同的协议地址结构,但是都可以作为实参传入sockaddr结构体中,不使用void的原因是,当时还没有提出void的概念。在使用的时候需要强转类型。
    我们可以查一下该结构体的内容:
    在这里插入图片描述
    建立一个协议地址结构,需要对该结构体的内容进行初始化。
    其中_SOCKADDR_COMMON是一个宏定义,使用family来替换它,传入的是协议族,这里我们可以传入AF_INET。
    sin_port表示一个端口号,需要我们自己定义一个端口号并传入。端口号不能直接传入,而是需要转化成网络序列再进行传入,将端口号转化为网络序列的函数为:

    uint16_t htons(uint16_t hostshort);

    传入的内容就是16进制的端口号,返回该端口号的网络序列。
    sin_addr是一个结构体,它里面有一个变量:
    在这里插入图片描述
    我们向该变量中传入本机的IP。但是传入本机IP的时候,需要将人可以识别的点分十进制转换成4字节整数IP。同时还要考虑大小端。
    系统提供了inet_addr函数来帮助实现这一过程:

    in_addr_t inet_addr(const char *cp);

    向其中传入的就是点分十进制IP,将返回值传给s_addr即可。对于服务器来说,如果绑定的是确定的IP,那么只有发向该IP的数据才会被交给网络进程,但是一般服务器可能由多张网卡,配置了多个IP,我们需要不是发给某个IP的数据,而是发给所有IP的数据,因此在绑定服务器IP的时候,通常使用INADDR_ANY。而以上的结构通常是客户端来使用的。
    bind的第三个参数表示的是该协议地址结构的大小。

    发送接收数据

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

    第一个参数表示套接字的文件描述符,第二个参数表示缓冲区的大小,第三个参数表示读的方式,通常默认为0。
    第四第五个参数表示的是和服务端进行通信的客户端套接字的信息,表明是谁发的数据。
    返回值表示读到多少字节。

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

    第一个参数表示套接字的文件描述符,第二个参数表示缓冲区的大小,第三个参数表示方式,第四个参数表示向谁发送数据,第五个参数表示发送数据的长度。

    6.通信的实现

    (1)服务端

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    const uint16_t port = 8080;
    
    // udp_server,细节最后在慢慢完善
    
    int main()
    {
        //1. 创建套接字,打开网络文件
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if(sock < 0){
            std::cerr << "socket create error: " << errno << std::endl;
            return 1;
        }
        //2. 给该服务器绑定端口和ip(特殊处理)
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(port); //此处的端口号,是我们计算机上的变量,是主机序列
        local.sin_addr.s_addr = INADDR_ANY;
        //给套接字绑定主机的IP和端口号
        if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
            std::cerr << "bind error : " << errno << std::endl;
            return 2;
        }
        //3. 提供服务
        bool quit = false;
        #define NUM 1024
        char buffer[NUM];
        while(!quit)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);//从peer这个协议地址结构接收数据
            std::cout << "client# " << buffer << std::endl;
            std::string echo_hello = "hello";
            sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr*)&peer, len);//向peer地址结构发送数据
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    (2)客户端

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include
    
    void Usage(std::string proc)
    {
        std::cout << "Usage: \n\t" << proc << " server_ip server_port" << std::endl;
    }
    
    // ./udp_client server_ip server_port
    
    int main(int argc, char *argv[])
    {
        if (argc != 3)
        {
            Usage(argv[0]);
            return 0;
        }
    
        // 1. 创建套接字,打开网络文件
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock < 0)
        {
            std::cerr << "socket error : " << errno << std::endl;
            return 1;
        }
    
        //客户端需要显示的bind的吗??
        // a. 首先,客户端必须也要有ip和port
        // b. 但是,客户端不需要显示的bind!一旦显示bind,就必须明确,client要和哪一个port关联
        // client指明的端口号,在client端一定会有吗??有可能被占用,被占用导致client无法使用
        // server要的是port必须明确,而且不变,但client只要有就行!一般是由OS自动给你bind()
        // 就是client正常发送数据的时候,OS会自动给你bind,采用的是随机端口的方式!
    
        // b. 你要给谁发??
        struct sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(atoi(argv[2]));
        server.sin_addr.s_addr = inet_addr(argv[1]);
    
        // 2.使用服务
        while (1)
        {
            // a. 你的数据从哪里来??
            std::string message;
            std::cout << "输入# ";
            std::cin >> message;
    
            sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
    
            //此处tmp就是一个”占位符“
            struct sockaddr_in tmp;
            socklen_t len = sizeof(tmp);
            char buffer[1024];
            recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&tmp, &len);
    
            std::cout << "server echo# " << buffer << std::endl;
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    如果云服务器没有开放端口号,使用本地IP:

    127.0.0.1

    即可进行通信。

  • 相关阅读:
    ElementUI之首页导航+左侧菜单
    电子统计台账:处理时间与名称所在行有交错的流水账格式
    python,满分,砝码称重【第十二届】【省赛】【研究生组】
    frp内网穿透搭建-宝塔版
    双折射取向层对方位角锚定能的测量影响
    .NET Core HttpReports 监控
    基于Python的“书怡”在线书店系统的设计与实现毕业设计源码082332
    NLP论文解读:无需模板且高效的语言微调模型(上)
    海康设备、LiveNVR等通过GB35114国密协议对接到LiveGBS GB28181/GB35114平台的详细操作说明
    RabbitMQ(七)延迟队列
  • 原文地址:https://blog.csdn.net/qq_51492202/article/details/126233420