计算机网络核心知识归纳

第一章:概论

互联网、以太网、广域网、局域网、万维网

局域网:(Local Area Network,LAN), 局域网是一个局部范围的计算计组,比如家庭网络就是一个小型的局域网,里面包含电脑、手机和平板等,他们共同连接到你家的路由器上。又比如学校的机房就是一个局域网,里面有几百几千台电脑,当机房无法上外网时,但是电脑之间仍可以通信,你们可以通过这个局域网来打CS 、玩红警。理论上,局域网是封闭的,并不可以上外网,可以只有两台电脑,也可以有上万台。

广域网:(WAN,Wide Area Network),广域网的范围就比较大了,可以把你家和别人家、各个省、各个国家连接起来相互通信。广域网和局域网都是从范围的角度来划分的,广域网也可以看成是很多个局域网通过路由器等相互连接起来。

以太网:(Ethernet),以太网可以看成是一种实现局域网通信的技术标准,是目前最广泛的局域网技术。以太网的运行速率有10Mbps,100Mbps,1Gbps,10Gbps的,它的传输介质有的是双绞线,有的是光纤。 简单的说,以太网就是在局域网内,把附近的设备连接起来,可以进行通讯。

互联网:(Internet),互联网可以看成是局域网、广域网等组成的一个最大的网络,它可以把世界上各个地方的网路都连接起来,个人、政府、学校、企业,只要你能想到的,都包含在内。互联网是一种宽泛的概念,是一个极其庞大的网络。

万维网:万维网(英语:World Wide Web)亦作WWW、Web、全球广域网,是一个透过互联网访问的,由许多互相链接的超文本组成的信息系统[1]。英国科学家蒂姆·伯纳斯-李于1989年发明了万维网。1990年他在瑞士CERN的工作期间编写了第一个网页浏览器[2][3]。网页浏览器于1991年1月向其他研究机构发行,并于同年8月向公众开放。

万维网并不等同互联网,万维网只是互联网所能提供的服务其中之一,是靠着互联网运行的一项服务。

VLAN(Virtual Local Area Network)即虚拟局域网,是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。每个VLAN是一个广播域,VLAN内的主机间可以直接通信,而VLAN间则不能直接互通。这样,广播报文就被限制在一个VLAN内。

LAN与VLAN主要区别
创建LAN需要集线器、交换机和路由器等设备;VLAN是使用交换机或网桥创建的。 LAN中只有一个广播域,因此每个数据包都会广播到除发送设备之外的每个连接;VLAN可以在单个介质中实现多个广播域并且可以将数据包发送到所需的LAN网段。

WiFi和WLAN:
Wi-Fi,又称“无线网络”,是Wi-Fi联盟的商标,一个基于IEEE 802.11标准的无线局域网技术。“Wi-Fi”常被写成“WiFi”或“Wifi”,但是这些写法并没有被Wi-Fi联盟认可。
WLAN 无线局域网

总结

  1. LAN, WAN, WLAN是概念 Ethernet, WWW, WiFi是它们的具体IEEE技术标准
  2. Internet是一切能想到的网络的集合,是一个宽泛的概念

概念

计算机网络体系结构中的专用术语

实体:任何可发送或接收信息的硬件或软件进程
对等实体:收发双方相同层次中的实体
协议:控制两个对等实体进行逻辑通信的规则的集合

协议的三要素
语法:定义所交换信息的格式
语义:定义收发双方所要完成的操作
同步:定义收发双方的时序关系

服务:在协议的控制下, 两个对等实体间的逻辑通信使得本层能够向上一层提供服务。要实现本层协议, 还需要使用下面一层所提供的服务。

协议是“水平的" , 服务是"垂直的" 。

实体看得见相邻下层所提供的服务,但井不知道实现该服务的具体协议。也就是说,下面的协议对上面的实体是透明的。

服务访间点:在同一系统中相邻两层的实体交换信息的逻辑接口, 用千区分不同的服务类型。

服务原语:上层使用下层所提供的服务必须通过与下层交换一些命令, 这些命令称为服务原语。

