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 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> c++ webserver/第四章 通信编程(下) -> 正文阅读

[系统运维]c++ webserver/第四章 通信编程(下)

1. socket

一系列相关API, 客户只需要提供(IP,端口号)即可进行通信

定义

对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。提供应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。

socket 是由 IP 地址和端口结合的,提供向应用层进程传送数据包的机制。

在 Linux 环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux 系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

每个套接字文件都有:读/写缓存.

客户端:主动向服务器发起连接,

服务端;被动接受连接,一般不主动发起.

2.字节序

1.定义

  1. 字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。
  2. 字节序分为大端字节序(Big-Endian) 和小端字节序(Little-Endian)。大端字节序是指一个整数的最高位字节(23 ~ 31 bit)存储在内存的低地址处,低位字节(0 ~ 7 bit)存储在内存的高地址处;小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处
  3. 字节 ----> 高地址–低地址
  4. 内存 ----> 低地址–高地址

所以大端模式是从第一位开始存,小端模式是从最后一位开始存,一般计算机采用小段字节序

2.存储和检测

//例子
0x0102030405060708;
//小段模式存储
0x08 | 0x07 |.....|0x01
//大端模式
0x01 | 0x02 |.....|0x08
    
//检测
#include<stdio.h>  
    
//union共享内存,任何数据都是从第一个地址开始.
union var{  
        //c[4]和i和l指向同一块内存地址
        char c[4]; 
        int i;
    	int l;
};  
  
int main(){  
	union var data;
	data.c[0] = 0x04;//因为是char类型,数字不要太大,算算ascii的范围~  
	data.c[1] = 0x03;//写成16进制为了方便直接打印内存中的值对比  
	data.c[2] = 0x02;
	data.c[3] = 0x11;

	data.c[4] = 0x11;//因为是char类型,数字不要太大,算算ascii的范围~  
	data.c[5] = 0x02;//写成16进制为了方便直接打印内存中的值对比  
	data.c[6] = 0x03;
	data.c[7] = 0x04;

	printf("%x\n", data.i);		//11020304
	printf("%x\n", data.l);		//11020304

	data.i = 5;
	printf("%x\n", data.l);		//5 
}  
//出现11020304为小端模式

3.字节序转换函数

  1. 发送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)
  2. 网络字节序固定:大端模式
//进行网络通信时,需要将主机字节序转换为网络字节序(小端化大端,大端不变!)
h  		-host 主机,主机字节序;
to  	-转换目标;
n		-network 网络,网络字节序;
s		-short(unsigned short)	 	指明类型,2个字节(端口);unsigned short int
l		-long(unsigned int)	 		指明类型,4个字节(IP);unsigned int

#include<arpa/inet.h>
//转换端口
uint16_t htons(unit16_t hostshort);		//主机-网络
uint16_t ntohs(unit16_t netshort);		//网络-主机
//转换IP
uint32_t htonl(unit32_t hostlong);		//主机-网络
uint32_t ntohl(unit32_t netlong);		//网络-主机
4.测试
//例子
#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    // htons 转换端口
    unsigned short port = 0x8081;

    unsigned short netport = htons(port);

    printf("%x\n", netport); // 8180

    // htonl 转换IP
    unsigned char ip[4] = {196, 168, 1, 1};
    unsigned int ipInt = *(int *)ip; //转化为int型
    unsigned int net = htonl(ipInt); //转化为网络字节序

    unsigned char *netip = (char *)&net; //输出
    printf("%d %d %d %d\n", *netip, *(netip + 1), *(netip + 2), *(netip + 3));

    return 0;
}

//补充
char c[6] = {1,1,1,1,1,1};
//char数组转化为int
int c = *(int *)a;
printf("%d\n",c);
//int转为char数组
int d = 10;
char* a = (char *)&d;

3. socket地址

//socket地址就是一个结构体,封装IP和端口号等信息.API的基础
//客户端 -> 服务器(ip,port);

//一下一般用IPV4, IPV6无法使用
typedef unsigned short int sa_family_t;
#include <bits/socket.h> struct sockaddr 
{ 
    //sa_family 成员是地址族类型(sa_family_t)的变量。
    sa_family_t sa_family;
    //地址详细数据,用于存放 socket 地址值。
    char sa_data[14];
};

1.参数详细

  1. 地址族类型通常与协议族类型对应。常见的协议族(protocol family,也称 domain PF)和对应的地址族入 下所示:

