建设一个旅游网站必备的,国外wordpress主机,做网站选择什么相机,软件开发公司哪家好文章目录 1. 前导知识1.1 源MAC地址和目的MAC地址1.2 源IP地址和目的IP地址1.3 MAC地址和IP地址的配合1.4 源端口号和目的端口号1.5 Socket1.6 UCP协议和TCP协议1.7 网络字节序高低位高低地址大端和小端网络字节序常用转换函数 2. socket 网络编程2.1 socket 常见接口创建套接字… 文章目录 1. 前导知识1.1 源MAC地址和目的MAC地址1.2 源IP地址和目的IP地址1.3 MAC地址和IP地址的配合1.4 源端口号和目的端口号1.5 Socket1.6 UCP协议和TCP协议1.7 网络字节序高低位高低地址大端和小端网络字节序常用转换函数 2. socket 网络编程2.1 socket 常见接口创建套接字绑定监听套接字接收请求建立连接 2.2 常见套接字域间套接字原始套接字网络套接字 2.3 sockaddr 结构体 3. 实践实现简易UDP网络程序实现简易TCP网络程序 1. 前导知识
友情链接网络基础入门
1.1 源MAC地址和目的MAC地址
MAC地址Media Access Control Address 局域网地址在OSI模型的第二层数据链路层发挥作用标识本地网络上的设备物理地址。
对于处于同一局域网的多台主机它们直接向局域网发送的数据是被所有主机共享的包括发送的主机自己也就相当于广播但是只有特定的主机才会处理它虽然所有主机都收到了信息。这是因为主机发送的数据中包含了指定主机的MAC地址除此之外为了校验数据的完整性还包含了发生数据的主机本身的MAC地址以供主机在发送信息后再接收校验。
其中发送信息的主机的MAC地址叫做源MAC地址接收信息的主机的MAC地址叫做目的MAC地址。
1.2 源IP地址和目的IP地址
IP地址Internet Protocol 互联网协议在OSI模型的第三层网络层发挥作用它是一个逻辑地址用于唯一标识互联网连接设备。
MAC地址标识着设备的全球唯一性但是仅靠MAC地址无法完成不同网络中数据的传输。我们知道数据传输是通过网络协议栈传输的数据自上而下传输时会被每一层协议封装一个报头信息当数据自下而上传输时每一层协议会解封装直到应用层取到数据本身。但是不同的网络可能在某些层的协议有所区别因此报头的封装和解封装的过程就不像局域网那样对称因此需要配合IP地址在不同的网络中跳转。
1.3 MAC地址和IP地址的配合
在不同网络中路由器起着“指路人”的作用实际上数据在传输过程中可能会经过多个不同网络那么报头信息中的两个MAC地址一直在随着路由器路由器也是硬件的变化而变化但是源IP地址和目的IP地址不会改变。这就像唐僧每到一个地方都会说“自东土大唐而来去西天取经”出发点和目的地是不应该被改变的在某些特殊情况源IP可能会被改变但是目的IP绝对不会被改变但是遇到的好心人听到这句话以后都会告诉唐僧下一个地方应该怎么走这就是MAC地址和IP地址在不同网络中配合数据传输的过程。
1.4 源端口号和目的端口号
端口号PORT的主要作用是表示一台计算机中的特定特指网络服务进程所提供的服务它在传输层发挥作用标识主机上进程的唯一性。言外之意是一个端口号只能被一个进程使用而一个进程可以使用多个端口号。
端口号是一个16位的无符号整数范围从0到65535。在Internet上端口号用于识别不同的网络服务。例如Web服务器通常使用端口号80SMTP服务器使用端口号25等 。
结合进程相关知识数据本身是被运行起来的进程处理的因此数据通过网络传输到不同主机中只是一个搬运的过程。因此可以认为数据是在不同主机中的不同进程之间传输也就是网络层面上的进程间通信。端口号的名字很形象现实中的港口port也是类似的。主机中各种不同的进程就好像一个个蓄势待发的货船它们在不同编号的位置等待货物一旦货物就绪一个个进程就会对其处理。
IP地址标识了公网中主机的唯一性端口号标识主机上进程的唯一性那么IP地址端口号就标识了网络上某台主机中的进程的唯一性。和IP地址类似端口号会在传输层被封装进报头信息中。 既然PID和端口号都能表示主机上进程的唯一性为什么不用PID进行网络传输 端口号标识的进程是PID标识的进程的子集它们标识的范围不同。PID就像每个人的身份证虽然它能表示我们在这片土地上的唯一性但是我们很多时候不使用它而是使用范围合适、便于管理的标识例如在教室用座位号、在学校用学号、在高考中用准考证和在银行里用身份证等等。使用PID当然可以但是这样会增加筛选所有进程中的网络服务进程的负担也会增加其他非网络服务进程的安全风险。这也是一种解耦的做法单独用一种标识表示特定种类的元素能省去筛选的成本。
1.5 Socket
Socket套接字是计算机网络中的一个软件结构它用于在计算机网络中的节点之间发送和接收数据。套接字的结构和属性由网络架构的应用程序编程接口API定义。它允许应用程序将I/O插入到网络中并与网络中的其他应用程序进行通信。简单来说Socket是计算机之间进行通信的一种约定或一种方式。 Socket这个词在计算机网络中的翻译为“套接字”原意指的是插座或者插槽。在计算机网络中它被用来描述两个程序之间建立连接的端点。就像电器插头需要插入插座才能通电一样两个程序之间也需要一个“插座”来建立连接。因此这个词被引申为“套接字”。 Socket函数是应用程序与TCP/IP协议族通信的中间软件抽象层它是一组接口。在设计模式中Socket其实就是一个门面模式它把复杂的TCP/IP协议族隐藏在Socket接口后面对用户来说一组简单的接口就是全部。它将底层复杂的协议体系、执行流程进行了封装封装完的结果就是一个SOCKET了也就是说SOCKET是我们调用协议进行通信的操作接口。 Socket起源于Unix而Unix/Linux基本哲学之一就是“一切皆文件”都可以用“打开open – 读写write/read – 关闭close”模式来操作。Socket就是该模式的一个实现socket即是一种特殊的文件一些socket函数就是对其进行的操作读/写IO、打开、关闭。 在实践过程中其实不必要关心它的各种定义可以简单地理解为它就是一个数据包是包含各种通信相关属性的结构体。内置的库中有许多函数它们会在函数内部对这个数据包中的属性处理。值得注意的是socket本质是一个按照某种规则协议构造出来的一个文件只要通信两端都按照约定好的规则使用它其中的数据就能实现通信过程。
友情链接
socket是什么套接字是什么SOCK、SOCKET和TCP_SOCK之间的关系
1.6 UCP协议和TCP协议 下面简单介绍UCP协议和TCP协议。 TCPTransmission Control Protocol传输控制协议提供的是面向连接可靠的字节流服务。即客户和服务器交换数据前必须现在双方之间建立一个TCP连接之后才能传输数据。并且提供超时重发丢弃重复数据检验数据流量控制等功能保证数据能从一端传到另一端。
UDPUser Datagram Protocol用户数据报协议是一个简单的面向数据报的运输层协议。它不提供可靠性只是把应用程序传给IP层的数据报发送出去但是不能保证它们能到达目的地。由于UDP在传输数据报前不用再客户和服务器之间建立一个连接且没有超时重发等机制所以传输速度很快。
简单地说TCP就像打电话首先要通信信道才能进行通信。 为什么UDP不提供可靠性还要使用它 尽管UDP不提供可靠性但它的优点在于传输速度快。由于UDP在传输数据报前不用再客户和服务器之间建立一个连接且没有超时重发等机制所以传输速度很快 。这对于一些对实时性要求较高的应用程序来说非常重要例如在线游戏、实时音视频传输等。在这些情况下使用UDP协议能够提供更快的响应速度。一般情况下为了数据安全都使用TCP在特殊场景下例如直播和视频可能会使用UDP。在优秀的通信算法中常常会同时使用TCP和UDP根据实际情况调度策略。
实际上这里的“可靠”是相对的是中性词。也就是说TCP为了达到“可靠”付出了很多代价例如协议更复杂、维护难度高因此它的传输速度没有UDP快。其“可靠”与否是协议本身的特点。如果它们会说话的话那么UDP可能会对TCP说“何必这么累呢跟我一样直接把数据甩给对面不就好了”
1.7 网络字节序
高低位
对于任意一个十进制的数值它可以用多项式 1 0 n 10^n 10n的和表示例如 123 1 × 1 0 2 2 × 1 0 1 3 × 1 0 0 123 {1×10^2} {2×10^1} {3×10^0} 1231×1022×1013×100字节的高低对应着权值的大小。例如对于整数0x123456780x12是最高位字节它的权值是16的三次方0x78是最低位字节它的权值是16的零次方。
高低地址
内存地址的高低是指内存地址的数值大小。比如0x1000是一个比0x0100更高的地址。
简单地说就是左边低右边高。
大端和小端
小端数据的高权值位对应高地址处。大端反之。
假设我们有一个16位的整数0x1234它占用两个字节。在大端字节序的计算机中这个整数将按照0x12 0x34的顺序存储在内存中。也就是说最高位字节0x12存储在内存的低地址处最低位字节0x34存储在内存的高地址处。
而在小端字节序的计算机中这个整数将按照0x34 0x12的顺序存储在内存中。也就是说最低位字节0x34存储在内存的低地址处最高位字节0x12存储在内存的高地址处。
只要记住大端更符合我们现代人从左到右的读写习惯即可。
网络字节序 接收数据的主机知道对方主机是大端还是小端吗 不知道。因为主机的大小端是不确定的因此如果接收数据的主机必须要知道对方主机是大端还是小端。否则就会出现数据读取错误。 发送数据的主机将它的大小端属性特征字段放进报头信息中不就好了 找到属性字段的前提是接收数据的主机已经知道了发送数据的主机是大端还是小端这样就矛盾了。
所以网络字节序直接规定了使用大端。因此主机在发送数据和接收数据时都要对数据进行字节序转换。 转换什么 数据在发送前需要从主机字节序转换为网络字节序数据在接收后需要从网络字节序转换为主机字节序。
常用转换函数
这个转换的工作已经由C标准库完成实际上Windows也使用的是相同的一套函数。
#include arpa/inet.huint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);命名解读
hhost表示主机字节序nnet表示网络字节序llong表示32位长整数sshort表示16位短整数。
通常情况下不论测试机是大端还是小端为了可移植性都要调用这些函数进行转换如果机器本身是大端那么这些函数将直接返回。 编码习惯虽然有时候某些步骤在理论上是不必要的但实际应用中可能会出现各种各样的问题所以为了保险起见都会多执行一步。 2. socket 网络编程
2.1 socket 常见接口
TCP是面向连接的通过socket实现通信的步骤是
创建套接字服务端和客户端绑定端口号服务端监听套接字服务端建立连接客户端
UDP是面向字节流的它的步骤比较简单
创建套接字服务端和客户端绑定端口号服务端
其中TCP和UDP的服务端都要创建套接字并绑定端口号这些步骤将在实践中介绍仅通过接口的数量就能看到TCP比UDP多做了不少工作。
在此由于知识的局限某些参数无法作详细的解释将在TCP/UDP专题中介绍。
通过man [函数名]能很方便地查询函数相关信息。
它们的头文件都是:
#include sys/types.h
#include sys/socket.h创建套接字
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传输协议。
绑定
bind()函数用于将套接字与指定的IP地址和端口号绑定。通常在TCP协议或UDP协议的服务端设置。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);参数
sockfd要绑定的套接字文件描述符它的本质是一个数组下标。addr是一个指向struct sockaddr类型结构体的指针该结构体中包含了要绑定的IP地址和端口号。addrlen 是addr所指向的地址结构体的大小。
监听套接字
listen() 函数用于将套接字转换为被动监听状态。通常在TCP协议的服务端设置。
int listen(int sockfd, int backlog);参数
sockfd要监听的套接字文件描述符。backlog未完成连接队列的最大长度即允许等待连接的客户端数量 。
接收请求
accept() 函数用于从监听套接字的未完成连接队列中提取第一个连接请求创建一个新的已连接套接字并返回一个指向该套接字的文件描述符。通常在TCP协议的服务端设置。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);参数
sockfd监听套接字的文件描述符。addr是一个指向 struct sockaddr 类型结构体的指针用于存储客户端的地址信息。addrlen是一个指向 socklen_t 类型变量的指针用于存储客户端地址结构体的大小。
建立连接
connect() 函数用于建立与指定套接字的连接。通常在TCP协议的服务端设置。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数
sockfd 是要连接的套接字文件描述符。addr 是一个指向 struct sockaddr 类型结构体的指针该结构体中包含了要连接的服务器的地址信息。addrlen 是 addr 所指向的地址结构体的大小。
2.2 常见套接字
套接字是一种通信机制用于在不同主机或同一主机上的进程间通信。套接字有多种类型包括流式套接字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协议进行数据传输提供无连接的、不可靠的通信服务。
2.3 sockaddr 结构体
在介绍socket网络套接字的接口时曾多次提到sockaddr结构体它是一个通用的套接字地址结构用于在套接字编程中传递不同协议族的地址信息。它的定义如下
struct sockaddr {
sa_family_t sa_family; /* 地址族 */
char sa_data[14]; /* 地址数据 */
};sa_family字段表示地址族address family用于指定地址的类型。常见的地址族有AF_INETIPv4地址、AF_INET6IPv6地址和AF_UNIXUnix域地址等。 sa_data字段表示协议地址其长度和内容取决于地址族。例如对于IPv4地址它包含了IP地址和端口号对于Unix域地址它包含了文件系统中的路径名。
由于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地址 */
};这个结构体比sockaddr结构体更具体它包含了IPv4协议族所需的地址信息。当我们调用套接字函数时例如bind(2)我们需要将sockaddr_in结构体指针强制转换为sockaddr结构体指针如下所示
struct sockaddr_in addr;
/* 初始化addr */
bind(sockfd, (struct sockaddr *)addr, sizeof(addr));这样做是为了让套接字函数能够根据sa_family字段来判断实际的地址类型并进行相应的处理。同样的道理对于其他协议族例如IPv6或UNIX域套接字也有各自的地址结构体例如sockaddr_in6和sockaddr_un它们都可以转换为sockaddr结构体指针。
因此我们可以认为sockaddr结构体是一个抽象的接口它隐藏了不同协议族地址结构体之间的差异让我们可以使用统一的方式来操作套接字。 为了统一使用接口Linux内核用结构体的前2个字节标定套接字的类型。即即套接字的类型。sa_family字段是一个sa_family_t类型无符号整型的变量通常占用两个字节。
地址族用于指定地址的类型它决定了套接字如何解释地址信息。常见的地址族有AF_INETIPv4地址、AF_INET6IPv6地址和AF_UNIXUnix域地址等。不同类型的套接字使用不同的协议来传输数据因此需要使用不同的地址结构来表示它们的地址信息。
通过在sockaddr结构体中使用一个通用的字段来表示地址族Linux内核可以统一处理不同类型的套接字地址简化了套接字API的使用。在使用上的体现就是不管是何种通信方式网络还是本地通信虽然在初始化套接字中的属性时使用的是struct sockaddr_in或struct sockaddr_un但是传参都统一类型转换为sockaddr*。这样就不用单独为不同的通信方式实现不同的接口了从而减少了使用成本。 在多线程编程中我们经常利用void*它可以传递任意类型的数据来给线程函数传递信息为什么socket不使用void*来保存通信相关属性呢 套接字API的设计可以追溯到20世纪70年代末当时由贝尔实验室的研究人员开发了BSD Unix操作系统。在当时C语言和Unix操作系统都处于起步阶段许多现代编程语言和操作系统的特性还没有出现。
在设计套接字API时研究人员希望能够提供一种通用的接口用于支持不同类型的网络协议。为了实现这一目标他们定义了一组通用的套接字地址结构用于表示不同类型的网络地址。这些结构体包含了特定的字段用于存储地址族、协议地址等信息。
虽然使用void*指针也可以实现类似的功能但是这样做会使得代码变得更加复杂和难以维护。程序员需要手动管理内存并且需要使用类型转换来访问指针指向的数据。相比之下使用特定的结构体类型来表示套接字地址更加简单、直观和安全。
因此套接字API最终采用了特定的结构体类型来表示套接字地址而不是使用void*指针。这一设计决策为套接字API提供了清晰、简洁和易用的接口并且在后来被广泛采纳。
3. 实践
实际上有了这些接口我们便能按照“套路”实现网络程序到目前为止这是我觉得除了进程间通信之外最有趣的实验。 由于文章还没写完所以给出两个权威的规范样例。 实现简易UDP网络程序 C socket UDP client C socket UDP server
实现简易TCP网络程序
C socket TCP clientC socket TCP server