补充
协议数据单元PDU:对等层次之间传送的数据包称为该层的协议数据单元。

服务数据单元SDU:同一系统内, 层与层之间交换的数据包称为服务数据单元。

多个SDU可以合成为一个PDU; 一个SDU也可划分为几个PDU。

分组/包、数据报、帧、报文、数据单元

1.报文(message)
我们将位于应用层的信息分组称为报文。报文是网络中交换与传输的数据单元,也是网络传输的单元。报文包含了将要发送的完整的数据信息,其长短不需一致。报文在传输过程中会不断地封装成分组、包、帧来传输,封装的方式就是添加一些控制信息组成的首部,那些就是报文头。

2.报文段(segment)

通常是指起始点和目的地都是传输层的信息单元。

3.分组/包(packet)
网络专有名词。大多数计算机网络都不能连续地传送任意长的数据,所以实际上网络系统把数据分割成小块,然后逐块地发送,这种小块就称作分组(packet)。也有些书籍把分组定义为网络层的协议数据单元。
这个术语通常用来一般性地表示任何类型的报文。

在信息技术中,分组是目前分组交换网络中传输的格式化数据块,是基本的信息传输单位。分组与数据报意义近似,但有微小的区别。分组是一个泛指词,而数据报往往用于不可靠服务场合。

4.数据报(datagram)
面向无连接的数据传输,其工作过程类似于报文交换。采用数据报方式传输时,被传输的分组称为数据报。 通常是指起始点和目的地都使用无连接网络服务的网络层的信息单元。(指IP数据报)

5.帧(frame)
帧是数据链路层的传输单元。 它将上层传入的数据添加一个头部和尾部,组成了帧。它的起始点和目的点都是数据链路层。

6.数据单元(data unit)

指许多信息单元。常用的数据单元有服务数据单元(SDU)、协议数据单元(PDU)。

SDU是在同一机器上的两层之间传送信息。PDU是发送机器上每层的信息发送到接收机器上的相应层(同等层间交流用的)。

举例:
应用层——消息

传输层——报文段(segment)/数据报(datagram) (注:TCP叫TCP报文段,UDP叫UDP数据报,也有人叫UDP段)

网络层——分组、数据包(packet)

链路层——帧(frame)

物理层——P-PDU(bit)

mac地址和ip地址辨析

mac地址是唯一的吗?

Each MAC address is unique to the network card installed on a device, but the number of device-identifying bits is limited, which means manufacturers do reuse them. Each manufacturer has about 1.68 million available addresses, so when it burns a device with a MAC address ending in FF-FF-FF, it starts again at 00-00-00.

IP地址和Mac地址产生的目的是方便别人找到自己

Mac地址有点像身份证号码,而IP地址就像门牌号码。在茫茫大海中仅凭一个身份证号码找到一颗别样的沙粒很难,但如果先找到具体的沙滩,沙滩划分很小的网格片区,就很容易通过身份证找到这颗别样的沙粒了。

网络分层

[{"src":"xap:resources/052312145cc6cdaeeed1dcd9281ad5765f8f9556e57cd2c305a1ba28c93f34c1.png","width":445,"height":177}]image

应用层协议 :

  • HTTP协议(超文本传输协议,网页浏览常用的协议)
  • DHCP协议(动态主机配置)
  • DNS系统原理(域名系统)
  • FTP协议(文件传输协议)
  • Telnet协议(远程登陆协议)
  • 电子邮件协议等(SMTP、POP3、IMAP)

传输层协议 :

  • TCP协议
  1. 报文段结构
  2. 可靠数据传输
  3. 流量控制
  4. 拥塞控制
  • UDP协议
  1. 报文段结构
  2. RDT(可靠数据传输协议)

网络层协议 :

  • IP协议(TCP/IP 协议的基础,分为 IPv4 和 IPv6)
  • ARP协议(地址解析协议,用于解析 IP 地址和 MAC地址之间的映射)
  • ICMP协议(控制报文协议,用于发送控制消息)
  • NAT协议(网络地址转换协议)
  • RIP协议、OSPF协议、BGP协议(路由选择协议)

