网络编程之套接字

端口 && IP

在学习套接字编程之前,我们必须了解一下前缀知识。首先是IP和端口的作用。

在这之前,我们要明白一件事。那就是把数据从一台主机发送到另一台主机,是目的吗???当然不是!!我们要把数据从一台主机发送到另一台主机指定中的一个进程,再由这个进程对数据进行处理后返回给用户。这才是目的!!那么我们通过IP找到了指定的主机,如何找到主机上指定的进程呢?? 那么我们就需要用到端口号(port)。

IP : 标识全网唯一一台主机(不太准确,暂时这么理解)

port 端口号 : 标识主机中唯一的一个进程

所以,IP + port 是不是就可以确定全网唯一 一个进程? 因为 IP确定全网唯一一台主机,端口号确定主机上唯一的一个进程。所以 IP + port 就可以确定全网唯一 一个进程。

而这个时候,我们主机与主机之间的通信,就变成了进程与进程之间的通信!进程间通信的本质是什么?是让多个进程可以看到同一份资源!在本地主机中这份资源可以是内存,也可以是文件。但是在多台主机中,这份资源就是我们熟悉的网络!!

所以我们可以用server把数据发送到网络中,再由对端的client从网络中读。就类似于读文件和写文件的操作一样,就可以实现跨网络的进程间通信!!

注意!

端口一旦确定,那么就不能改变!!因为在对应的客户端必须要指定服务器的端口,而此时如果服务器改变了自己的端口号,那么客户端也必须做到相应的改变!但显然客户并不会意识到这一点,所以端口号一旦确定,不要轻易改变!!!

TCP && UDP

接下来我们再浅浅了解一下TCP和UDP 。

TCP协议的特点

  • 传输层协议
  • 会建立连接
  • 可靠传输
  • 面向字节流

UDP协议的特点

  • 传输层协议
  • 不建立连接
  • 不可靠传输
  • 面向数据报

有连接 && 无连接

显然,UDP和TCP都是传输层协议,那么建立连接这个怎么理解呢?这就很类似两个人打电话,必须要先有一方给另一方播电话。而另一方看到电话来了只有在接通之后两人才能进行通信。而这个过程就类似于TCP的连接过程。

而UDP是无连接的,就意味着你给对方发送数据压根就不用等对方确认。就相当于你给对方打电话,对方不能自己接通,而是电话自动接通。这就是无连接 。

**可靠传输 && 不可靠传输 **

首先我们要知道,可靠和不可靠在这里都是一个中性词。并不是一个贬义词,因此不能因为UDP是不可靠传输就说UDP差。我们要知道,保证可靠性是有代价的!! 因为TCP提供了各种可靠性机制,所以这也就意味着TCP会非常复杂!而UDP是不可靠性传输,就意味着它比较简单。各有各的优势,还是看场景进行选择。

注意!!无论是UDP和TCP,没有明显的谁慢谁快。因为跨网络传输看的是两台主机之间的距离和当前的网络状态!!与协议关系并不大。

面向字节流 && 面向数据报

面向字节流就是以字节流的形式进行报文的发送,那么这就意味着发送的时候,报文可能并不完整!比如你主机的传输层缓冲区已经满了,但是你的报文只接收了前面一点点。就只能等缓冲区被提取后才会接收后面的数据。这种现象就是面向字节流的!

面向数据报则是每次发送/接收,都必须是完整的报文!不存在断截之后再续上的现象,这就叫做面向数据报传输!!

网络字节序

我们都知道内存的字节存储方式有大端和小端。

大端存储 高位放在低字节序,低位放在高字节序 如:0X11223344 在内存中存储是 11 22 33 44

小端存储 低位放在低字节序,高位放在高字节序 如:0X11223344 在内存中存储是 44 33 22 11

既然不同的机器有不同的存储方式,那你怎么保证你小端发送的数据和大端接收的数据是匹配的?