PF_*AF_* 都定义在 bits/socket.h 头文件中,且后者与前者有完全相同的值,所以二者通常混用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CfJhDntd-1646227978408)(第四章 通信编程.assets/image-20220302131652721.png)]

  1. 不同的协议族的地址值具有不同的含义和长度,如下所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gofXRjmd-1646227978409)(第四章 通信编程.assets/image-20220302132411361.png)]

2.优化,可以存放IPV6

Linux 定义了下面这个新的通用的 socket 地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的。

#include <bits/socket.h> 

typedef unsigned short int sa_family_t;
struct sockaddr_storage 
{ 
    //地址族类型
    sa_family_t sa_family;
    //内存对齐使用
    unsigned long int __ss_align;
    //存储具体IP数据和端口号等.
    char __ss_padding[ 128 - sizeof(__ss_align) ];
};

3.专用的socket地址(现在使用)

所有专用 socket 地址(以及 sockaddr_storage)类型的变量在实际使用时都需要转化为通用 socket 地址类型 sockaddr**(强制转化即可)**,因为所有 socket 通信编程接口使用的地址参数类型都是 sockaddr。

sockaddr_in : IPV4

sockaddr_un: unix本地

sockaddr_in6: IPV6

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0VP5K7g-1646227978409)(第四章 通信通信编程.assets/image-20220302133429857.png)]

//unix专用
#include <sys/un.h> 
struct sockaddr_un 
{ 
    sa_family_t sin_family;
    char sun_path[108]; 
};


//定义
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
typedef unsigned short uint16_t;
typedef unsigned int uint32_t; 
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;

struct in_addr 
{ in_addr_t s_addr; };

//IPV4
#include <netinet/in.h>
struct sockaddr_in 
{
    sa_family_t sin_family; /* 协议族 */
    in_port_t sin_port; /* 端口. */ 
    struct in_addr sin_addr; /* IP地址 */
    unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; };	//保留段


//IPV6
struct sockaddr_in6
{
    sa_family_t sin6_family; /* 协议族 */
    in_port_t sin6_port; /* 端口. */ 
    uint32_t sin6_flowinfo; /* 对齐 */ 
    struct in6_addr sin6_addr;/* IP地址 */
    uint32_t sin6_scope_id; /* IPv6 scope-id */ 
};

4. IP地址转化

1.旧版

字符串IP - 整数(还需要转化为网络字节序). 主机字节序 -> 网络字节序

//老版,一般不使用.只能用于IPV4
#include <arpa/inet.h>
//传入字符串,生成int型数据(二进制),!转化后的是网络字节序!
in_addr_t inet_addr(const char *cp);

//传入一个字符串,再传入一个 in_addr结构体,就可以
//把点分十进制的IP转化为二进制的网络字节序IP地址.并保存再inp结构体中
//返回值:1表示成功,0表示非法,值errno;
int inet_aton(const char *cp, struct in_addr *inp);

//传入一个 in_addr ,转化为一个点分十进制的字符串;
char *inet_ntoa(struct in_addr in);

2.新版

既可以用IPV4,也可以用IPV6即可

#include <arpa/inet.h> 
// p:点分十进制的IP字符串,n:表示network,网络字节序的整数

//将点分十进制的IP地址字符串,转换成网络字节序的整数
int inet_pton(int af, const char *src, void *dst); 
	af:地址族: AF_INET AF_INET6 
    src:需要转换的点分十进制的IP字符串 
    dst:转换后的结果保存在这个里面
//返回值:1表示成功,0表示非法. -1表示错误,置errno;
        
        
// 将网络字节序的整数,转换成点分十进制的IP地址字符串
// 字符串接受长度 16; char ip[16]; 点分十进制(每位3个) * 4 + 3个. + 结束符 = 16位;
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
	af:地址族:
    AF_INET AF_INET6 
    src:要转换的ip的整数的地址
    dst: 转换成IP地址字符串保存的地方 
    size:第三个参数的大小(数组的大小)s
 返回值:返回转换后的数据的地址(字符串),和 dst 是一样的