网络接口层 :

  • 差错检测技术多路访问协议(信道复用技术)
  • CSMA/CD协议
  • MAC协议以太网技术

TCP/IP协议栈

TCP/IP 协议栈

  • 地址解析协议ARP (已知IP地址, 找出其相应的MAC地址)
  • 路由信息协议RIP ( 一种小型的内部路由协议)
  • 动态主机配置协议DHCP (用于自动配置IP地址)

DHCP(DynamicHost ConfigurationProtocol),动态主机配置协议,是一个应用层协议,具体可以参考《TCP/IP四层模型》文章。 当我们将客户主机ip地址设置为动态获取方式时,DHCP服务器就会根据DHCP协议给客户端分配IP,使得客户机能够利用这个IP上网。用来给自己分配一个IP地址的

应用层协议

DHCP

DNS

定义

网域名称(英语:Domain Name,简称:Domain),简称域名、网域,是由一串用点分隔的字符组成的互联网上某一台电脑或电脑组的名称,用于在数据传输时标识电脑的电子方位。域名可以说是一个IP地址的代称,目的是为了便于记忆后者。 例如,wikipedia.org是一个域名。人们可以直接访问wikipedia.org来代替IP地址,然后域名系统(DNS)就会将它转化成便于机器识别的IP地址。这样,人们只需要记忆wikipedia.org这一串带有特殊含义的字符,而不需要记忆没有含义的数字。

DNS占用53号端口,同时使用TCP和UDP协议。

那么DNS在什么情况下使用这两种协议?

DNS在区域传输的时候使用TCP协议,其他时候使用UDP协议(比如进行域名解析时)。

DNS uses TCP for Zone transfer and UDP for name
解释:

  1. 区域传输数据量很大(要把整个服务器的信息更新过去),因此需要可靠传输和长报文的TCP(UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。)
  2. 其他时候用UDP可以保证快速响应(无复杂的三握手四挥手过程),减轻服务器的负载
    DNS的规范规定了2种类型的DNS服务器,一个叫主DNS服务器,一个叫辅助DNS服务器。
    在一个区中主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息,而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时,它需要与主DNS服务器通信,并加载数据信息,这就叫做区域传输(zone transfer)。

DNS主从复制,就是将主DNS服务器的解析库复制传送至从DNS服务器,进而从服务器就可以进行正向、反向解析了。

从服务器向主服务器查询更新数据,保证数据一致性,此为区域传送

也可以说,DNS区域传送,就是DNS主从复制的实现方法,DNS主从复制是DNS区域传送的表现形式。

总结

大多数情况下,DNS解析请求和响应都很小,使用UDP协议更加高效,虽然没有TCP可靠,但是速度快,消耗的系统资源更少,非常适合少量数据包的传输。

一些DNS事务,比如区域传输或其他附加查询,可能会产生大于512字节的数据包,因此使用TCP更加可靠,使用TCP会减少丢包和重新发包的情况,因此更加可靠与高效。

DNS的递归和迭代查询方式

image
image
递归:客户端只发一次请求,要求对方给出最终结果。

迭代:客户端发出一次请求,对方如果没有授权回答,它就会返回一个能解答这个查询的其它名称服务器列表,

客户端会再向返回的列表中发出请求,直到找到最终负责所查域名的名称服务器,从它得到最终结果。

授权回答:向dns服务器查询一个域名,刚好这个域名是本服务器负责,返回的结果就是授权回答。

从递归和迭代查询可以看出:

客户端-本地dns服务端:这部分属于递归查询。(定义)

本地dns服务端---外网:这部分属于迭代查询。

递归查询时,返回的结果只有两种:查询成功或查询失败.

迭代查询,又称作重指引,返回的是最佳的查询点或者主机地址.

HTTP

公钥(Public Key)与私钥(Private Key)是通过加密算法得到的一个密钥对(即一个公钥和一个私钥,也就是非对称加密方式)。 公钥可对会话进行加密、验证数字签名,只有使用对应的私钥才能解密会话数据,从而保证数据传输的安全性。 公钥是密钥对外公开的部分,私钥则是非公开的部分,由用户自行保管。image