很简单!!只要规定在网络上传输的数据都必须按大端字节序来存储!!发送发如果是小端则只需要转到大端发送即可,接收方是小端再由大端转回小端即可,如果是大端则不需要操作。

但是如果让我们自己来转换,那是不是太麻烦了呢?所以操作系统提供了下面的接口来供我们转换序列。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong); //主机转网络序列,转成long类型
uint16_t htons(uint16_t hostshort); //主机转网络序列,转成short类型
uint32_t ntohl(uint32_t netlong); //网络序列转主机序列,转成long类型
uint16_t ntohs(uint16_t netshort); //网络序列转主机序列,转成short类型

其中的h代表主机host,to就是到的意思,n则是network网络的意思。l对应long,s对应short。所以上面的函数其实很好记忆。

Socket套接字编程

Socket是一个套接字,它的本质就是一个文件描述符。对应的是网卡文件,我们只需要往文件描述符里面写入,就可以把数据发送到网络。

函数介绍

套接字的创建(UDP/TCP/服务端/客户端)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

int domain : 通信范围,例如AF_UNIX,AF_LOCAL是本地通信,AF_INET是IPV4,AF_INET6是IPV6
int type: 通信类型,最常见的是TCP的SOCK_STREAM面向字节流,UDP的SOCK_DGRAM面向数据报
int protocol: 确定socket支持的哪个协议,一般默认为0即可。
返回值:一个文件描述符,小于0则代表创建套接字(打开文件)失败

端口号绑定(UDP/TCP/服务端/客户端)

对套接字绑定端口,一般服务器显示绑定。客户端OS自动绑定。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

*int sockfd: 要绑定的套接字文件描述符
const struct sockaddr addr :一个结构体的指针,该结构体包含了domain,port,ip等信息…
socklen_t addrlen:结构体体长度
返回值:成功返回0,绑定失败返回-1,或者返回错误码

这个结构体的首元素是一个16位地址类型,虽然下面的内容都不一样,但只要统一强转成 struct sockaddr,那么就可以拿到前16位。再根据前16位判断出结构体的类型,再做出对应的处理。我们可以把sockaddr 看成是一个父类,sockaddr_in 和 sockaddr_un 是它的子类。而无论我们是使用sockaddr_in还是sockaddr_un,我们都可以用sockaddr来接收。再根据前16位判断地址类型,做出选择。这就很像我们的多态,因为这个功能出来的时候,C语言没有void*指针这个功能,所以我们传入时必须要进行类型强转。

在这里插入图片描述

监听套接字(TCP/服务端)

UDP不会用到监听套接字这个函数,因为UDP是无连接的。而TCP是有连接的,所以必须把这个套接字设置为监听状态。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

int sockfd:要监听的套接字
int backlog:连接同时存在的最大数量(因为监听是有消耗的,如果大量连接过来,监听不过来了,那么就会把这些连接暂时存储起来,而backlog就是存储的最大数量。)
返回值: 成功返回0,监听失败返回-1,或者返回错误码

接收连接请求(TCP/服务端)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int sockfd  监听后的套接字

**struct sockaddr addr: 存储对端domain,port,ip的结构体
socklen_t addrlen: 结构体的大小
返回值: 一个可以直接通信的套接字

建立连接(TCP/客户端)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

int sockfd:的套接字

**const struct sockaddr *addr: ** 对端的addr结构体,可以提取到对端的IP和端口

**socklen_t addrlen: ** 要接收的长度。

UDP通信测试

介绍2个函数,UDP传输要用的两个函数。 因为udp是无连接的,所以需要特定的函数来进行通信。

sendto函数

#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

该函数的功能是往网络套接字中发送数据,如果调用该函数时套接字还没有绑定端口,那么该函数会自动为套接字随机分配端口。

**int sockfd: 套接字 **

*void buf, size_t len: 发送的数据

size_t len:发送数据的大小

int flags:发送方式,0为阻塞发送

