项目概要:从零开始认识客户端到服务器之间如何沟通、联络,如何进行数据之间的通信
客户端与服务器交互原理:
在日常生活中,如果我们想要上网的话就要去办理宽带,买路由器,并且由安装师傅帮我们安装并设置一些东西后,我们才可以上网。那我们上网数据是怎样一个传输模式呢?国际标准化组织(ISO)给出了标准OSI七层模型
一、OSI的七层模型:(了解)
1.应用层:针对特定应用的协议
2.表示层:设备固有数据格式和网络标准数据格式的转换
3.会话层:通信管理,负责建立和断开通信连接。管理传输层以下的分层
4.传输层:管理两个结点之间的数据传输。负责可靠传输
5.网络层:地址管理与路由选择
6.数据链路层:互联设备之间传送和识别数据帧
7.物理层:相当于安装的线(网线),插入路由器那个水晶头
是不是觉得很复杂呢?有人推出了TCP/IP四层模型/五层模型
二、TCP/IP四层(五层)模型(重点理解)
TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇.
TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求
1.物理层:基建,比如网线啊之类的。主要负责光/电信号的转换
2.数据链路层:设备之间的数据帧的传送和识别,如网卡设备的驱动、帧同步、冲突检查、数据差错校验等工作,有以太网、令牌环网等工作在数据链路层
3.网络层(IP协议):负责地址管理和路由选择. 例如在IP协议中, 通过IP地址来标识一台主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层
4.传输层(TCP/UDP协议):负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机.
5.应用层(HTTP、SSH、SIP等协议):负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问 协议(Telnet)等. 我们的网络编程主要就是针对应用层一般我们不讨论物理层,所有有我们称TCP/IP四层模型
模型如下:后面我们将一一介绍各个部分的功能
三、什么是协议protocol以及协议的作用
1.什么是协议
比如在打LOL中,solo赛中补满100个兵或者第一个杀掉对方的人就算赢,我们都会遵守这种规则。像这种人定的规则,双方都遵守的,我们称之为协议。
2.协议有什么用?
通过协议我们可以从中获得有用的信息
基础知识socket/TCP/HTTP
一、socket套接字:
数据是由人产生,我们想要从我们这段传送数据,首先得先找到谁要接收我的数据,他的机器上正在跑的哪个进程要接收我的数据,就产生了IP地址和端口号port,一个IP地址标志了互联网中唯一一台计算机,这样我们就可以找到我们要传送给谁,port则标志着这台计算机中正在运行的进程是哪一个,这样我们就可以找到我们要送给谁来,socket内就包含着IP和port
二、如何写一个套接字?(服务器端)
1.首先我们先认识以下套接字接口
void Socket()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);//创建一个套接字
if(listen_sock<0)
{
LOG(WARNING,"Create socket fail...");
exit(1);
}
//创建成功
LOG(INFO,"Create socket success...");
//假设对方连接断开了,或者导致程序挂掉了,我们无法立马让服务器跑起来了,所以我们需要用到setsockopt
int opt=1;
setsockopt(listen_sock,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
LOG是我们自己写的打印日志
#pragma once //防止重复包头文件
#include<iostream>
#include<string>
#include<ctime>
#define INFO 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
#define LOG(level,message) log(#level,message,__FILE__,__LINE__)
//#level 将int 转为 字符 __FILE__指代个文件 ,__LINE__指那一行
void log(std::string level , std::string message ,std::string file_name , int line)
{
std::cout<<"["<<level<<"]["<<time(nullptr)<<"]["<<message<<"]["<<file_name<<"]["<<line<<"]"<<std::endl;
}
setsockopt是防止连接断开后无法立马重启所用的函数,这里就要设计到四次挥手的问题,请看以前的文章(TCP那一章,还没写)
到这里我们套接字就完成了
2.绑定ip和端口号
先认识一下绑定函数bind及其参数
1.IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中 ,IPv4 地址用 sockaddr_in 结构体表示 , 包括 16 位地址类型 , 16位端口号和 32 位 IP 地址 .2.IPv4 、 IPv6 地址类型分别定义为常数 AF_INET 、 AF_INET6. 这样 , 只要取得某种 sockaddr 结构体的首地址 ,不需要知道具体是哪种类型的 sockaddr 结构体 , 就可以根据地址类型字段确定结构体中的内容 .3.socket API 可以都用 struct sockaddr * 类型表示 , 在使用的时候需要强制转化成 sockaddr_in; 这样的好 处是程序的通用性 , 可以接收 IPv4, IPv6, 以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数
const struct sockaddr是通用的类型,我们要把他转成我们所对应的结构sockaddr_in,进行填充数据
void Bind()
{
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(ERROR,"Bind Socket fail...");
exit(2);
}
LOG(INFO,"Bind Socket success...");
}
local.sin_family是选择哪一个ip协议,local.sin_port是存放的端口号,local.sin_addr.s_addr存放的是ip地址,给哪一台主机通信,这里我们填的是INADDR_ANY存放的其实就是0,因为我们这里是服务器端,不能只连接一台设备进行通信,我们需要接受多个设备的申请才叫服务器
绑定成功后返回值是零,如果失败为-1,所以可以根据这个判断是否绑定成功
3.监听Listen
当我们绑定好后就可以开始监听了,就是看谁来申请跟我连接,认识一下listen接口吧
void Listen()
{
if(listen(listen_sock,BackLog)<0)
{
LOG(ERROR,"Listen client fail...");
exit(3);
}
LOG(INFO,"Listen client success...");
}
到这里我们就接收成功了,得到了对方的套接字
前期的准备工作就完成了
所以我们创建一个套接字需要的步骤就是1.先创建一个套接字2.对该套接字进行绑定
如果是服务器的话3.是listen进入监听
如果是客户端的话3.是connect要与服务器连接
我们将前三个步骤封装在一起,可以快速进行套接字的初始化
void InitTCP_Server()
{
Socket();
Bind();
Listen();
LOG(INFO,"Initial Server success...");
}
最后我们还可以提供一个接口,返回从listen那接收到的套接字sock给别的文件使用
int Sock()
{
return listen_sock;
}
最后再搞个单例模式,整体代码如下
#define BackLog 5
class Tcp_Server
{
private:
int listen_sock;
int port;
static Tcp_Server* svr;
Tcp_Server(int _port):port(_port),listen_sock(-1)
{}
Tcp_Server(const Tcp_Server& s){}
public:
static Tcp_Server* GetInstance(int port)
{
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
if(svr==nullptr)
{
pthread_mutex_lock(&mtx);
//原子性操作,上锁
if(svr==nullptr)
{
svr = new Tcp_Server(port);
svr->InitTCP_Server();
}
pthread_mutex_unlock(&mtx);
}
return svr;
}
void Socket()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);//创建一个套接字
if(listen_sock<0)
{
LOG(WARNING,"Create socket fail...");
exit(1);
}
//创建成功
LOG(INFO,"Create socket success...");
//假设对方连接断开了,或者导致程序挂掉了,我们无法立马让服务器跑起来了,所以我们需要用到setsockopt
int opt=1;
setsockopt(listen_sock,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
void Bind()
{
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(ERROR,"Bind Socket fail...");
exit(2);
}
LOG(INFO,"Bind Socket success...");
}
void Listen()
{
if(listen(listen_sock,BackLog)<0)
{
LOG(ERROR,"Listen client fail...");
exit(3);
}
LOG(INFO,"Listen client success...");
}
void InitTCP_Server()
{
Socket();
Bind();
Listen();
LOG(INFO,"Initial Server success...");
}
int Sock()
{
return listen_sock;
}
};
Tcp_Server* Tcp_Server::svr=nullptr;