https s for secure, using SSL/TLS to encrypt

SSL 和 TLS 的区别?
SSL 和 TLS 没有太大的区别。SSL 指安全套接字协议(Secure Sockets Layer),首次发布与 1996 年。SSL 的首次发布其实已经是他的 3.0 版本,SSL 1.0 从未面世,SSL 2.0 则具有较大的缺陷(DROWN 缺陷——Decrypting RSA with Obsolete and Weakened eNcryption)。很快,在 1999 年,SSL 3.0 进一步升级,新版本被命名为 TLS 1.0。因此,TLS 是基于 SSL 之上的,但由于习惯叫法,通常把 HTTPS 中的核心加密协议混成为 SSL/TLS。# SSL/TLS

这两实际上是一个东西
SSL是洋文“Secure Sockets Layer的缩写,中文叫做安全套接层]。它是在上世纪 90 年代中期,由网景公司设计的。
到了1999年,SSL因为应用广泛,已经成为互联网上的事实标准。IETF 就在那年把 SSL标准化。标准化之后的称改为 TLS(是“Transport Layer Security”的缩写) ,中文叫做 传输层安全协议]。
很多相关的文章都把这两者并列称呼 (SSL/TLS) ,因为这两者可以视作同一个东西的不同阶段

著作权归所有
原文链接:https://javaguide.cn/cs-basics/network/http&https.html

非对称加密原理

非对称加密的公钥和私钥需要采用一种复杂的数学机制生成(密码学认为,为了较高的安全性,尽量不要自己创造加密方案)。公私钥对的生成算法依赖于单向陷门函数。

单向函数:已知单向函数 f,给定任意一个输入 x,易计算输出 y=f(x);而给定一个输出 y,假设存在 f(x)=y,很难根据 f 来计算出 x。

单向陷门函数:一个较弱的单向函数。已知单向陷门函数 f,陷门 h,给定任意一个输入 x,易计算出输出 y=f(x;h);而给定一个输出 y,假设存在 f(x;h)=y,很难根据 f 来计算出 x,但可以根据 f 和 h 来推导出 x。
image

HTTPS采用的是对称加密和非对称加密结合的混合加密方式:

在通信建立前采用非对称加密的方式交换会话秘钥,后续就不再使用非对称加密
在通信过程中全部使用对称加密的会话秘钥,方式加密明文数据。

先高成本用非对称加密商量出一个会话秘钥,非对称加密保证别人很难知道这个会话秘钥是什么,然后用这个绝对安全的会话秘钥去低成本地做对称加密,传输数据。

对称加密使用 SSL/TLS 进行通信的双方需要使用非对称加密方案来通信,但是非对称加密设计了较为复杂的数学算法,在实际通信过程中,计算的代价较高,效率太低,因此,SSL/TLS 实际对消息的加密使用的是对称加密。
image

对称加密:
通信双方共享唯一密钥 k,加解密算法已知,加密方利用密钥 k 加密,解密方利用密钥 k 解密,保密性依赖于密钥 k 的保密性
通信双方只需要一次非对称加密,交换对称加密的密钥,在之后的信息通信中,使用绝对安全的密钥,对信息进行对称加密,即可保证传输消息的保密性

采用混合加密的方式的原因:

对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换。
非对称加密使用两个密钥: 公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。

摘要草法(就是哈希函数)
摘要算法用来实现完整性,能够为数据生成独一无二的指纹,用于校验数据的完整性,解决了篡改的风险.
消息摘要算法也被称为哈希(Hash)算法或散列算法。 任何消息经过散列函数处理后,都会获得唯一的散列值,这一过程称为“消息摘要”,其散列值称为“数字指纹”,其算法自然就是“消息摘要算法”了。 换句话说,如果其数字指纹一致,就说明其消息是一致的。

非对称加密很理想,但是如何保证公钥传输的安全性呢?用CA

