【Linux Network】网络编程套接字

news2024/11/17 13:23:10

目录

1. 源IP地址与目的IP地址的认识

2. 端口号的认识

3. 套接字socket

4. TCP协议和UDP协议

5. 网络字节序

6. socket编程

7. socket编程接口

8. 使用UDP协议跨网络通信程序


 Linux网络编程✨

1. 源IP地址与目的IP地址的认识

在因特网上,一台主机和一个IP地址往往是一一对应的。

但还有例外:一个网卡可以使用多个IP地址,但总的来说唯一的一个IP地址便可以确定一个主机;

主机A 有自己的一个IP地址,主机C 有自己的一个IP地址;中间的路由器跨两个网段,有两个IP地址;

使用路由器连接的两个不同网段的主机A 和主机C通信:

主机A要向主机C发送数据时,数据经过封装后,首先由主机A的IP地址转发给路由器左边端口的IP,经过路由器转发至右边的端口IP,最后转发至主机C的IP;

在这期间经过多个IP地址的转化,但源IP地址和目的IP地址是不变的;

源IP地址:主机A的IP地址;

目的IP地址:主机C的IP地址;

2. 端口号的认识

在计算机通信中,虽然看起来是各主机之间的通信,但其本质是两台主机上的两个进程之间的通
信,计算机网络只不过是两进程通信的临界资源,如何确定这两个进程呢?
我们使用IP地址可以确定相互通信的两台主机,但无法确定是主机上的哪个进程,这时我们便要使
用到端口号;
端口号:
端口号 (port) 是传输层协议的内容, 是一个2字节16位的整数,用来标识一个进程, 告诉操作系统当
前的这个数据要交给哪一个进程来处理;
总的来说:端口号:唯一的标识一台机器上的唯一的一个进程,并且 一个端口号只能被一个进程占
用;
源端口号和目的端口号:
传输层协议 (TCP UDP) 的数据段中有两个端口号 , 分别叫做源端口号和目的端口号 . 就是在描述
" 数据是谁发的 , 要发给谁"(类似于源IP地址和目的IP地址);

3. 套接字socket

在一个计算机内部,我们可以使用 pid 来确定是哪两个进程进行通信;
在计算机网中,我们便要使用套接字来确定是哪两个进程相互通信;
套接字:IP(确定通信的两台主机)+端口号(确定主机上的进程);
pid表示唯一一个进程,此处端口号也是唯一确定一个进程,在这里使用端口号而不使用pid,主要
是为将网络和OS两个学科进行一个解耦,避免出现OS改变导致整个网络也要跟着变的情况;
在这里提一下:
一个进程可以绑定多个端口号 ; 但是一个端口号不能被多个进程绑定,由此端口号和进程是1:1的 ;

4. TCP协议和UDP协议

TCP协议:
此处我们先对 TCP(Transmission Control Protocol 传输控制协议 ) 有一个直观的认识 ;
后面我们再详细讨论 TCP 的一些细节问题.
  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流
UDP协议:
此处我们也是对 UDP(User Datagram Protocol 用户数据报协议 ) 有一个直观的认识 ;
后面再详细讨论 .
  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

注意:

上述两种协议并无好坏之分,具体场景选择合适的协议;

5. 网络字节序

我们已经知道 , 内存中的多字节数据相对于内存地址有大端和小端之分;
磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分; 
网络数据流同样有大端小端之分 . 那么如何定义网络数据流的地址呢 ?
  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

总的来说:不管主机是大端存储还是小端存储,在网络数据流中,总是大端的; 

为使网络程序具有可移植性 , 使同样的 C 代码在大端和小端计算机上编译后都能正常运行 , 可以调用
以下库函数做网络字节序主机字节序的转换。
  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

6. socket编程

sockaddr结构

socket API 是一层抽象的网络编程接口, 适用于各种底层网络协议, IPv4 IPv6, 以及后面要讲
的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同;
  • IPv4IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32IP地址;
  • IPv4IPv6地址类型分别定义为常数AF_INETAF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
  • socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;
sockaddr 结构
sockaddr_in 结构
虽然socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时 , 使用的数据结构是
sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号 , IP 地址 .
in_addr 结构
in_addr用来表示一个IPv4 IP 地址 . 其实就是一个 32 位的整数;

