IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 网络套接字实现UDP机制通信 -> 正文阅读

[系统运维]网络套接字实现UDP机制通信

? ? ? ?今天是开学上线下课的第一天,博主没有成功逃课,所以写博客的时候都块晚上了。接下来,博主会连续更新博客,主要因为我的云服务器快过期了~

目录

认识UDP协议

网络字节序

调用接口

IP地址搭配调用接口

inet_addr

inet_ntoa

端口号搭配调用接口(常用)

htons

ntohs?

socket常见API

socket(TCP/UDP)

?bind(TCP/UDP)

listen(TCP)

accept(TCP)

connect(TCP,客户端)

UDP读取数据调用接口

recvfrom

sendto

注意

执行命令的函数调用

popen

pclose

UDP通信代码实现

udp_client.cc

udp_server.cc

Makefile

运行测试

UDP实现总结

服务端

客户端


认识UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识;
1、传输层协议

2、无连接
3、不可靠传输
4、面向数据报

关于UDP更多的知识,会在传输层报文那里详细讲解,现在还不到时候~

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

调用接口

IP地址搭配调用接口

inet_addr

inet_addr:将点分十进制,人识别的字符串IP地址,转换为4字节整数的IP,并考虑大小端。

inet_ntoa

inet_ntoa:将4字节整数的IP地址,转换为字符串风格的IP地址。

端口号搭配调用接口(常用)

htons

ntohs?

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。

举个栗子:

htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。?

socket常见API

socket(TCP/UDP)

int domain:协议家族类型。

int type:通信类型。

int protocol:选项,前两个参数指明后,最后一个参数不用指明,赋值为0就可以了。

返回值:返回一个新创建的文件描述符。

是不是还算是不太清楚?别急,往下看:

?bind(TCP/UDP)

返回值:0表示成功,-1表示失败。

sockfd:socket创建好的文件描述符。

const struct sockaddr* addr:输入型参数,下面详讲。

socklen_t addrlen:定义结构体的大小。?

关于const struct sockaddr* addr的详讲:

我们先来看协议家族类型的分类:

? ? ? ? 我们通常使用AF_INET,记住,绑定的时候有一个细节,参数类型是struct sockaddr*,所以我们要手动强转一下类型。

listen(TCP)

int sockfd:socket的返回值。?

int backlog:能够建立全连接的最大个数+1。(这个到后面再来解释,现在还不太合适,只要记住我们在使用的时候给个5,目前就够了)

返回值:0表示成功,-1表示失败。

accept(TCP)

?int sockfd:socket的返回值。?

注意:后面两个参数是输出型参数!可以取出客户端的相关信息。

connect(TCP,客户端)

?int sockfd:socket的返回值。?

后面两个参数参考上面的bind函数。

UDP读取数据调用接口

recvfrom

int sockfd:socket的返回值。?

void* buf:自己提供的缓冲区,要把数据读取到该缓冲区里面。

size_t len:缓冲区大小。

int flags:我们不用关心这个选项,设置为0就可以了。

struct sockaddr* src_addr:输入输出型参数,可以拿到对方的信息,为方便sendto使用。

socklen_t addrlen:结构体大小。

返回值:读取数据字节大小。

sendto

int sockfd:socket的返回值。?

const void* buf:要发送数据对应缓冲区的地址。

size_t len:缓冲区的大小。

int flags:我们不用关心这个选项,设置为0就可以了。

struct sockaddr* src_addr:输入型参数,要指明通信对端的信息。

socklen_t addrlen:结构体大小。

注意

TCP通信的话,我们通常使用read和write进行读写,这个和UDP还是有一点区别。

执行命令的函数调用

popen

const char* command:字符串指令。

const char* type:r->连接到command的标准输出 w->连接到标准输入。

pclose

关闭文件指针,防止内存泄漏。?

举个栗子:

UDP通信代码实现

udp_client.cc

#include<iostream>
#include<string>
#include<cerrno>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<string.h>

using namespace std;

void Usage(string proc) //不能使用引用 --- 这里构造函数
{
    cout << "Usage: \n\t" << proc << " server_ip server_port" << 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)
    {
        cerr << "socket error: " << errno << 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]); //字符串IP转化为4字节整型IP,并考虑大小端

    //2、使用服务
    while(1)
    {
        //a、你的数据从哪里来?
        //string message;
        //cout << "输入# ";
        //cin >> message;

        //旧版本
        // string message;
        // cout << "输入# ";
        // fflush(stdout);
        // cin >> message;

        //新版本
        cout << "MyShell$ ";
        char line[1024];
        fgets(line, sizeof(line), stdin);

        sendto(sock, line, strlen(line), 0, (struct sockaddr*)&server, sizeof(server));

        //sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server,sizeof(server));

        //此处tmp就是一个"占位符"
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buff[1024];
        ssize_t cnt = recvfrom(sock, buff, sizeof(buff)-1, 0, (struct sockaddr*)&tmp, &len);
        if(cnt > 0)
        {
            //在网络通信中,只有报文大小,或者是字节流字节的个数,没有C/C++字符串
            //这样的概念(虽然我们后序可能会经常遇到这样的情况)
            buff[cnt] = 0;
            cout << buff << endl;
        }
        //cout << "server echo# " << buff << endl;
    }
    return 0;
}