因为大家都可以捕获通信包,你怎么知道就是正规的服务器发给你的公钥呢?
一个诱饵公钥AS可以诱导你发出使用AS公钥加密的信息,AS收到以后用AS私钥解密就得到数据了!
image

数字证书 = CA数字签名+服务器公钥

为了公钥传输的信赖性问题,第三方机构应运而生——证书颁发机构(CA,Certificate Authority)。
CA 默认是受信任的第三方。CA 会给各个服务器颁发证书,证书存储在服务器上,并附有 CA 的电子签名(见下节)。当客户端(浏览器)向服务器发送 HTTPS 请求时,一定要先获取目标服务器的证书,并根据证书上的信息,检验证书的合法性。一旦客户端检测到证书非法,就会发生错误。客户端获取了服务器的证书后,由于证书的信任性是由第三方信赖机构认证的,而证书上又包含着服务器的公钥信息,客户端就可以放心的信任证书上的公钥就是目标服务器的公钥。

CA的权威性由电子签名保证

总结

端口号:
HTTP 默认是 80,HTTPS 默认是 443。
URL前缀:
HTTP 的 URL 前缀是 http://,HTTPS 的 URL 前缀是 https://。
安全性和资源消耗:
HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。

http1.1 http1.0 diference

总结

连接方式 : HTTP 1.0 为短连接,HTTP 1.1 支持长连接。

状态响应码 : HTTP/1.1中新加入了大量的状态码,光是错误响应状态码就新增了24种。比如说,100 (Continue)——在请求大资源前的预热请求,206 (Partial Content)——范围请求的标识码,409 (Conflict)——请求与当前资源的规定冲突,410 (Gone)——资源已被永久转移,而且没有任何已知的转发地址。

缓存处理 : 在 HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。

带宽优化及网络连接的使用 :HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。

Host头处理 : HTTP/1.1在请求头中加入了Host字段。
HTTP Server 1.0 :单进程

HTTP Server 2.0 :多进程
对于这个新的socket ,不在主进程里处理,而是新创建子进程来接管。这样主进程就不会阻塞在receive 上,可以继续接收新的连接了。

经常需要创建成百上干的进程来处理,每个进程都要耗费大量的系统资源。很明显,操作系统老大己经不堪重负了。

HTTP Server 3.0 : select 模型
切搜回单进程模型,一个进程最多只能监控1024 个socket

quic层,tcp和ssl结合起来,使用udp

HTTP Server 4.0 : epoll 模型
使用epoll 和select其实类似,,“不同的地万是,我只会告诉你那些可以读/写的socket ,你只需要处理这些准备就绪的socket 就可以了。”1000 多个socket 可能只有那么几十个真正需要处理

运输层协议

TCP

滑动窗口是用来管理接收方的,拥塞窗口是用来管理发送方的
tcp报文段由首部和数据载荷构成

网络编程:TCP客户端

关键步骤
TCP 客户端通信程序需要执行的关键系统调用大致如下:

  • socket ,创建套接字;
  • connect ,操作套接字与服务器建立 TCP 连接,内核协议栈接到调用后便执行三次握手;
  • send ,操作套接字向对端发送数据;
  • recv ,从套接字中取出对端发来的数据;(接收 TCP 数据前需要先准备缓冲区空间,再执行 recv 系统调用)
  • close ,操作套接字关闭连接,内核协议栈将执行四次挥手;
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include "argparse.h"
#define MAX_LINE_LEN 10240