7. socket编程接口

socket:创建套接字:

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

返回值:

  • 套接字创建成功返回一个文件描述符 = ,创建失败返回-1,同时错误码会被设置。

参数:

  • domain:创建套接字的域或者叫做协议家族,也就是创建套接字的类型。该参数就相当于struct sockaddr结构的前16个位。如果是本地通信就设置为AF_UNIX,如果是网络通信就设置为AF_INET(IPv4)或AF_INET6(IPv6)
  • type:创建套接字时所需的服务类型。其中最常见的服务类型是SOCK_STREAM和SOCK_DGRAM,如果是基于UDP的网络通信,我们采用的就是SOCK_DGRAM,叫做用户数据报服务,如果是基于TCP的网络通信,我们采用的就是SOCK_STREAM,叫做流式套接字,提供的是流式服务。
  • protocol:创建套接字的协议类别。你可以指明为TCP或UDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。
     
struct sockaddr_in结构体

在绑定时需要将网络相关的属性信息填充到一个结构体当中,然后将该结构体作为bind函数的第二个参数进行传入,这实际就是struct sockaddr_in结构体。

struct sockaddr_in当中的成员如下:

  • sin_family:表示通信机制(本地/网络)。
  • sin_port:表示端口号,是一个16位的整数。
  • sin_addr.s_addr:表示IP地址,是一个32位的整数。

剩下的字段一般不做处理,当然你也可以进行初始化。

其中sin_addr的类型是struct in_addr,实际该结构体当中就只有一个成员,该成员就是一个32位的整数,IP地址实际就是存储在这个整数当中的。

bind:绑定端口号:

// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

返回值:

  • 绑定成功返回0,绑定失败返回-1,同时错误码会被设置。

参数:

  • socket:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
  • addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。
读取数据:recvfrom函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

返回值:

  • 读取成功返回实际读取到的字节数,读取失败返回-1,同时错误码会被设置。

参数:

  • sockfd:对应操作的文件描述符。表示从该文件描述符索引的文件当中读取数据。
  • buf:读取数据的存放位置。
  • len:期望读取数据的字节数。
  • flags:读取的方式。一般设置为0,表示阻塞读取。
  • src_addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:调用时传入期望读取的src_addr结构体的长度,返回时代表实际读取到的src_addr结构体的长度,这是一个输入输出型参数。

注意:

  • 由于recvfrom函数提供的参数也是struct sockaddr类型的,因此我们在传入结构体地址时需要将struct sockaddr_in类型进行强转。
     
发送数据:sendto函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

返回值:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

参数:

  • sockfd:对应操作的文件描述符。表示将数据写入该文件描述符索引的文件当中。
  • buf:待写入数据的存放位置。
  • len:期望写入数据的字节数。
  • flags:写入的方式。一般设置为0,表示阻塞写入。
  • dest_addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。 addrlen:传入dest_addr结构体的长度。

8. 使用UDP协议跨网络通信程序

服务器:

  • 1. 创建套接字;
  • 2. 定义结构体;
  • 3. 绑定端口;
  • 4. 通信; 
客户端:
  • 1. 创建套接字;
  • 2. 定义结构体;
  • 3. 通信; 
结果演示:
源代码:
  • makefile
.PHONY:all
all:udp_server udp_client

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

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

.PHONY:clean
clean:
	rm -f udp_client udp_server
  • 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>

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); //此处的端口号,是我们计算机上的变量,是主机序列
    // a. 需要将人识别的点分十进制,字符串风格IP地址,转化成为4字节整数IP
    // b. 也要考虑大小端
    // in_addr_t inet_addr(const char *cp); 能完成上面ab两个工作.
    // 坑: 
    // 云服务器,不允许用户直接bind公网IP,另外, 实际正常编写的时候,我们也不会指明IP
    // local.sin_addr.s_addr = inet_addr("42.192.83.143"); //点分十进制,字符串风格[0-255].[0-255].[0-255].[0-255]
    // INADDR_ANY: 如果你bind的是确定的IP(主机), 意味着只有发到该IP主机上面的数据
    // 才会交给你的网络进程, 但是,一般服务器可能有多张网卡,配置多个IP,我们需要的不是
    // 某个IP上面的数据,我们需要的是,所有发送到该主机,发送到该端口的数据!
    local.sin_addr.s_addr = INADDR_ANY;

    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);

        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);
    }

    return 0;
}
  • udp_client.cc