udp_server.cc

#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

const uint16_t port = 8080;

// udp_server细节后面慢慢完善

string Usage(string proc)
{
    cout << "Usage: " << proc << " port" << endl;
}
//  ./udp_server.cc port
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return -1;
    }

    uint16_t port = atoi(argv[1]);

    // 1、创建套接字,打开网络文件
    int sock = socket(AF_INET /*协议种族*/, SOCK_DGRAM /*通信类型*/, 0);
    if (sock < 0)
    {
        cerr << "socket create error: " << errno << endl;
        return 1;
    }

    // 2、给该服务器绑定端口和ip(特殊处理)
    struct sockaddr_in local;   // _in 网络通信 _un域间通信
    local.sin_family = AF_INET; // 16位地址类型
    //此处的端口号,是我们计算机上的变量,是主机序列
    // a、需要将人识别的点分十进制,字符串风格的IP地址,转换为4字节整数IP
    // b、也要考虑大小端
    // in_addr_t inet_addr(const char* cp);能完成上面ab两个工作
    //坑:
    //云服务器,不允许用户直接bind公网IP,另外,实际正常编写的时候,我们也不会指明IP
    // local.sin_addr.s_addr = inet_addr("120.53.247.65") [0-255].[0-255].[0-255].[0-255]
    // INADDR_ANY: 如果你bind的是确定的IP(主机),意味着只有发到该IP主机上面的数据
    //才会交给你的网络进程,但是,一般服务器可能有多张网卡,配置多个IP,我们需要的不是
    //某个IP上面的数据,我们需要的是,所有发送到该主机,发送到该端口的数据!
    local.sin_port = htons(port); // h to n 主机转网络 s 16位短整型

    local.sin_addr.s_addr = INADDR_ANY; //((in_addr_t) 0x00000000) 127.0.0.1本地环回
    if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        cerr << "bind error: " << errno << endl;
        return 2;
    }

    // 3、提供服务
    bool quit = false;
#define NUM 1024
    char buff[NUM];
    while (!quit)
    {
        struct sockaddr_in perr;
        socklen_t len = sizeof(perr);
        //注意: 我们默认认为通信的数据是双方互发字符串

        //sizeof(buff)-1 读满时,是可以防止后面buff[cnt] = 0越界
        ssize_t cnt = recvfrom(sock, buff, sizeof(buff)-1, 0, (struct sockaddr *)&perr, &len);
        if(cnt > 0)
        {
            buff[cnt] = 0;//0 == '\0', 可以当作一个字符串命令
            //r读取buff的字符串内容,识别命令,然后执行
            FILE* fp = popen(buff, "r");//r->连接到buff的标准输出 w->连接到标准输入

            string echo_hello;
            char line[1024] = {0};
            while(fgets(line, sizeof(line), fp) != nullptr) //读取失败就返回空,按行读取
            {
                echo_hello += line;
            }

            pclose(fp);
            cout << "client# " << buff << endl;

            //根据用户输入,构建一个新的返回字符串
            //echo_hello += "..."
            sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr*)&perr, len);
        }
        

    }
    return 0;
}

Makefile

.PHONY:all
all:udp_client udp_server

udp_client:udp_client.cc
	g++ -o $@ $^ -std=c++11

udp_server:udp_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udp_client udp_server

运行测试

UDP实现总结

服务端

1、创建套接字

?int sock = socket(AF_INET /*协议种族*/, SOCK_DGRAM /*通信类型*/, 0);

2、绑定ip和端口号

bind(sock, (struct sockaddr *)&local, sizeof(local))

3、提供服务

......

客户端

1、创建套接字

int sock = socket(AF_INET, SOCK_DGRAM, 0)

2、使用服务

......

注意:

? ? ? ?客户端不需要自己绑定确定的端口号,因为会存在自己绑定的端口号出现占用的问题。而一个端口只能被一个进程绑定,那么就会出现错误,客户端找客户端端口时出现问题。所以我们不需要手动去绑定,直接交给OSbind就可以了。相反,服务端必须要绑定,因为服务端的ip和端口号一定要被确定,才能被客户访问,服务端通常也不会随意修改端口号。

看到这里,给博主点个赞吧~

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-09-30 01:24:17  更:2022-09-30 01:24:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 10:02:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码