int main(int argc, char *argv[]) {
    // 解析命令行参数(该函数可从Github获取)
    const struct client_cmdline_arguments *arguments = parse_client_arguments(argc, argv);
    if (arguments == NULL) {
        fprintf(stderr, "Failed to parse cmdline arguments\n");
        return -1;
    }

    // socket系统调用,创建套接字
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s == -1) {
        perror("Failed to create socket");
        return -1;
    }

    // 准备sockaddr_in结构体用于保存服务器IP地址和端口
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));

    // 填充地址族和服务器端口
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(arguments->server_port);

    // 填充服务器IP地址
    if (inet_aton(arguments->server_ip, &server_addr.sin_addr) != 1) {
        perror("Bad server address");
        return -1;
    }

    // connect系统调用,建立TCP连接
    if (connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("Failed to connect server");
        return -1;
    }

    // 主循环不断接收用户输入,将数据发到服务器,并等待服务器响应
    for (;;) {
        // 打印输入提醒
        printf("> ");

        // 准备缓冲区并读取用户输入
        char line[MAX_LINE_LEN];
        if (fgets(line, MAX_LINE_LEN, stdin) == NULL) {
            printf("\nbye.\n");
            break;
        }

        if (strlen(line) == 0) {
            continue;
        }

        // send系统调用,将用户输入数据发给服务器
        if (send(s, line, strlen(line), 0) == -1) {
            perror("Failed to send data");
            return -1;
        }

        // recv系统调用,从服务器接收响应数据
        int bytes = recv(s, line, MAX_LINE_LEN, 0);
        if (bytes == -1) {
            perror("Failed to receive data");
            return -1;
        }

        // write系统调用,将服务器响应数据写到标准输出(打印到屏幕上)
        if (write(STDOUT_FILENO, line, bytes) == -1) {
            perror("Failed to write stdout");
            return -1;
        }
    }

    return 0;
}

网络编程:TCP服务端

bind 和 accept 系统调用

编写 TCP 服务器同样离不开套接字——服务器需要先创建一个套接字,并监听服务端口:
那么,如何创建监听套接字s呢?关键步骤如下:

  • 创建一个 TCP 套接字;
  • 分配地址结构体,填充监听地址和端口信息;
  • 执行 bind 系统调用,为套接字绑定监听地址和端口;
  • 执行 listen 系统调用,开始监听客户端发过来的连接请求;

listen到了之后,用accept 系统调用接受一个客户端连接,得到一个连接套接字conn,以后连接由这个新的连接套接字维护,

然后可以使用recv系统调用接受客户端发来的数据

进行逻辑处理以后(这里是小写字母转大写)

再用send系统调用发回客户端

#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>

#include "argparse.h"

#define BUFFER_SIZE 102400

// 将数据转成大写
void uppercase(void *input, int bytes) {
    char *buffer = (char *)input;

    while (bytes > 0) {
        bytes--;
        buffer[bytes] = toupper(buffer[bytes]);
    }
}

// 发送数据
void send_data(int s, void *data, int bytes) {
    // bytes大于0说明数据还没发完,继续循环发送
    while (bytes > 0) {
        // 执行send系统调用进行发送
        int sent = send(s, data, bytes, 0);
        if (sent == -1) { // 检查错误
            // 如果是被信号终端,继续重试
            if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
                continue;
            }

            // 其他错误退出
            perror("Failed to send");
            break;
        }

        // 更新数据指针,跳过已发部分
        data += sent;
        // 更新剩余字节数,减去已发字节数
        bytes -= sent;
    }
}

// 处理客户端连接
void process_connection(int s) {
    // 永久处理循环,不断接收客户端发来的数据并处理
    for (;;) {
        // 分配缓冲区用于接收客户端发来的数据
        char input[BUFFER_SIZE];

        // 通过连接套接字接收客户端发来的数据
        int bytes = recv(s, input, sizeof(input), 0);
        if (bytes == -1) {
            if (errno == EINTR) {
                continue;
            }

            perror("Failed to recv");
            break;
        }

        // 数据长度为0,说明客户端关闭了连接,退出处理循环
        if (bytes == 0) {
            break;
        }

        // 将数据改成大写
        uppercase(input, bytes);

        // 调用send_data想客户端发送数据
        send_data(s, input, bytes);
    }
}