#include <iostream>
#include <string>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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]);
    socklen_t len = sizeof(server);
    // 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];
        socklen_t len = sizeof(server); 
        recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&server, &len);

        std::cout << "server echo# " << buffer << std::endl;
    }

    return 0;
}

如果上述文章对您有所帮助的话,还请点赞👍,收藏😉,关注🎈

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

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

相关文章

【VM服务管家】VM4.2平台SDK_6.1 环境配置类

目录 2.1.1 环境配置&#xff1a;基于Csharp二次开发环境配置方法2.1.2 环境配置&#xff1a;基于MFC二次开发环境配置方法2.1.3 环境配置&#xff1a;基于Qt二次开发环境配置方法2.1.4 用户权限&#xff1a;普通以EXE方式启动Server的方法2.1.5 环境配置&#xff1a;程序启动后…

基于类别级正则化的无监督域自适应眼底图像分割

文章目录 Unsupervised Domain Adaptive Fundus Image Segmentation with Category-Level Regularization摘要方法Inter-domain Category RegularizationSource Domain Category RegularizationTarget Domain Category Regularization总损失 Unsupervised Domain Adaptive Fund…

总结目前敏捷开发框架(持续更新....)

文章目录 0 敏捷开发1 类型分类1.1. Scrum1. 2.极限编程&#xff08;XP&#xff09;1. 3. 快速应用程序开发 (RAD)1. 4. 动态系统开发方法&#xff08;DSDM&#xff09;1.5.统一流程&#xff08;UP&#xff09;1. 6. 精益方法1. 7. 看板1. 8.FDD&#xff08;功能驱动开发&#…

2023年最新版【接口自动化测试,web自动化测试,app自动化测试】全套自动化测试面试题

前言&#xff1a; 自动化测试是软件测试中的一个重要领域&#xff0c;它可以帮助企业提高软件开发质量、缩短测试周期和降低测试成本。随着信息技术的不断发展&#xff0c;自动化测试也在不断地创新和发展。本篇文章收集了2023年最新版的接口自动化测试、Web自动化测试和App自…

通俗理解CNN感受野的计算方法

x o u t x i n − k s 1 x_{out} \frac{x_{in} - k}{s} 1 xout​sxin​−k​1 如果不考虑padding&#xff0c;卷积输出的feature map的计算公式如上&#xff0c;那么 x i n ( x o u t − 1 ) ∗ s k x_{in} (x_{out} - 1) * s k xin​(xout​−1)∗sk。因此计算模型的感…

springbooot使用google验证码

springbooot使用google验证码 1、使用场景2、springboot使用google验证码1、引入依赖2、编写配置类3、编写控制层4、前端实现 1、使用场景 由于需要做一个前后端分离的项目&#xff0c;想着使用google验证码&#xff0c;由于年龄大了&#xff0c;这些知识啊&#xff0c;用完就…

防伪标志使用的全息薄膜,竟可合成大自然的“结构色”

大自然为生物赋予了各种各样的色彩&#xff0c;除了常见的赤橙黄绿青蓝紫外&#xff0c;还有反光效果很好的金属色等等。有趣的是&#xff0c;一些生物身上的颜色也能像金属那样闪闪发光&#xff0c;在不同光线下颜色甚至还会有变化。实际上&#xff0c;大自然很多色彩由色素产…

全注解下的SpringIoc 续6-多环境配置

我们都知道&#xff0c;在企业开发过程中&#xff0c;一个项目往往都会有开发、测试、仿真、生产等环境配置&#xff0c;除了使用配置中心&#xff08;比如Apollo等&#xff09;之外&#xff0c;Spring Boot也提供了不同环境之间切换的机制&#xff0c;下面让我们来一起看看。 …

Docker安全最佳实践

目录 1、探测容器开放端口和服务漏洞 2、宿主机、网络、镜像、DockerApi安全 3、更新Docker、日志、事件 4、Docker安全测试 5、Docker安全最佳实践 1、探测容器开放端口和服务漏洞 使用Nmap扫描Docker容器中的开放端口 使用docker ps命令获取正在运行的容器ID或名称。在…

密码学作业——置换密码部分