*const struct sockaddr dest_addr:该参数包含着目标主机的IP和端口,根据该参数找到服务器

socklen_t addrlen:传入的sockaddr的长度

recvfrom函数

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

该函数的功能是往从网络套接字中读取数据。

int sockfd: 套接字

*void buf, size_t len: 读取数据的缓冲区

size_t len:读取数据的大小

int flags:是否阻塞读取,0为阻塞

*const struct sockaddr dest_addr:输出型参数,该参数对端主机的IP和端口,可以用来获取对端的主机IP和端口

socklen_t addrlen:接收的sockaddr 的长度

udp代码演示

服务器代码

server.cc文件

#include "server.hpp"
#include <memory>

int main(int argc , char* argv[])
{
    if(argc != 2) //命令行参数不为2就退出
    {
        std::cout << "Usage : " << argv[0] << "   bindport" << std::endl;  //打印使用手册
        exit(1);
    }
    uint16_t port = atoi(argv[1]); //命令行传的端口转换成16位整形
    std::unique_ptr<UdpServer> s(new UdpServer(port)); //创建UDP服务器
    s->init(); //初始化服务器,创建 + 绑定
    s->start(); //运行服务器
}

server.hpp 代码:

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>

class UdpServer
{
private:
    int _sock; 
    uint16_t _port;
public:
    UdpServer(uint16_t port): _port(port) { }
    ~UdpServer() { close(_sock); }
    void init()
    {
        _sock = socket(AF_INET,SOCK_DGRAM,0);  //创建套接字
        if(_sock < 0)
        {
            //创建失败
            std::cout << "create socket failed...." << std::endl;
            abort();
        }
        //绑定 
        struct sockaddr_in ser; 
        ser.sin_port = htons(_port);  //填入端口
        ser.sin_family = AF_INET; // 填入地址类型
        ser.sin_addr.s_addr = INADDR_ANY; //填入IP地址
        if(bind(_sock,(sockaddr*)&ser,sizeof ser) != 0) //绑定
        {
            //绑定失败
            std::cout << "bind socket failed...." << std::endl;
            abort();
        }
    }

    void start()
    {
        struct sockaddr_in peer; //对端
        socklen_t peer_len = sizeof peer;
        char buff[1024] = {0};   
        while(1)
        {
            //接收对端发来的消息
            int n = recvfrom(_sock,buff,1023,0,(struct sockaddr*)&peer,&peer_len); 
            buff[n] = 0;
            if(read == 0) //对端关闭了
            {
                std::cout << "one client quit..." << std::endl;
                continue;
            }else if(read < 0) //读取出错了
            {
                 std::cout << "read error..." << std::endl;
                 break;
            }
            //读取正常...
            std::string clientip = inet_ntoa(peer.sin_addr); //获取对端的IP
            uint16_t clientport = ntohs(peer.sin_port);// 获取对端的端口
            //打印信息....
            std::cout << "["<< clientip <<"] "<< clientport << ": say : " << buff << std::endl; 
            
        }
    } 
};

客户端代码

client.cc:

#include "client.hpp"
#include <memory>

int main(int argc , char* argv[])
{
    if(argc != 3) //命令行参数少于3个退出,因为必须 ./client 服务器ip 端口号  才可以调用
    {
        std::cout << "Usage : " << argv[0] << "   serverip  serverport" << std::endl; 
        exit(1);
    }
    uint16_t port = atoi(argv[2]); //获取服务器端口
    std::string ip = argv[1]; //获取服务器IP
    std::unique_ptr<UdpClient> cli(new UdpClient(port,ip)); //创建客户端
    cli->init(); //初始化客户端
    cli->start(); //客户端运行
}

client.hpp

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

class UdpClient
{
public: 
    UdpClient(uint16_t port , const std::string& ip) : _port(port), _svr_ip(ip){}
    ~UdpClient(){ close(_sock); }