int main(int argc, char *argv[]) {
    // 解析命令行参数
    const struct server_cmdline_arguments *arguments = parse_server_arguments(argc, argv);
    if (arguments == NULL) {
        fprintf(stderr, "Failed to parse cmdline arguments\n");
        return -1;
    }

    // 创建套接字
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s == -1) {
        perror("Failed to create socket");
        return -1;
    }

    // 分配地址结构体,并填充监听地址和端口
    struct sockaddr_in bind_addr;
    bzero(&bind_addr, sizeof(bind_addr));

    bind_addr.sin_family = AF_INET;
    bind_addr.sin_addr.s_addr = INADDR_ANY;
    bind_addr.sin_port = htons(arguments->port);

    // 解析IP地址
    if (arguments->bind_ip != NULL) {
        if (inet_aton(arguments->bind_ip, &bind_addr.sin_addr) == 0) {
            fprintf(stderr, "Invalid IP: %s\n", arguments->bind_ip);
            return -1;
        }
    }

    // 将监听地址和端口与套接字绑定
    if (bind(s, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) == -1) {
        perror("Failed to bind address");
        close(s);
        return -1;
    }

    // 开始监听新连接
    if (listen(s, 100) == -1) {
        perror("Failed to listen");
        close(s);
        return -1;
    }

    printf("listening at port: %s:%d, waiting for connections...\n", arguments->bind_ip, arguments->port);

    // 永久循环(死循环)
    for (;;) {
        // 用于保存对端(客户端)地址和端口的结构体
        struct sockaddr_in peer_addr;
        int addr_len = sizeof(peer_addr);

        // 接受一个客户端连接,得到一个连接套接字
        int conn = accept(s, (struct sockaddr *)&peer_addr, &addr_len);
        if (conn == -1) {
            // EINTR表示被信号打断,忽略
            if (errno == EINTR) {
                continue;
            }

            // 其他错误直接退出
            perror("Failed to accept");
            break;
        }

        printf("\n%s:%d connected\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));

        // 处理新连接,为客户端提供服务
        process_connection(conn);

        // 当客户端退出后,关闭套接字
        close(conn);

        printf("%s:%d disconnected\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
    }

    // 关闭监听套接字
    close(s);

    return 0;
}

UDP

应用层的哪些协议是基于TCP

应用层的哪些协议是基于UDP

Linux网络编程(socket)

socket的翻译问题

为什么翻译成套接字?因为套接管,土木工程和一种连接两个管道的套接水管。

并发请求如何处理

最基础的 TCP的 Socket 编程,它是阻塞 I/O 模型,基本上只能一对一通信,那为了服务更多的客户端,我们需要改进网络 I/O 模型。

比较传统的方式是使用多进程/线程模型,每来一个客户端连接,就分配一个进程/线程,然后后续的读写都在对应的进程/线程,这种方式处理 100 个客户端没问题,但是当客户端增大到 10000 个时,10000 个进程/线程的调度、上下文切换以及它们占用的内存,都会成为瓶颈。(C10K问题)

为了解决上面这个问题,就出现了 I/0 的多路复用,可以只在一个进程里处理多个文件的 /0,Linux 下有三种提供 I/0 多路复用的系统调用API,分别是: select、poll、epoll。

select 和 poll 并没有本质区别,它们内部都是使用线性结构,来存储进程关注的 Socket 集合

在使用的时候,首先需要把关注的 Socket 集合通过 select/poll 系统调用从用户态拷贝到内核态,然后由内核检测事件,当有网络事件产生时,内核需要遍历进程关注 Socket 集合(线性表查找O(n)),找到对应的 Socket,并设置其状态为可读/可写,然后把整个 Socket 集合从内核态拷贝到用户态,用户态还要继续遍历整个 Socket 集合找到可读/可写的 Socket,然后对其处理。

很明显发现,select和 poll 的缺陷在于,当客户端越多,也就是 Socket 集合越大,Socket 集合的遍历和拷贝会带来很大的开销,因此也很难应对 C10K。

epoll 是解决 C10K 问题的利器,通过两个方面解决了 select/poll 的问题

epoll 在内核里使用红黑树,来关注进程所有待检测的 Socket,红黑树是个高效的数据结构,增删查一般时间复杂度是 O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个 Socket 集合,减少了内核和用户空间大量的数据拷贝和内存分配

epoll 使用事件驱动的机制,内核里维护了一个链表,来记录就绪事件,只将有事件发生的 Socke集合传递给应用程序,不需要像 select/poll 那样轮询扫描整个集合 (包含有和无事件的 Socket),大大提高了检测的效率

网络层协议

IP

ARP

ICMP

tarceroute程序的实现

几乎每个操作系统都有一个traceroute命令可以查看本主机到另一个主机到路由跳数,以及经过的路由器的ip地址
用法
traceroute baidu.com
在Windows下,traceroute命令默认使用ICMP协议发送ip报,而在Linux系统中默认使用的是udp协议的ip报。而每个路由器设置的防火墙属性不一样,因此使用不同的协议发送ip报,能顺利查到的路由器会不一样。

在进行网络调试的时候,发现属于同一网段的两台主机,一台是windows虚拟机、一台linux虚拟机,traceroute同一目的IP的时候,windows能够完整打印出整个路由路径,而linux则打印两跳之后就显示星号不可达了。
  由于两台主机属于同一网段,所以中间网络的ACL策略是一样的,不会是策略差异的问题。

原因是:linux虚拟机在traceroute时,默认使用UDP报文,而不是使用ICMP报文;而防火墙为了方便网络调试是放行了ICMP报文,但没有放行UDP报文,这就导致了linux虚拟机的traceroute报文(UDP)被防火墙拦截了,windows虚拟机的traceroute报文(ICMP)正常通行。

解决方法:
使用
来源:https://www.jianshu.com/p/e73a6e26dc05

NAT

内网穿透

内网IP地址:
也就是局域网网络地址,内网的计算机以NAT(网络地址转换)协议,通过一个公共的网关访问Internet。内网的计算机可向Internet上的其他计算机发送连接请求,但Internet上其他的计算机无法向内网的计算机发送连接请求。

内联网(Intranet):
又称企业内部网或内部网,是指采用因特网技术的计算机网络,它以TCP/IP协议作为基础,以Web为核心应用构成统一和便利的信息交换平台,例如文件传输、文件管理、电子邮件、网络管理、广域互连等多种服务。

内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。

UDP 内网穿透的实质是利用路由器上的NAT 系统。NAT 是一种将私有(保留)地址转化为合法IP地址的转换技术,它被广泛应用于各种类型 Internet 接入方式和各种类型的网络中。NAT可以完成重用地址,并且对于内部的网络结构可以实现对外隐蔽。

作者:愤怒青年
链接:https://www.zhihu.com/question/63098230/answer/1989327965

我在局域网中有一台电脑,部署了web服务,现在希望所有人都能访问它。

很显然,这台电脑只有一个局域网ip,没有公网ip。那么同一局域网内的设备可以通过局域网ip找到他。而其他设备则找不到你。最多只能找到你的路由器,却进不了你的局域网。

那么就需要内网穿透了。

情况一:你家的路由器被分配了一个固定的公网ip这种情况非常容易,但是极其少见。只需要在路由器中将路由器的某些端口映射到局域网某电脑的某些端口上就ok了。然后就可以使用公网ip+端口号访问了。

情况二:你家的路由器被分配了一个临时的公网ip这种情况比情况一多见,但依然很少见。操作同上,但是因为公网ip会变,所以客户端每次也要跟着变。如果你有域名的话,可以使用ddns将你的域名解析到改公网ip上,并且在公网ip变化时自动更换解析。

情况三:你家的路由器被分配了一个内网ip这种情况才是最普遍的。此时必须借助一个有公网ip的云服务器。内网机器向云服务器建立一个长连接,然后云服务器就可以主动向内网机器传数据。云服务器将自己某端口的数据转发到内网机器上,然后客户端访问云服务器的那个端口就可以访问内网机器了。

既然我已经有了云服务器,为什么还要多此一举使用内网的电脑?

可能的的数据比较重要,不希望放在云服务器上。可能你的云服务器配置不够,而你的服务需要高cpu高内存高磁盘容量。如果你买了云服务器的话,可以使用frp来映射。没有云服务器的话,网上有免费的frp服务,可以去找一下,但是可能不稳定。

应用:使用Docker部署Ngrok实现内网穿透

posted @   wjybq  阅读(243)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示