3.测试

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    //点分十进制字符串转整数
    const char *str = "192.168.1.1";
    unsigned int num = 0;
    int ret = inet_pton(PF_INET, str, &num);
    if (ret <= 0)
    {
        perror("pton\n");
        exit(0);
    }
    printf("num = %d\n", num);		//16885952

    //整数转点分十进制字符串
    char res[16] = {0};
    const char *rec = inet_ntop(PF_INET, &num, res, sizeof(res));
    printf("ret = %s\n", rec);		//192.168.1.1
    printf("res = %s\n", res);		//192.168.1.1
    printf("str = %s\n", str);		//192.168.1.1
    return 0;
}

5. TCP通信

1. TCP 和 UDP

  1. 均为传输层协议
  2. UDP:用户数据报协议,面向无连接,可以单播,多播,广播,面向数据报,不可靠
    1. 不关心用户是否接受;
    2. 无拥塞控制,所以即使网络差也会以恒定速度发送;
  3. TCP:传输控制协议,面向连接的可靠的基于字节流(一端发送,另一端接受),仅支持单播传输
UDPTCP
是否创建连接无连接面向连接
是否可靠不可靠可靠的
连接的对象个数一对一、一对多、多对一 、多对多一对一
传输的方式面向数据报面向字节流
首部开销8个字节最少20个字节(头部)
适用场景实时应用(视频会议,直播)可靠性高的应用(文件传输)

2.通信流程

1.服务端
  1. ? 创建一个监听的套接字
    • 监听: 监听客户端的连接
    • 套接字:本质是文件描述符
  2. 监听文件描述符绑定本地的 IP 和端口号
    • 客户端连接的时候使用的 IP 的端口号,需要固定
  3. 设置监听,即监听的fd开始工作
  4. 阻塞等待,直到有客户端发起连接,接触阻塞,接受客户端的连接. 连接完成可得到客户端的套接字(fd);
  5. 通信
    • 接收数据
    • 发送数据
  6. 通信结束,断开连接
2.客户端
  1. 创建一个套接字
  2. 连接服务器,指定服务器的IP 和 端口
  3. 连接成功,即可进行通信
    • 接收数据
    • 发送数据
  4. 通信结束,断开连接
3.流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3g29U0c2-1646227978410)(第四章 通信编程.assets/image-20220302170444995.png)]

4.套接字函数
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略


// 功能:创建一个套接字
int socket(int domain, int type, int protocol);
/*
	参数:
	1.	domain:协议族
		AF_INET: IPV4
		AV_INET6: IPV6
		AV_UNIX,AF_LOCAL : 本地套接字
	2.	type:通信过程中使用的协议类型
		SOCK_STREAM  : 流式协议
		SOCK_DGRAM : 报式协议
	3.	protocol: 具体协议,一般写0;
		如果第二个参数写SOCK_STREAM,默认为TCP
		如果第二个参数写SOCK_DGRAM,默认为UDP
返回值:成功返回文件描述符,操作的就是内核缓冲区.失败返回 -1 置errno;
*/


//将文件描述符和 IP 和 端口号进行绑定,
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
参数:
	sockfd : 通过socket函数获得的文件描述符
	addr: 绑定的socket地址,这个地址封装了 IP 和 端口号信息;
	addrlen: 第二个参数结构体占的内存大小
返回值:成功0.失败返回 -1 置errno;
*/


//监听这个socket上的连接
int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn
/*
	sockfd :  通过socket函数获得的文件描述符
	backlog : 未连接和已经接和的最大值(线程最大值?),不需要太大(5).因为已连接后就要执行下recv(),那么就会-1;
返回值:成功0.失败返回 -1 置errno;
*/


//接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*
参数:
	sockfd: 通过socket函数获得的文件描述符;
	addr : 传出参数,记录连接成功后的客户端信息(ip,port)
	addrlen : 指定第二个参数对应的内存大小(是一个指针,需要在定义一个变量,用引用传递).
成功: 返回用于通信的文件描述符; 失败 -1置errno;
*/


//客户端连接服务器
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
参数:
	sockfd : !!!用于通信的文件描述符(自己创建)!!!
	addr :   客户端要连接的服务器地址信息
	addrlen: 第二个参数的内存大小(变量)
返回值:成功0.失败返回 -1 置errno;
*/


ssize_t write(int fd, const void *buf, size_t count); // 写数据
ssize_t read(int fd, void *buf, size_t count); // 读数据
//如果读出来的数据长度为0,那么就断开连接!

课程:https://www.nowcoder.com/study/live/504.如果侵权请及时通知

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 16:53:58  更:2022-03-03 16:54:16 
 
开发: 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/16 3:18:51-

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