Liinux——(网络)socket编程

预备知识

源IP地址和目的IP地址

  • 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址

认识端口号

  • 端口号(port)是传输层协议的内容.
  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要发给谁

简单认识TCP协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

简单认识UDP协议

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

网络字节序

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

补充
计算机在存储数据时是有大小端的概念的:
大端模式: 数据的高字节内容保存在内存的低地址处,数据的低字节内容保存在内存的高地址处。
小端模式: 数据的高字节内容保存在内存的高地址处,数据的低字节内容保存在内存的低地址处。
在这里插入图片描述

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

网络字节序与主机字节序之间的转换
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。ntoh表示网络转主机,hton表示主机转网络
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

socket编程接口

socket常见API

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

参数解析:
int domain
参数 domain 用于指定一个通信域;这将选择将用于通信的协议族。下面是三个常用的协议簇

  • AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
  • AF_INET6 与上面类似,不过是来用IPv6的地址
  • AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用

int type

  • SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
  • SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的使用UDP来进行它的连接。
  • SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
  • SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
  • SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序

int protocol
参数 protocol 通常设置为 0,表示为给定的通信域和套接字类型选择默认协议。当对同一域和套接字类型支持多个协议时,可以使用 protocol 参数选择一个特定协议。

  • 在 AF_INET 通信域中,套接字类型为SOCK_STREAM 的默认协议是传输控制协议(TCP);
  • 在 AF_INET 通信域中,套接字类型为 SOCK_DGRAM 的默认协议时 UDP。

返回值:成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
在这里插入图片描述

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

socket上面介绍了这里不用多说

主要介绍一下这const struct sockaddr *address
这个结构体指针指向一个 struct sockaddr 类型变量,如下所示:
一般有三种结构 sockaddr sockaddr_in sockaddr_un(后面两种在使用的时候都要进行一下强制转换)

客户端不用bind,OS自动给其bind——客户端的port要随机OS分配防止出现启动冲突,
服务端要自己bind——服务器的端口不能随意改变,众所周知且不能随意改变 同一家公司的port号需要统一规范化
什么时候绑定呢?——在我们首次调用系统用发送数据的函数的时候,os会在底层随机选择clientport+自己的IP