    void init()
    {
        //创建套接字
         _sock = socket(AF_INET,SOCK_DGRAM,0); 
        if(_sock < 0)
        {
            std::cout << "create socket failed...." << std::endl;
            abort();
        }   
    }
    void start()
    {
        struct sockaddr_in svr; //该结构体填入服务器的端口和IP
        svr.sin_port = htons(_port);  //填入端口
        svr.sin_addr.s_addr = inet_addr(_svr_ip.c_str());  //填入IP
        svr.sin_family = AF_INET; //设置地址类型,ipv4
        int i = 1;  //打印数字,随意,可要可不要
        //char buff[1024] = {0};  如果需要读数据可用的输出缓冲区
        while(1)
        {
            std::string message = "hello server " + std::to_string(i++); //构建发送消息
            //发送消息
            sendto(_sock,message.c_str(),message.size(),0,(struct sockaddr*)&svr,sizeof svr);
            //可以通过下面代码再接收服务器发送的数据,如果服务器有发送的话
            // int n = recvfrom(_sock,buff,1023,0,nullptr,nullptr);
            // buff[n] = 0 ;
            // std::cout << "server say# " << buff << std::endl;
            sleep(1); // 睡眠1s
        }
    }

private: 
    int _sock; //套接字
    uint16_t _port; //服务器的端口
    std::string _svr_ip; //服务的IP

};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Hdoop学习笔记(HDP)-Part.20 安装Flume

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

java学习part27线程死锁

基本就是操作系统的内容 138-多线程-线程安全的懒汉式_死锁_ReentrantLock的使用_哔哩哔哩_bilibili

基于SpringBoot+Vue的前后端分离的房屋租赁系统2

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 开发过程中&#xff0…

基于Django框架搭建的协同过滤算法电影推荐网站-爬取的豆瓣电影数据

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介概述技术栈实现流程 二、功能三、系统四. 总结 一项目简介 # 电影推荐网站介绍 概述 该电影推荐网站是基于Django框架搭建的&#xff0c;旨在为用户提供个…

前端文本省略号后面添加复制文字

前端文本省略号后面添加复制文字 1、效果图 2、代码展示 <div class"link-content-wrap" click"copyLinkText"><div class"link-content">{{ shareResult.url || }} </div><span class"show-ellipsis" click&…

Linux MIPI 调试中常见的问题

一、概述 做嵌入式工作的小伙伴知道&#xff0c;有时候程序编写没有调试过程中费时&#xff0c;之间笔记里有 MIPI 摄像头驱动开发的过程&#xff0c;有需要的小伙伴可以参考&#xff1a;Linux RN6752 驱动编写。而我也是第一次琢磨 MIPI 协议&#xff0c;其中有很多不明白的地…

本科毕业生个人简历23篇

刚毕业的本科生如何制作一份令招聘方印象深刻的简历&#xff1f;可以参考以下这23篇精选的本科毕业生应聘简历案例&#xff01;无论您的专业是什么&#xff0c;都能从中汲取灵感&#xff0c;提升简历质量&#xff0c;轻松斩获心仪职位&#xff01;小伙伴们快来看看吧&#xff0…

LeetCode刷题---合并两个有序链表

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏&#xff1a;http://t.csdnimg.cn/ZxuNL http://t.csdnimg.cn/c9twt 前言&#xff1a;这个专栏主要讲述递归递归、搜索与回溯算法&#xff0c;所以下面题目主要也是这些算法做的 我讲述…

3D模型材质编辑

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 如今&#xff0c;3D 纹理、打印和建模都非常流行。使用可用的高级工具&#xff0c;创建 3D 模型…

24双非硕的秋招总结

24 双非硕的秋招总结 结果&#xff1a; 运气捡漏去了腾讯 想想自己整个研究生学习过程&#xff0c;还是挺坎坷的&#xff0c;记录一下&#xff0c;也给未来的同学提供一些参考。 研一 我是研一上开始学前端的&#xff0c;应该是21年10月份左右&#xff0c;我们实验室是专门…

