> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。> 目标:理解并掌握socket套接字。
> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!
> 专栏选自:网络
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
🌟前言
前面我们已经学习了网络的基础知识,对网络的基本框架已有认识,算是初步认识到网络了,如果上期我们的学习网络是步入基础知识,那么这次学习的板块就是基础知识的实践,我们今天的板块是学习网络重要之一,学习完这个板块对虚幻的网络就不再迷茫,那话不多说进入今天的主题【网络】socket套接字基础知识
⭐主体
学习【网络】网络基础入门咱们按照下面的图解:
🌙 前导知识
💫 IP与端口号
IP:
每台主机都有自己的IP地址,所以当数据从一台主机传输到另一台主机就需要IP地址。报头中就会包含源IP和目的IP。
- 源IP地址:发送数据报那个主机的IP地址。
- 目的IP地址:想发送到的那个主机的IP地址。
我们把数据从一台主机传递到另一台主机不是真正目的,真正通信的不是这两个机器,其实是这两台机器上面的软件,而这个软便网络的应用层中,我们知道应用层不止一个软件,公网IP标识了一台唯一的主机,那么数据就可以由一台主机传递到另一台主机。但是有这么多的软件(进程),怎么保证软件A发送的被软件B接收呢?也就是说用什么来标识主机上客户或者服务进程的唯一性呢?最好的解释就是下面这段内容:
为了更好的表示一台主机上服务进程的唯一性,用端口号port标识服务进程、客户端进程的唯一性。
端口号:
- 端口号是一个2字节16位的整数。
- 端口号用来标识一个进程,告诉操作系统要把数据交给哪一个进程。
- 一个端口号只能被一个进程占用(同一个主机)。
总结:
- IP地址(标识主机全网唯一主机)+ 端口号(标识服务器上唯一的进程)能够标识网络上的某一台主机的某一个进程(全网唯一进程)。
- 网络通信的本质就是进程间通信。而我们之前说过进程间通信的本质是看到同一份资源,现在这个资源就是网络。
- 通信的本质就是IO,因为我们上网的行为就两种情况:1.把数据发送出去 2.接收到数据。
两个问题:
标识一个进程有pid,那么为什么还需要端口号port呢?
- 解耦:首先pid是系统规定的,而port是网络规定的,这样就可以把系统和网络解耦。
- port标识服务器的唯一性不能做任何改变,要让客户端能找到服务器,就像110,120样不能被改变。而每次启动进程pid就会改变。
- 不是所有的进程都需要提供网络服务或请求(不需要port),但每个进程都需要pid。
一个端口号只能被一个进程占用,但是一个进程可以绑定多个端口号?(对)
- 底层OS如何根据port找到指定的进程——uint16(端口号)——task_struct——哈希
- 我们在网络通信的过程中,IP+port标识唯一性,IP有源IP和目的IP,port也有源端口号和目的端口号。所以我们在发送数据的时候也要把自己的IP和端口号发送过去,因为数据还要被发送回来。所以发送数据的时候一定会多出一部分数据(以协议的形式呈现)
💫 源MAC地址和目的MAC地址
概念分析:
MAC地址(Media Access Control Address, 局域网地址)在OSI模型的第二层数据链路层发挥作用,标识本地网络上的设备物理地址。
总结分析:
对于处于同一局域网的多台主机,它们直接向局域网发送的数据是被所有主机共享的(包括发送的主机自己),也就相当于广播,但是只有特定的主机才会处理它(虽然所有主机都收到了信息)。这是因为主机发送的数据中包含了指定主机的MAC地址,除此之外,为了校验数据的完整性,还包含了发生数据的主机本身的MAC地址,以供主机在发送信息后再接收校验。其中,发送信息的主机的MAC地址叫做源MAC地址,接收信息的主机的MAC地址叫做目的MAC地址。
💫 源IP地址和目的IP地址
在IP与端口号中我们初步了解,这里我们再单独拿出来讲解。
概念分析:
IP地址(Internet Protocol, 互联网协议)在OSI模型的第三层网络层发挥作用,它是一个逻辑地址,用于唯一标识互联网连接设备。
总结分析:
MAC地址标识着设备的全球唯一性,但是仅靠MAC地址无法完成不同网络中数据的传输。我们知道,数据传输是通过网络协议栈传输的,数据自上而下传输时会被每一层协议封装一个报头信息,当数据自下而上传输时,每一层协议会解封装,直到应用层取到数据本身。但是不同的网络可能在某些层的协议有所区别,因此报头的封装和解封装的过程就不像局域网那样对称,因此需要配合IP地址在不同的网络中跳转。
💫 MAC地址和IP地址的配合
基础概念:
在不同网络中,路由器起着“指路人”的作用,实际上数据在传输过程中可能会经过多个不同网络,那么报头信息中的两个MAC地址一直在随着路由器(路由器也是硬件)的变化而变化,但是源IP地址和目的IP地址不会改变。这就像唐僧每到一个地方都会说“自东土大唐而来,去西天取经”,出发点和目的地是不应该被改变的(在某些特殊情况源IP可能会被改变,但是目的IP绝对不会被改变),但是遇到的好心人听到这句话以后都会告诉唐僧下一个地方应该怎么走,这就是MAC地址和IP地址在不同网络中配合数据传输的过程。
💫 源端口号和目的端口号
什么是端口号?
- 端口号(PORT)的主要作用是表示一台计算机中的特定(特指网络服务)进程所提供的服务,它在传输层发挥作用,标识主机上进程的唯一性。言外之意是一个端口号只能被一个进程使用,而一个进程可以使用多个端口号。
- 端口号是一个16位的无符号整数,范围从0到65535。在Internet上,端口号用于识别不同的网络服务。例如,Web服务器通常使用端口号80,SMTP服务器使用端口号25等 。
总结分析:
结合进程相关知识,数据本身是被运行起来的进程处理的,因此数据通过网络传输到不同主机中只是一个搬运的过程。因此可以认为数据是在不同主机中的不同进程之间传输,也就是网络层面上的进程间通信。端口号的名字很形象,现实中的港口(port)也是类似的。主机中各种不同的进程就好像一个个蓄势待发的货船,它们在不同编号的位置等待货物,一旦货物就绪,一个个进程就会对其处理。
IP地址标识了公网中主机的唯一性,端口号标识主机上进程的唯一性,那么IP地址+端口号就标识了网络上某台主机中的进程的唯一性。和IP地址类似,端口号会在传输层被封装进报头信息中。
既然PID和端口号都能表示主机上进程的唯一性,为什么不用PID进行网络传输?
端口号标识的进程是PID标识的进程的子集,它们标识的范围不同。PID就像每个人的身份证,虽然它能表示我们在这片土地上的唯一性,但是我们很多时候不使用它,而是使用范围合适、便于管理的标识,例如在教室用座位号、在学校用学号、在高考中用准考证和在银行里用身份证等等。使用PID当然可以,但是这样会增加筛选所有进程中的网络服务进程的负担,也会增加其他非网络服务进程的安全风险。这也是一种解耦的做法,单独用一种标识表示特定种类的元素,能省去筛选的成本。
💫 Soket
概念分析:
Socket(套接字)是计算机网络中的一个软件结构,它用于在计算机网络中的节点之间发送和接收数据。套接字的结构和属性由网络架构的应用程序编程接口(API)定义。它允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。简单来说,Socket是计算机之间进行通信的一种约定或一种方式。
通俗概念:
Socket这个词在计算机网络中的翻译为“套接字”,原意指的是插座或者插槽。在计算机网络中,它被用来描述两个程序之间建立连接的端点。就像电器插头需要插入插座才能通电一样,两个程序之间也需要一个“插座”来建立连接。因此,这个词被引申为“套接字”。
Socket函数:
Socket函数是应用程序与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。它将底层复杂的协议体系、执行流程进行了封装,封装完的结果就是一个SOCKET了,也就是说,SOCKET是我们调用协议进行通信的操作接口。
总结分析:
Socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
在实践过程中,其实不必要关心它的各种定义,可以简单地理解为它就是一个数据包,是包含各种通信相关属性的结构体。内置的库中有许多函数,它们会在函数内部对这个数据包中的属性处理。值得注意的是,socket本质是一个按照某种规则(协议)构造出来的一个文件,只要通信两端都按照约定好的规则使用它其中的数据,就能实现通信过程。
💫 TCP/UDP协议
TCP概念:
TCP(Transmission Control Protocol,传输控制协议)提供的是面向连接,可靠的字节流服务。即客户和服务器交换数据前,必须现在双方之间建立一个TCP连接,之后才能传输数据。并且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
TCP特点:
- 传输层协议
- 有连接(正式通信前要先建立连接)
- 可靠传输(在内部帮我们做可靠传输工作)
- 面向字节流
UDP概念:
UDP(User Datagram Protocol,用户数据报协议)是一个简单的面向数据报的运输层协议。它不提供可靠性,只是把应用程序传给IP层的数据报发送出去,但是不能保证它们能到达目的地。由于UDP在传输数据报前不用再客户和服务器之间建立一个连接,且没有超时重发等机制,所以传输速度很快。
UDP特点:
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
理解不可靠传输:
- 如发送数据时出现了丢包的情况、或者数据被重复传递了(传递了多份)、或者网络出现了问题等等造成的后果就叫做不可靠。所以传输层就是用来解决可靠性的一个协议。
- 可不可靠是一个中性词。可靠是需要成本的,往往在维护和编码上都比较复杂;而不可靠没有成本,使用起来也简单。所以要分场景使用。
为什么UDP不提供可靠性,还要使用它?
尽管UDP不提供可靠性,但它的优点在于传输速度快。由于UDP在传输数据报前不用再客户和服务器之间建立一个连接,且没有超时重发等机制,所以传输速度很快 。这对于一些对实时性要求较高的应用程序来说非常重要,例如在线游戏、实时音视频传输等。在这些情况下,使用UDP协议能够提供更快的响应速度。一般情况下,为了数据安全都使用TCP,在特殊场景下(例如直播和视频)可能会使用UDP。在优秀的通信算法中,常常会同时使用TCP和UDP,根据实际情况调度策略。
💫 网络字节流
高低位:
对于任意一个十进制的数值,它可以用多项式10ⁿ的和表示,例如123 = 1×10² + 2×10¹ + 3×10º字节的高低对应着权值的大小。例如,对于整数0x12345678,0x12是最高位字节,它的权值是16的三次方,0x78是最低位字节,它的权值是16的零次方。
高低地址:
- 内存地址的高低是指内存地址的数值大小。比如,0x1000是一个比0x0100更高的地址。
- 简单地说,就是左边低,右边高。
大端和小端:
- 小端:低权值的数放入低地址。
- 大端:低权值的数放入高地址。
- 网络的数据都是大端
如何定义网络数据流的地址:
- 发送主机把发送缓冲区中的数据按内存地址从低到高的顺序发出。
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,先发出的数据是低地址,后发出的数据是高地址。
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据,如果当前发送主机是小端, 就需要先将数据转成大端, 否则就忽略,直接发送即可。
常用转换函数:
#include <arpa/inet.h>
// 主机序列转网络序列
uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);
// 网络序列转主机序列
uint16_t ntohs(uint16_t netshort);
uint32_t ntohl(uint32_t netlong);
命名解读:
- h:host,表示主机字节序;
- n:net,表示网络字节序;
- l:long,表示32位长整数;
- s:short,表示16位短整数。
通常情况下,不论测试机是大端还是小端,为了可移植性都要调用这些函数进行转换,如果机器本身是大端,那么这些函数将直接返回。
🌙 socket 网络编程
💫 socket 常见接口
TCP是面向连接的,通过socket实现通信的步骤是:
- 创建套接字(服务端和客户端)
- 绑定端口号(服务端)
- 监听套接字(服务端)
- 建立连接(客户端)
UDP是面向字节流的,它的步骤比较简单:
- 创建套接字(服务端和客户端)
- 绑定端口号(服务端)
头文件包含:
#include <sys/types.h>
#include <sys/socket.h>
1.创建套接字
socket()函数用于创建套接字:
int socket(int domain, int type, int protocol);
参数讲解:
domain(域):指定套接字家族,简单地说就是指定通信的方式是本地还是网络:
- AF_UNIX, AF_LOCAL:本地通信。
- AF_INET:网络通信。
- …
- type:指定套接字的类型,即传输方式:
- SOCK_STREAM:面向连接的套接字/流格式套接字。
- SOCK_DGRAM:无连接的套接字/数据报套接字。
- protocol(协议):指定传输协议,默认为
0
,常用的有:
- IPPROTO_TCP:表示TCP传输协议。
- IPPTOTO_UDP:表示UDP传输协议。
2.绑定
bind()函数用于将套接字与指定的IP地址和端口号绑定。通常在TCP协议或UDP协议的服务端设置:
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
参数讲解:
- sockfd:要绑定的套接字文件描述符,它的本质是一个数组下标。
- addr:是一个指向struct sockaddr类型结构体的指针,该结构体中包含了要绑定的IP地址和端口号。
- addrlen 是addr所指向的地址结构体的大小。
3.监听套接字
listen() 函数用于将套接字转换为被动监听状态。通常在TCP协议的服务端设置:
int listen(int sockfd, int backlog);
参数讲解:
- sockfd:要监听的套接字文件描述符。
- backlog:未完成连接队列的最大长度,即允许等待连接的客户端数量 。
4.接收请求
accept() 函数用于从监听套接字的未完成连接队列中提取第一个连接请求,创建一个新的已连接套接字,并返回一个指向该套接字的文件描述符。通常在TCP协议的服务端设置:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数讲解:
- sockfd:监听套接字的文件描述符。
- addr:是一个指向 struct sockaddr 类型结构体的指针,用于存储客户端的地址信息。
- addrlen:是一个指向 socklen_t 类型变量的指针,用于存储客户端地址结构体的大小。
5.建立连接
connect() 函数用于建立与指定套接字的连接。通常在TCP协议的服务端设置:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数讲解:
- sockfd 是要连接的套接字文件描述符。
- addr 是一个指向 struct sockaddr 类型结构体的指针,该结构体中包含了要连接的服务器的地址信息。
- addrlen 是 addr 所指向的地址结构体的大小。
💫 常见套接字
概念:
套接字是一种通信机制,用于在不同主机或同一主机上的进程间通信。套接字有多种类型,包括流式套接字(SOCK_STREAM)、数据报套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)等。在这里,我们讨论的是网络套接
域间套接字:
域间套接字(Domain Socket)是一种特殊类型的套接字(socket)。套接字是一种通信机制,用于在不同主机或同一主机上的进程间通信。套接字有多种类型,包括流式套接字(SOCK_STREAM)、数据报套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)等。域间套接字是其中的一种类型,用于在同一台主机上的进程间通信。
简单来说,域间套接字是套接字的一种类型,它与其他类型的套接字共享相似的API和通信机制,但是它专门用于在同一台主机上的进程间通信。
原始套接字:
- 原始套接字(Raw Socket)是一种特殊类型的套接字,它允许直接发送和接收IP协议数据包,而不需要任何传输层协议格式。这意味着使用原始套接字时,应用程序需要自己处理传输层协议的相关细节。
- 原始套接字通常用于安全相关的应用程序,如nmap,或用于在用户空间实现新的传输层协议。它也常用于网络设备上的路由协议,例如IGMPv4、开放式最短路径优先协议 (OSPF)、互联网控制消息协议 (ICMP)。
网络套接字:
- 网络套接字(Network Socket)是一种用于在不同主机上的进程间通信的套接字。它使用了网络协议栈,如TCP/IP协议栈,来实现跨网络的通信。网络套接字使用IP地址和端口号来标识通信端点。
- 网络套接字有两种类型:流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。流式套接字使用TCP协议进行数据传输,提供可靠的、面向连接的通信服务。数据报套接字使用UDP协议进行数据传输,提供无连接的、不可靠的通信服务
💫 sockaddr的结构体
概念:
sockaddr结构并不能很好地表示各种类型的地址,因此通常会使用特定于地址族的结构来表示套接字地址,例如sockaddr_in(用于IPv4地址)和sockaddr_un(用于Unix域地址)。这些结构与sockaddr结构具有相同的大小和对齐方式,可以相互转换。
这个结构体的唯一目的是为了将不同协议族的地址结构体指针转换为一个“通用”类型,以避免编译器警告。例如,对于IPv4协议族的地址结构体sockaddr_in,它的定义如下:
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 端口号 */
struct in_addr sin_addr; /* IPv4地址 */
};
🌟结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。