part1: encrypt加密函数_代码补充&#xff1a; /*请在此处添加*/cout<<Substition[plain[i]]; part2:Decrypt解密函数_代码补充: /*请在此处添加代码*/ cout<<ReverseTable[cipher[i]];part3:设置 转换表Substition[]部分: // 将i对应的置换表元素设为s1中对应…

prometheus实战之三:告警规则

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 本篇概览 本文是《prometheus实战》系列的第三篇&#xff0c;一起来学习prometheus的告警功能&#xff0c;如下图所示&#xff0c;整个告警功能分为规则和通…

Python使用AI animegan2-pytorch制作属于你的漫画头像/风景图片

Python使用AI animegan2-pytorch制作属于你的漫画头像 1. 效果图2. 原理3. 源码参考 git clone https://github.com/bryandlee/animegan2-pytorch cd ./animegan2-pytorch python test.py --photo_path images/photo_test.jpg --save_path images/animegan2_result.png1. 效果图…

全球首个机器人辅助试管婴儿降生

近日&#xff0c;两名通过机器人辅助受精手术成功诞生的女婴成为全球瞩目的焦点。这是全球首批由机器人成功辅助受精的婴儿案例&#xff0c;预示着未来生育技术的发展趋势。 以往&#xff0c;试管婴儿受精过程中将精子注入卵子内的环节都是由人工完成。胚胎技术人员需要在显微镜…

最简单的循环

☃️个人主页&#xff1a;fighting小泽 &#x1f338;作者简介&#xff1a;目前正在学习C语言和数据结构 &#x1f33c;博客专栏&#xff1a;C语言学习 &#x1f3f5;️欢迎关注&#xff1a;评论&#x1f44a;&#x1f3fb;点赞&#x1f44d;&#x1f3fb;留言&#x1f4aa;&am…

6. Docker——详说镜像

本章讲解知识点 Docker 镜像 Union File System&#xff08;联合文件系统&#xff09;技术 回说 Docker 镜像分层 Docker 镜像分层原理 1. Docker 镜像 镜像是一种轻量级、可执行的独立软件包&#xff0c;用来打包软件运行环境和基于运行环境开发的软件&#xff0c;它包含…

一文了解 Zebec Labs 投资的 Coral Finance,空投计划或在不久推出

在前不久&#xff0c;Zebec Labs 宣布对链上衍生品协议 Coral Finance 进行150万美元的投资&#xff0c;以帮助该协议完成早期启动并&#xff0c;并在后续持续的为其提供孵化支持。Coral Finance 将在不久部署在 Nautilus Chain 主网上。据了解&#xff0c;Coral Finance 是 Na…

【VM服务管家】VM4.2平台SDK_6.2 模块操作类

目录 2.2.1 流程操作&#xff1a;通过流程或Group设置输入输出图像的方法2.2.2 模块操作&#xff1a;设置输入图像、参数和ROI2.2.3 N点标定&#xff1a;清空标定点、生成标定文件2.2.4 分支字符&#xff1a;控制调试模式开关的方法2.2.5 条件检测&#xff1a;条件检测模块设置…

【GAMES101】02 Review Of Linear Algebra

1.点乘&#xff1a; 向量点乘 → 一个数值 点乘在图形学中的应用&#xff1a; 找到两个方向之间的夹角。找到一个向量投影到另一个向量上是什么样的。计算两个向量的方向&#xff08;是接近还是远离&#xff09;判定高光范围&#xff0c;从1&#xff08;重合&#xff…

【VM服务管家】VM4.2平台SDK_6.5 全局类

目录 2.5.1 全局相机&#xff1a;获取全局相机列表和设置相机参数的方法 2.5.1 全局相机&#xff1a;获取全局相机列表和设置相机参数的方法 描述 环境&#xff1a;VM4.2 VS2013及以上 问题&#xff1a;问题1&#xff1a;如何获取方案中所有的全局相机的连接状态&#xff0c;…

c++基础-分支语句

目录 if语句 if-else switch语句 if语句 在C中&#xff0c;if语句是一种分支结构&#xff0c;它允许程序根据条件执行不同的操作。可以使用if语句来检查一个条件是否为真&#xff0c;如果为真就执行一个语句块&#xff0c;否则执行另一个语句块。 if语句的语法如下&#xff…