Nacos多数据源插件

Nacos从2.2.0版本开始,可通过SPI机制注入多数据源实现插件,并在引入对应数据源实现后,便可在Nacos启动时通过读取application.properties配置文件中spring.datasource.platform配置项选择加载对应多数据源插件.本文档详细介绍一个多数据源插件如何实现以及如何使其生效。 注意:…

DOM 事件的传播机制

前端面试大全DOM 事件的传播机制 &#x1f31f;经典真题 &#x1f31f;事件与事件流 事件流 事件冒泡流 事件捕获流 标准 DOM 事件流 &#x1f31f;事件委托 &#x1f31f;真题解答 &#x1f31f;总结 &#x1f31f;经典真题 谈一谈事件委托以及冒泡原理 &#x1f3…

134. 加油站(贪心算法)

根据题解 这道题使用贪心算法&#xff0c;找到当前可解决问题的状态即可 「贪心算法」的问题需要满足的条件&#xff1a; 最优子结构&#xff1a;规模较大的问题的解由规模较小的子问题的解组成&#xff0c;规模较大的问题的解只由其中一个规模较小的子问题的解决定&#xff…

蓝桥第一期模拟总结

文章目录 1.动态的 Tab 栏2.地球漫游3.迷惑的this4.燃烧卡路里5.魔法失灵了6.年龄统计 所有题目链接 1.动态的 Tab 栏 本题要实现一个tab栏固定效果&#xff0c;看见题目就想到css中的 position: fixed; 尝试了很久都没能实现效果&#xff0c;后来又想到了粘性定位 position: …

【深度学习】深度学习框架的环境配置

目录 1. 配置cuda环境 1.1. 安装cuda和cudnn 1.1.1. 显卡驱动配置 1.1.2. 下载安装cuda 1.1.3. 下载cudnn&#xff0c;将解压后文件复制到cuda目录下 1.2. 验证是否安装成功 2. 配置conda环境 2.1. 安装anaconda 2.2. conda换源 2.3. 创建conda环境 2.4. pip换源 3…

封装员工头像组件

创建image-upload组件 <template><el-uploadclass"avatar-uploader"action"":show-file-list"false":before-upload"beforeAvatarUpload"><!-- (自动上传)action是上传地址 --><img v-if"value" :s…

第二次量子化

专栏目录: 高质量文章导航-持续更新中 前置复盘: 玻色子和费米子: 首先,我们希望把描述单粒子态的量子力学推广到全同多粒子体系。我们的做法是从单粒子态的希尔伯特空间(Hilbert Space)出发,构造全同多粒子态的态空间——福克空间(Fock Space),它实际上就是无穷个…

20231202 VCSA 7.0 日志清理及空间扩容

日志清理 ssh 到 VCSA 上&#xff0c;输入 shell 进入控制台 df -lh 查看空间大小&#xff0c;发现 /storage/archive 占据空间比较大&#xff0c;这里应该是备份日志的地方&#xff0c;里边文件不大但很多&#xff0c;查找并删除 30 天以前的日志 cd /storage/archive/vpostgr…

对于Windows就是找不到 环境变量 的解决

我认为将“我的电脑”从桌面上隐藏掉纯粹是傻逼行为 说下解决办法&#xff1a; 1. 找到文件资源管理器&#xff0c; 2. 右键点击“此电脑” -- 选择属性&#xff1a; 3. 进入属性界面&#xff0c;应该进入的是“关于”界面&#xff1a;选择“高级系统设置”&#xff1a; 4. 终…

更有效的问卷发布方法与必备问卷工具推荐

问卷怎么发&#xff1f;通过哪些渠道发&#xff1f;怎么发收集的数量更多&#xff1f;怎么获得有效数据&#xff1f;这些是做问卷的调查人员经常会遇到的问题。的确&#xff0c;问卷的发放是否有效不仅会影响到收集数据的体量&#xff0c;更会影响到最终结论的真实性。所以&…