struct sockaddr {
 sa_family_t sa_family;
 char sa_data[14];
}
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);//协议簇
    in_port_t sin_port;			/* 端口号  */
    struct in_addr sin_addr;		/* ip地址  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };

一般在使用时要将这个结构体的内容清零
在这里插入图片描述
这里有一个问题:在传输ip地址的时候,肯定时以字符串的风格如"1.1.1.1"但是我们这里要接收的话,是要以四字节整数为基础接收的,所以说,要进行转化(不是强转),那么该怎么办呢?
在这里插入图片描述

在这里插入图片描述
这里面的提供了解决办法

in_addr_t inet_addr(const char*cp)

这个接口的作用就是将一个字符串风格的ip地址,转换为四字节ip地址,并且默认内部已经将主机序列转换为了网络序列
在这里插入图片描述

注意事项:
云服务器:不需要bind ip地址,需要让服务器自己指定ip地址
自己本地装的虚拟机,或者是物理机是允许的

绑定的时候需要ip地址,但云服务器一般不要指明某一个确定的ip,一个服务器可能有多张网卡,或者说有多个ip,为了提高效率,我们直接使用INADDR_ANY绑定本主机上的 任意ip或者说是所有的ip,只要端口号是我们指明的端口号,都能转到机器上

UDP接收信息

在这里插入图片描述

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

返回值说明:

  • 成功则返回实际接收到的字符数,失败返回-1,错误原因会存于errno 中。

参数说明:

  • sockfd: socket描述符;

  • buf: UDP数据报缓存区(包含所接收的数据);

  • len: 缓冲区长度。

  • flags: 调用操作方式(一般设置为0)。

  • src_addr: 指向发送数据的客户端地址信息(需要知道客户端的ip和端口号)的结构体(sockaddr_in需类型转换)

addrlen:指针,指向实际输出时结构体的大小
在这里插入图片描述
在这里插入图片描述

UDP发送信息
 int sendto(int sockfd, const void *buf, ssize_t len, unsigned int flags, const struct sockaddr *dest_addr, int addrlen);

返回值说明:

  • 成功则返回实际传送出去的字符数,失败返回-1,错误原因会存于errno 中。

参数说明:

  • sockfd: socket描述符;

  • buf:UDP数据报缓存区(包含待发送数据);

  • len: UDP数据报的长度;

  • flags:调用方式标志位(一般设置为0);

  • dest_addr:  指向接收数据的主机地址信息的结构体(sockaddr_in需类型转换);

  • addrlen:发送的结构体的长度;

轮哭了,

用上面的接口应用的简单通信程序

err.hpp

#pragma once

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR
};

udp_client.cc

#include "udp_client.hpp"
#include <cstring>

// 127.0.0.1: 本地环回,就表示的就是当前主机,通常用来进行本地通信或者测试

static void usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n" << std::endl; 
}

// ./udp_client serverip serverport
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(SOCKET_ERR);
    }
    // client 这里要不要bind呢?要的!socket通信的本质[clientip:clientport, serverip:serverport]
    // 要不要自己bind呢?不需要自己bind,也不要自己bind,OS自动给我们进行bind -- 为什么?client的port要随机让OS分配防止client出现
    // 启动冲突 -- server 为什么要自己bind?1. server的端口不能随意改变,众所周知且不能随意改变的 2. 同一家公司的port号
    // 需要统一规范化

    // 明确server是谁
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    while(true)
    {
        
        // 用户输入
        std::string message;
        std::cout << "[请输入]# ";
         std::cin >> message;

        // 什么时候bind的?在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP,1. bind 2. 构建发送的数据报文
        //发送
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));

        //接受
        char buffer[1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int n = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "server echo# "<<buffer << std::endl;
        }
    }

    return 0;
}

}

udp_server.cc

#include "udp_server.hpp"
#include <memory>
#include <string>
#include <cstdio>

using namespace ns_server;
using namespace std;

static void usage(string proc)
{
    std::cout << "Usage:\n\t" << proc << " port\n"<< std::endl;
}


// ./udp_server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<UdpServer> usvr(new UdpServer(port));

    usvr->InitServer(); // 服务器的初始化
    usvr->start();

    return 0;
}


udp_client.hpp

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "err.hpp"


udp_server.hpp

#pragma once

#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unordered_map>
#include "err.hpp"

namespace ns_server
{
    const static uint16_t default_port = 8080;
    using func_t = std::function<std::string(std::string)>;

    class UdpServer
    {
    public:
        UdpServer(uint16_t port = default_port) : port_(port)
        {
            std::cout << "server addr: " << port_ << std::endl;
        }
        void InitServer()
        {
              // 1. 创建socket接口,打开网络文件
            sock_ = socket(AF_INET, SOCK_DGRAM, 0);
            if (sock_ < 0)
            {
                std::cerr << "create socket error: " << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            std::cout << "create socket success: " << sock_ << std::endl; // 3

            // 2. 给服务器指明IP地址(??)和Port
            struct sockaddr_in local; // 这个 local 在哪里定义呢?用户空间的特定函数的栈帧上,不在内核中!
            bzero(&local, sizeof(local));

            local.sin_family = AF_INET; // PF_INET
            local.sin_port = htons(port_);
            // inet_addr: 1,2
            // 1. 字符串风格的IP地址,转换成为4字节int, "1.1.1.1" -> uint32_t -> 能不能强制类型转换呢?不能,这里要转化
            // 2. 需要将主机序列转化成为网络序列
            // 3. 云服务器,或者一款服务器,一般不要指明某一个确定的IP
            local.sin_addr.s_addr = INADDR_ANY; // 让我们的udpserver在启动的时候,bind本主机上的任意IP
            if (bind(sock_, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                std::cerr << "bind socket error: " << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            std::cout << "bind socket success: " << sock_ << std::endl; // 3
        }
        void start()
        {
            char buffer[1024];
           while (true)
            {
                // 收
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer); // 这里一定要写清楚,未来你传入的缓冲区大小
                int n = recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                if (n > 0)
                    buffer[n] = '\0';
                else continue;

                std::string clientip = inet_ntoa(peer.sin_addr);
                uint16_t clientport = ntohs(peer.sin_port);
                std::cout << clientip<< "-"<<clientport<<"#"<<"收到信息# " << buffer << std::endl;
                //发
                sendto(sock_, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, sizeof(peer));
            }
        }

        ~UdpServer()
        {

        }

    private:
        int sock_;
        uint16_t port_;
        // std::string ip_; 
    };
} // end NS_SERVER


在这里插入图片描述

在这里插入图片描述

开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

在上面的的这些接口中可以发现,这里面有一个结构体指针sockaddr* ,那么这个sockaddr结构体到底是什么呢?

sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同

在这里插入图片描述
这些结构体中 struct sockadd_in 表示基于网络 进行网络通信的套接字;
struct sockaddr_un叫做域间套接字,用来两个进程之间进行本地通信的这个是取代system V本地之间进行通信的解决方案

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16
    位端口号和32位IP地址.
  • IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
  • *socket API可以都用struct sockaddr 类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为
    参数

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

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

相关文章

js【详解】DOM

文档对象模型&#xff08;Document Object Model&#xff0c;简称DOM&#xff09; DOM 是哪种数据结构 &#xff1f; DOM 的本质是浏览器通过HTML代码解析出来的一棵 树。 操作 DOM 常用的 API 有哪些 &#xff1f; 获取 DOM 节点 //方式 1&#xff1a;通过【id】获取&#xf…

每日学习笔记:C++ STL 的队列Deque

定义 内存模型 Deque与Vector比较 操作函数 运用实例

每日OJ题_牛客HJ73 计算日期到天数转换(IO型OJ)

目录 牛客HJ73 计算日期到天数转换 解析代码 牛客HJ73 计算日期到天数转换 计算日期到天数转换_牛客题霸_牛客网 解析代码 #include <iostream> using namespace std; int main() {int year 0, month 0, day 0, sum 0;cin >> year >> month >>…

文本向量评测MTEB和C-MTEB

文章目录 简介MTEBC-MTEB参考资料 简介 MTEB(Massive Text Embedding Benchmark)是目前评测文本向量很重要的一个参考&#xff0c;其榜单也是各大文本向量模型用来展示与其他向量模型强弱的一个竞技台。 C-MTEB则是专门针对中文文本向量的评测基准。 MTEB MTEB的目的是为了…

esp32 GDEH0154D67屏幕调试

官方资料 说明手册&#xff1a;GDEH0154D67specf3d5.pdf (e-paper-display.cn) 官网&#xff1a;1.54寸黑白单色电子纸显示屏 200x200分辨率电子墨水屏 GDEH0154D67,黑白电子纸屏,电子墨水屏-大连佳显 (e-paper-display.cn) 驱动代码来源&#xff1a;桌面小屏幕实战课程资料…

面向对象的编程语言是什么意思?——跟老吕学Python编程

面向对象的编程语言是什么意思&#xff1f;——跟老吕学Python编程 面向对象是什么意思&#xff1f;面向对象的定义面向对象的早期发展面向对象的背景1.审视问题域的视角2.抽象级别3.封装体4.可重用性 面向对象的特征面向对象的开发方法面向对象程序设计基本思想实现 面向对象的…

机器学习周报第32周

目录 摘要Abstract一、文献阅读1.论文标题2.论文摘要3.论文背景4.论文方案4.1 多视角自注意力网络4.2 距离感知4.3 方向信息4.4 短语模式 二、self-attention 摘要 本周学习了多视角自注意力网络&#xff0c;在统一的框架下联合学习输入句子的不同语言学方面。具体来说&#x…

Node 旧淘宝源 HTTPS 过期处理

今天拉取老项目更新依赖&#xff0c;出现 urlshttps%3A%2F%2Fregistry.npm.taobao.org%2Fegg-logger%2Fdownload%2Fegg-logger-2.6.1.tgz: certificate has expired 类似报错。即使删除 node_modules 重新安装&#xff0c;问题依然无法解决。 一、问题演示 二、原因分析 1、淘…

子类和父类在同一包中的继承性

同一包中的继承性 继承非private的成员变量&#xff1b; 继承private的方法&#xff1b; 继承的成员变量或方法的访问权限保持不变。

elasticsearch 深度分页查询 Search_after(图文教程)

Search_after使用 一. 简介二. 不带PIT的search_after查询2.1 构造数据2.2 search_after分页查询2.2 问题 三. 带PIT的search_after查询3.1 构建第一次查询条件3.2 进行下一页查询3.3 删除PIT 四.参考文章 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注…

【JS】APIs:事件流、事件委托、其他事件、页面尺寸、日期对象与节点操作

1 事件流 捕获阶段&#xff1a;从父到子 冒泡阶段&#xff1a;从子到父 1.1 事件捕获 <body> <div class"fa"><div class"son"></div> </div> <script>const fadocument.querySelector(.fa);const sondocument.qu…

指针----三

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1.冒泡排序2、二级指针3.指针数组4.指针数组模拟二级指针5.字符指针变量6.数组指针变量6.1 数组指针变量是什么&#xff1f;2.2 数组指针变量的初始化 前言 下…

bug总结(1)--变量取错

a c t i v i t y [ ′ t a g n a m e ′ ] 应为 activity[tag_name]应为 activity[′tagn​ame′]应为couponActivitList[0][‘name’] .隐藏的bug&#xff0c;在测试中竟然测不出来&#xff0c;而且上线了好久。为啥会出现这种低级错误呢&#xff1f;第一是写的时候不够仔细认…

【Python数据结构与判断2/7】数据和判断小结

目录 序言 print() 变量 赋值 四种数据类型 字符串 格式化输出 四则运算 取整与取模 比较运算 逻辑运算 判断 if语句 if-else语句 if-elif-else语句 Tips 空值、0、非0非空值 实战案例 输入密码 短信模板 总结 序言 今天将对前面学过的内容进行一个复习小结…

如何从碎屏的华为手机恢复数据?6 种热门方法

“只是想知道是否可以从屏幕损坏的华为恢复数据&#xff1f;我尝试将其插入我的笔记本电脑&#xff0c;但手机不允许我进入&#xff0c;因为它要求我更改手机中的设置等.我最好的选择是什么&#xff1f; 当发生事故&#xff0c;我们的华为手机屏幕损坏时&#xff0c;访问这些关…

【数据分享】2000-2022年全国1km分辨率的逐月PM10栅格数据(免费获取)

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2000-2022年全国范围逐月的PM2.5栅格数据、2013-2022年全国范围逐月SO2栅格数据和2013-2022年全国范围逐月CO栅格数据&#xff08;可以查看之前的文章获悉详情&#xff09;&#xff01; 本次我们…

云原生(一)、linux快速上手

Linux是一种开源的Unix-like操作系统内核。它是由Linus Torvalds于1991年首次发布&#xff0c;其后经过全球的自由软件社区的持续开发和改进。Linux内核是操作系统的核心部分&#xff0c;但通常与GNU项目合作&#xff0c;以形成完整的操作系统&#xff0c;被称为Linux发行版&am…

力扣hot100:152.乘积最大子数组(动态规划)

一个子数组问题&#xff0c;我们要使用线性dp&#xff0c;最好先考虑以i结尾&#xff0c;如果定义dp[i]为前i个数最大子数组乘积值 那么dp[i-1]就无法转移到dp[i]。因此我们先考虑dp[i]定义为以第i个数结尾的最大子数组乘积值。 53. 最大子数组和 最大子数组和是一个动态规划问…

b树(一篇文章带你 理解 )

目录 一、引言 二、B树的基本定义 三、B树的性质与操作 1 查找操作 2 插入操作 3 删除操作 四、B树的应用场景 1 数据库索引 2 文件系统 3 网络路由表 五、哪些数据库系统不使用B树进行索引 1 列式数据库 2 图形数据库 3 内存数据库 4 NoSQL数据库 5 分布式数据…

小巧设备,大能量:探索口袋中的远程控制神器

在这个科技日新月异的时代&#xff0c;我们的生活被各种手机软件所包围。几乎每个人都有一个甚至多个手机&#xff0c;你是否也有遇到过需要远程操作自己某一台手机的场景呢&#xff1f;今天&#xff0c;我要向大家推荐一款神奇的手机远程操作神器&#xff0c;让你可以随时随地…