目录
[四、网络协议TCP/IP vs OSI](#四、网络协议TCP/IP vs OSI)
[(1)IP 地址的核心定义](#(1)IP 地址的核心定义)
[八、Socket 编程预备](#八、Socket 编程预备)
[(3)socket 编程接口](#(3)socket 编程接口)
[(4)UDP socket](#(4)UDP socket)
一、协议
协议本质:就是约定,没有约定,即便是两台计算机能够相互联通,对方也不能理解
二、OSI七层模型


物理层
物理层负责光信号与电信号的传递,核心是确定信号的传输介质。常见介质包括以太网通用的双绞线、早期以太网使用的同轴电缆(现多用于有线电视)、光纤,以及 WiFi 无线网络使用的电磁波。物理层直接决定网络的最大传输速率、传输距离和抗干扰能力,集线器(Hub)是工作在该层的设备。
数据链路层
数据链路层的核心任务是实现设备间数据帧的传送与识别。具体工作包含网卡设备驱动管理、帧同步(明确从网线信号中识别新帧开始的标准)、冲突检测(检测到冲突时自动重发数据),以及数据差错校验。该层有以太网、令牌环网、无线 LAN 等标准,交换机(Switch)是工作在该层的设备。
网络层
网络层主要负责地址管理与路由选择。以 IP 协议为例,它通过 IP 地址标识主机身份,并借助路由表规划两台主机之间的数据传输线路(即路由)。路由器(Router)是工作在该层的核心设备。
传输层
传输层的功能是保障两台主机之间的数据传输。其中传输控制协议(TCP)是典型协议,能确保数据从源主机可靠地发送到目标主机。
应用层
应用层负责应用程序之间的沟通,提供具体的网络应用服务。常见协议包括简单电子邮件传输协议(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。日常的网络编程主要针对应用层展开。
三、OSI
- OSI(Open System Interconnection,开放系统互连)七层⽹络模型称为开放式系统互联参考
- 模型,是⼀个逻辑上的定义和规范;
- 把网络从逻辑上分为了7层. 每⼀层都有相关、相对应的物理设备,比如路由器,交换机;
- OSI 七层模型是⼀种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传输;
- 其实没有网络,即便单机情况,计算机内部也会有自己的协议
- 计算机内部走线十分的紧凑,所以会产生相互干扰所以需要校验协议
- 网络通信和单机的区别就是距离变成了(会产生新的问题,需要新的解决问题,这就是协议)
- TCP/IP是一种解决网络通信的具体方案,而问题是层状的所以解决方案也必须是层状的
四、网络协议TCP/IP vs OSI

总结:
1、协议本质是一个约定
2、OS一般用C语言来写的
3、操作系统需要先描述在组织
3、协议就是结构体(在协议栈内部的)
4、协议相同,不同的操作系统就可以通信
(1)协议的约定是如何做到的
- 遵守相同的标准
- 网络传输的基本流程
(2)局域网通信原理(以太网为例)
首先回答,两台主机在同⼀个局域⽹,是否能够直接通信?
- 原理类似点名(所有人都听到名字,但是只有对应的人才会答到,其他人会自动忽视)
- 每台主机在局域⽹上,要有唯⼀的标识来保证主机的唯⼀性:mac地址

(3)认识MAC地址
- MAC地址用来识别数据链路层中相连的节点;长度为48比特位,即6个字节,一般用16进制数字加上冒号的形式来表示(例如:08:00:27:03:fb:19)
- 在网卡出厂时就确定了,不能修改,mac地址通常是唯一的(虚拟机中的mac地址不是真实的mac地址,可能会冲突;也有些网卡支持用户配置mac地址)

(4)认识以太网
- 很多主机都开始互相发生消息,会互相碰撞,造成信息丢失
- 所以主机采用碰撞检测和碰撞避免 (让对应的主机等一下,在重新发送)
- 以太网 :是基于碰撞检查和碰撞避免的通信模式目前应有范围最广,被镶嵌在数据链路层
换一个角度
- 以太网是一个公共资源,碰撞检查和碰撞避免(任何一个时刻,只允许一台,使用以太网,向目标主机发送数据)就是锁的功能
为什么局域网会存在这么多的技术(以太网,令牌环网)
局域网最先出现
(4)链路层发送消息,从哪里来,谁让发的
用户发的

- 每一层都有协议,所以当我进行上述传输的时候,需要进行封装和解包

- 每一层的报头类似于贴在快递上的快递单

- 报头部分是对应协议层的结构体字段,一般叫做报头;
- 除了报头,剩下的叫做有效载荷,
- 故报文=报头+有效载荷。
- 不同的协议层对数据包有不同的称谓:在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。
- 应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装(Encapsulation)。
- 首部信息中包含了一些类似于首部有多长、载荷(payload)有多长、上层协议是什么等信息。
- 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的'上层协议字段'将数据交给对应的上层协议处理。
- 逻辑上同层之间都以为自己在和对方同层协议在直接通信,物理上是先从上到下,从下到上

任何协议(除了应用层)都要解决两个问题
(1)将报头和有效载荷分离
(2)把数据交付到上一层
五、重谈局域网通信

局域网通信,是主机通信吗
本质是协议栈之间在进行通信
六、跨网络传输

- 为了标识长距离的主机我们使用ip
- IP 协议有两个版本,分别是 IPv4 和 IPv6。在整个课程中,凡是提到 IP 协议且无特殊说明的,默认均指 IPv4。
(1)IP 地址的核心定义
- IP 地址是 IP 协议中用于标识网络中不同主机的地址。
- 对于 IPv4 而言,IP 地址是一个 4 字节(即 32 位)的整数。
- IPv4 地址通常采用 "点分十进制" 的字符串形式表示,例如 192.168.0.1。这种表示法中,用点分割的每一个数字对应一个字节,其取值范围为 0-255。
(2)MAC和ip地址的区别
IP 地址和 Mac 地址的区别主要体现在路由过程中的变化情况与功能定位上:
- IP 地址在整个路由过程中一直不变(当前阶段暂作此说明,后续会进一步修正)。
- Mac 地址在整个路由过程中一直变化。
- 目标 IP 是一种长远目标,Mac 地址是下一阶段目标;其中,目标 IP 是路径选择的重要依据,Mac 地址是局域网转发的重要依据。

七、传输路径


(1)mac地址为什么一直在变化
- 每经历一个路由器,就要向上交付,解包,向下支付,封装的过程,mac 地址只在局域网有效
- 上图我们会发现网络层向上,大家看到的数据报都是一样的,所以我们的互联网,是建立在ip网络的基础上的
(2)ip协议存在意义
虚拟化了底层局域网的差异
八、Socket 编程预备
- 数据传输到主机是目的吗?不是,因为数据是给人看的
- 但是⼈是怎么看到聊天信息的呢?怎么执行下载任务呢?怎么浏览网页信息呢?通过启动进程(例如: QQ ,微信),所以进程是人的代表,数据传输到主机不是目的,而是手段,到达主机内部将数据交给进程才是目的(网络通信就是进程通信)
- 数据到达主机后如何发给目标进程? 端口号(标识进程的唯一性)
九、认识端口号
端口号是一个 2 字节 16 位的整数;
- 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
- IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用。
- 一个进程可以多个端口号

(1)如何理解通过端口号找到目标进程
操作系统中存在一张哈希表,将端口号和TCP一一对应,通过TCP找到端口号
端口号划分
- 0 - 1023:属于知名端口号,HTTP、FTP、SSH 等广泛使用的应用层协议,它们的端口号都是固定的。
- 1024 - 65535:是操作系统动态分配的端口号,客户端程序的端口号,就是由操作系统从这个范围分配的。
十、理解socket
综上,IP 地址用于标识互联网中唯一的一台主机,端口(port)则用于标识该主机上唯一的一个网络进程。
- IP 地址与端口(port)结合,就能表示互联网中唯一的一个进程。
- 因此通信时,本质是互联网中的两个进程代表用户进行交互,而 {源 IP(srcIp)、源端口(srcPort)、目的 IP(dstIp)、目的端口(dstPort)} 这样的 4 元组,可精准标识互联网中这两个通信的进程。
- 由此可见,网络通信的本质,其实也是进程间通信。
- 我们将 IP 地址与端口(port)的组合,称为套接字(socket)。
(1)认识TCP协议
- 有连接
- 可靠传输
- 面向字节流
- 传输层协议
(2)认识UDP协议
- 无连接
- 不可靠传输
- 面向数据报
- 传输层协议
系统中同时存在大端和小端通信,网络通信必须使用大端(先发的是低地址,后发的是高地址)
(3)socket 编程接口
cpp
// 创建 socket ⽂件描述符 (TCP/UDP, 客⼾端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端⼝号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听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);
网络通信是有不同的场景的,也是有不同的解决方案的
1、unix 域间socket
2、网络socket
3、原始socket
我们使用一套socket 解决上述问题,所以我们socket 接口的参数中有一个struct sockaddr *address

(4)UDP socket
Echo server 接口服务
recvform

flags :设置读写状态,0为阻塞读
src _addr : 输入输出性参数,获取对方的ip+socket
addrlen: 输入输出性参数,获取对方的结构体大小
返回值:读取多少字节

dest addr: 目标主机
addrlen : 目标主机结构体大小
EchoServer
cpp
#pragma once
#include <iostream>
#include <string>
#include "mutex.hpp"
#include <filesystem>
#include <fstream>
#include <ctime>
#include <sstream>
#include <memory>
#include <unistd.h>
enum class Loglevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string LogLevelToString(Loglevel level)
{
switch (level)
{
case Loglevel::DEBUG:
return "DEBUG";
case Loglevel::INFO:
return "INFO";
case Loglevel::WARNING:
return "WARNING";
case Loglevel::ERROR:
return "ERROR";
case Loglevel::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string GetCurrTime()
{
time_t tm = time(nullptr);
struct tm curr;
localtime_r(&tm,&curr);
char timebuffer[64];
snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",
curr.tm_year + 1900,
curr.tm_mon + 1,
curr.tm_mday,
curr.tm_hour,
curr.tm_min,
curr.tm_sec
);
return timebuffer;
}
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string messgase) = 0;
};
class ConsoleLogStrategy : public LogStrategy
{
public:
~ConsoleLogStrategy()
{
}
void SyncLog(const std::string messgase) override
{
{
Lockguard lockguard(&_lock);
std::cout << messgase << std::endl;
}
}
private:
Mutex _lock;
};
const std::string path ="log";
const std::string filename = "test.log";
class FileLogStrategy: public LogStrategy
{
public:
FileLogStrategy(const std::string logpath = path, const std::string logfilename = filename)
:_logpath(logpath),
_logfilename(logfilename)
{
{
Lockguard lockguard(&_lock);
if (std::filesystem::exists(_logpath))
{
return;
}
try
{
std::filesystem::create_directories(_logpath);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
}
}
~FileLogStrategy()
{
}
void SyncLog(const std::string messgase) override
{
{
Lockguard lockguard(&_lock);
std::string target = _logpath;
target += '/';
target += _logfilename;
std::ofstream out(target.c_str(), std::ios::app);
if (!out.is_open())
{
return;
}
out << messgase << std::endl;
out.close();
}
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _lock;
};
class Logger
{
public:
Logger()
{
}
void EnableFileLogStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
void EnableConsoleLogStrategy()
{
_strategy = std::make_unique<ConsoleLogStrategy>();
}
class LogMessage
{
public:
LogMessage(Loglevel level, std::string &filename, int line, Logger &logger)
:_time(GetCurrTime()),
_level(level),
_pid(getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
std::stringstream ss;
ss << "[" << _time << "]"
<< "[" << LogLevelToString(_level) << "]"
<< "[" << _pid << "]"
<< "[" << _filename << "]"
<< "[" << _line << "]"
<< "-";
_loginfo = ss.str();
}
template<typename T>
LogMessage & operator<<(const T& info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if(_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
std::string _time;
Loglevel _level;
pid_t _pid;
std::string _filename;
int _line;
std::string _loginfo;
Logger &_logger;
};
LogMessage operator()(Loglevel type, std::string filename, int line)
{
return LogMessage(type, filename, line,*this);
}
~Logger()
{
}
private:
std::unique_ptr<LogStrategy> _strategy;
};
Logger logger;
#define LOG(type) logger(type, __FILE__, __LINE__)
#define FileLogStrategy() logger.EnableFileLogStrategy()
#define ConsoleLogStrategy() logger.EnableConsoleLogStrategy()
Makefile
cpp
.PHONY:all
all: udp_cient udp_server
udp_cient:UdpClient.cc
g++ -o $@ $^ -std=c++17
udp_server:UdpServer.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -rf udp_cient udp_server
mutex.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&lock,nullptr);
}
void Lock()
{
pthread_mutex_lock(&lock);
}
void Unlock()
{
pthread_mutex_unlock(&lock);
}
~Mutex()
{
pthread_mutex_destroy(&lock);
}
pthread_mutex_t *GetMutexOriginal()
{
return &lock;
}
private:
pthread_mutex_t lock;
};
class Lockguard
{
public:
Lockguard(Mutex* mutexgurd):_mutexgurd(mutexgurd)
{
_mutexgurd->Lock();
}
~Lockguard()
{
_mutexgurd->Unlock();
}
private:
Mutex *_mutexgurd;
};
cpp
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <cstring>
void Usage(std::string proc)
{
std::cout<< "usage fail" <<std::endl;
}
int main(int argc,char*argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(0);
}
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
return 1;
}
uint16_t port =std::stoi(argv[2]);
const std::string ip = argv[1];
struct sockaddr_in serv_addr;
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(ip.c_str());
while(true)
{
std::cout<<"please enter@";
std::string line;
std::getline(std::cin,line);
ssize_t n = sendto(sockfd,line.c_str(),line.size(),0,(struct sockaddr*)&serv_addr, sizeof(serv_addr));
char buffer [1024];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t a = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
}
return 0;
}
cpp
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <cstring>
void Usage(std::string proc)
{
std::cout<< "usage fail" <<std::endl;
}
int main(int argc,char*argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(0);
}
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
return 1;
}
uint16_t port =std::stoi(argv[2]);
const std::string ip = argv[1];
struct sockaddr_in serv_addr;
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(ip.c_str());
while(true)
{
std::cout<<"please enter@";
std::string line;
std::getline(std::cin,line);
ssize_t n = sendto(sockfd,line.c_str(),line.size(),0,(struct sockaddr*)&serv_addr, sizeof(serv_addr));
char buffer [1024];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t a = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
}
return 0;
}
Udpserver.hpp
cpp
#pragma once
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <cstring>
#include "Logger.hpp"
static const int gdefaultsockfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port)
:_sockfd(gdefaultsockfd),
_running(false),
_port(port)
{
}
void Init()
{
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd < 0)
{
LOG(Loglevel::FATAL)<<"creat socket fail";
exit(1);
}
LOG(Loglevel::INFO) <<"creat socket success";
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;//绑定任意ip
//不明确具体IP,只要是发给我对应的主机,对应的port 我都接受
int n = bind(_sockfd,(struct sockaddr*)& local,sizeof(local));
if(n < 0)
{
LOG(Loglevel::FATAL)<<"bind socket error";
exit(2);
}
LOG(Loglevel::INFO)<<"bind socket success :" << _sockfd;
}
void Start()
{
_running = true;
while(_running)
{
char buffer[1024];
buffer[0] = 0;
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
if (n > 0)
{
buffer[n] = 0;
LOG(Loglevel::DEBUG) << "client say ###" << buffer;
std::string echo_string = "sever say ###";
echo_string += buffer;
ssize_t n = sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&peer, len);
if (n < 0)
{
LOG(Loglevel::FATAL) << "sento fail";
}
}
}
_running = false;
}
void Stop()
{
_running = false;
}
~UdpServer()
{
}
private:
int _sockfd;
bool _running;
uint16_t _port;
// std::string _ip;//云服务器,禁止用户bind公网IP
};
(5)TCP的实现

backlog :
listen()声明sockfd处于监听状态, 并且最多允许有backlog个客⼾端处于连接等待状态, 如果接收
到更多的连接请求就忽略, 这⾥设置不会太大(⼀般是5), 具体细节同学们课后深⼊研究;
返回值:成功返回0,失败返回-1;

作用:获取新链接,没有新连接,默认是阻塞的,有新连接,获取的新连接
返回值:如果成功返回一个新的文件描述符

_socktd vs accpet return fd
例子:

一个饭店中,服务员一负责拉课,服务员二负责服务客人
饭店代表服务器,服务员一是_listensocktd作用是获取新链接,服务员二:服务新连接
connect

作用:发起链接请求
返回值:成功返回0,失败返回-1

作用:发送信息
inet_aton : 将主机地址转化成字符串

最佳实践:inet_pton

