学习路线:
- 计算机网络概念,七层模型和四层模型,协议(tcp,ip)mac地址 ip地址 port子网掩码...
- 数据包的资格组包和拆包流程tcp/udp特点
- udp编程 编程准备(字节序,端口,IP大小端转换的函数)udp API发送接收数据
- udp-tftp编程 udp广播(只允许在局域网广播)
- tcp编程 客户端和服务端流程 三次握手四次挥手
- tcp高并发服务器 多进程多线程服务器 select,poll,epoll实现的tcp服务器 epoll+线程池
- 手写web服务器
- 网络通讯过程
- 原始套接字(黑客)自己组底层的数据包,收一帧完整的数据包
- web-html 写个网页
目录
1、TCP/IP协议特点
这是一个协议簇(多种协议的集合)
1.1 网络分层结构
因为网络在通讯过程中是非常复杂的事情,我们要将网络分层一片片小的事情,把每一层做好了就可以发送了。上一层对下一层,或者下一层对上一层是屏蔽的。
OSI开发系统互联模型(七层)
- 物理层:各种网络接口的类型,传输速率等等的一些物理特性。
- 数据链路层:负责对数据帧的收发。
- 网路层:负责判断数据包是否是该主机接受的,通过IP地址,用来让数据包能够最大可能的到达主机。
- 传输层:将数据和程序通讯的桥梁,通过端口,提供进程之间的联系。
- 会话层:保持连接。
- 表示层:数据解压解码等。
- 应用层:应用程序。
4层模型(常见)
- 链路层:设备(网卡)对设备的收发。
- 网络层:主机到主机的收发。
- 传输层:进程到进程的数据收发(每个应用程序都有自己的端口)
- 应用层:应用程序
1. 2 IP(因特网互联协议)协议简介(网络协议)
- 特点:不可靠,不能保证IP数据包能成功到达它的目的地
- 无连接:不维护后续数据包的状态信息。每个数据包的处理是相互独立的,可以不按照发送顺序接收。
1.3 MAC地址、IP地址、Netmask
- 网卡(适配器):硬件,能传输数据的都是网卡信号转为数字信号,每个网卡都有自己的物理地址(全球理论上唯一)-> MAC地址48bit 6字节
- IP地址:表示手机或者网卡的虚拟地址,ipv4(局域网):32位; ipv6(广域网)。128位 主机ip(192.168.1.12)+子网id(255.255.255.0)
- 子网掩码:连续的1或者0,结合ip一起使用
- 子网id:IP中被子网掩码1中连续覆盖的位置
- 主机id:IP中被子网掩码0中连续覆盖的位置
回环地址:用来测试本主机,到网络层就截止了
linux下设置地址:
ifconfig eth0 192.168.1.1 netmask 255.255.255.0
1.4 端口
四层网络->链路层->网络层->传输层->应用层
端口号的主要用途是在计算机网络和应用程序之间(传输层)传输数据
端口号的范围从 0 到 65535(一个字节8位,2的16次方=65536)。它们分为三个子范围:
知名端口 (0-1023,比如说SSH:22) – 由IANA(互联网号码分配机构)分配给特定服务。在 Linux 系统上,只有以 root 身份运行的特权程序才能使用 1024 以下的端口。
注册端口 (1024-49151) – 组织可以向 IANA 注册以用于特定服务的端口。
动态端口 (49152-65535) – 由客户端程序使用。
在网络中有两种使用端口号的主要传输协议TCP和UDP)
端口可以重用,但是不能重复(标记应用程序的,为啥不用进程号?因为启动的时候没法确定进程号)
1.5 组包拆包
A主机的MAC地址和IP地址:应用层->发送飞秋协议->组合包->传输层走UDP(局域网)协议->
网络层走IP协议->链路层加mac
B主机的MAC地址和IP地址:反方向时钟
1.6 协议格式
注意:
无线网封装格式 :IEEE802.2
以太网封装:有限局域网 无线网比有限多8个字节
IP报头:
前部有20个字节用来放协议。
TTL:是一个经过了多少个路由器,默认是64个值,每经过一个就-1.
ping www.baidu.com
可以看出来,百度和你之间经过了64-56=8个路由器,当TTL减到0的时候,这个数据包就不会转发了,他会觉得你这个包是个垃圾,就不要了。
tcp报头
序列号:我当前包有个序列号
确认序列号:如果我收到了你给我发的那个包,确认收到(很重要,面试会问)
2、网络开发模式
B/S browser/sever 浏览器与服务器模型
- 缺点:对服务器性能要求过高,不能用于对性能要求高的程序。
- 优点:不容易偷数据,比较安全,移植性高不依赖平台。开发平台较短
C/S clien/sever 客户端和服务器
- 缺点:不是特别安全,依赖平台
- 优点:可运行对性能要求较高的程序
2.1 tcp和udp的区别
TCP是面向连接的:打电话模型的抽象,本质上是一个管道。分为建立连接->使用连接->中止连接
UDP面向无连接。(没有服务器和客户端一说):邮件系统服务模式的抽象,速度快。自己加协议,让其可靠。
- 不能保证分组的先后顺序
- 不能进行分组出错的恢复和重传
- 不能抱枕数据传输的可靠性
总结:TCP是传输控制协议,面向连接的打电话的抽象模型,出错重传,比较安全可靠,每次收到协议就会回应给ACK,UDP是用户协议包协议出错不重传,相对不可靠w。
2.2 字节序
大端和小端:
- 大端:低位存高地址,高位存低地址(服务器,网络端)
- 小端:地位存低地址,高位存高地址(电脑)
unsigned short num = 0x0102;
为了保证传输的正确,发送转成网络字节序,收就转主机序列
2.3 网络编程
确定电脑是大小端的代码:
#include <stdio>
typedef union stf
{
unsigned short a;
unsighef char b[2];
}STD;
int main()
{
STD tmp;
tmp.a = 0x0102;
if (tmp.b[0] == 0x01)
{
printf('big');
}
else
{
printf('small');
}
return 0;
}
2.4 网络字节序转API
字节序转换函数:
htonl(host_to_online) 将主机数转为网路段(大段) 32位
htons 将主机数转为网路字节序数据(大段) 16位
ntohl 将32位网络字节序数据转换成主机字节序数据(大段) 16位
ntohs 将16位网络字节序数据转换成主机字节序数据
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
int num = 0x01020304; //小端
int sum = htonl(num);
short b = htons(a);
return 0;
}
2.5 地址转换函数
inet_pton 字符串ip地址转整型数据
int inet_pton(int family,const char *strptr, void *addrptr);
- 功能:将点分十进制数串转换成32位无符号整数
- 参数:family:协议族 | strptr:点分十进制数串 |addrptr:32位无符号整数的地址
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
char ip_str[] = "10.0.13.100";
unsigned int ip_unit = 0;
unsigned char * ip_p =NULL;
inet_pton(AF_INET,ip_str,&ip_uint);
ip_p = (unsigned char *) &ip_uint;
return 0;
}
inet_ntop 字符串ip地址转整型数据
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
#include<stdio.h>
#include<arpa/inet.h>
int main()
{
unsigned char ip[]={10,0,13,252};
char ip_str[16];
inet_ntop(AF_INET,(unsigned int *)ip,ip_str,16);
printf("ip_str = %s\n",ip_str);
return 0;
}
output:10.0.13.252
2.6 网络编程接口 socket
网络通信要解决的是不同主机进程间的通信:
- 首要解决的网络进程标识问题
- 以及多重协议识别问题
socket 套接字解决(socket只能成对出现) —>socket pair 底层就是缓存区
2.7 网络编程接口 socket
对于UDP来说可以么有服务器,服务器只是认为规定的一种,主动发送的一方为客户端,被动接受的一方为服务器。如果作为服务器被动等待别人发送数据,这个服务器需要绑定上固定的IP+端口。
3. UD
流程:创建套接字->绑定端口
数据自上而下传输的过程,首先一开始通常是我们的客户端主动连接服务器,
自己的端口号+别人的端口号+报头和内容长度+校验和验证报文是否有问题+内容
3.1 代码编写
udp.h
#pragma once
#include "SocketInit.h"
#include "TcpEvent.h"
class Udp
{
Udp();
~Udp();
public:
static Udp& GetInstance();
void SetBindAddress(const char* ip = "127.0.0.1", int port = 0);
void SetRemoteAddress(const char* ip, int port);
bool Init(bool setBroadCast);
void Close();
int SendTo(TcpEvent* tcpEvent);
int RecvFrom(TcpEvent* tcpEvent);
bool ZipSendTo(TcpEvent* tcpEvent);
bool ZipRecvFrom(TcpEvent* tcpEvent);
private:
static Udp m_Instance;
SOCKET m_Socket;
sockaddr_in m_BindAddress;
sockaddr_in m_RemoteAddress;
int m_RemoteAddressLen;
};
udp.cpp
#include "Udp.h"
#include "Logger.h"
#include "zip/zlib.h"
#include <stdio.h>
#include <iostream>
using namespace std;
Udp Udp::m_Instance;
Udp::Udp()
{
m_Socket = INVALID_SOCKET;
memset(&m_BindAddress, 0, sizeof(m_BindAddress));
memset(&m_RemoteAddress, 0, sizeof(m_BindAddress));
m_RemoteAddressLen = sizeof(SOCKADDR);
}
Udp::~Udp()
{
closesocket(m_Socket);
}
Udp& Udp::GetInstance()
{
return m_Instance;
}
void Udp::SetBindAddress(const char* ip, int port)
{
m_BindAddress.sin_family = AF_INET;
m_BindAddress.sin_addr.S_un.S_addr = inet_addr(ip);
m_BindAddress.sin_port = htons(port);
WRITE_LOG(LogLevel::Info, "Udp SetBindAddress IP[%s], Port[%d]", ip, port);
}
void Udp::SetRemoteAddress(const char* ip, int port)
{
m_RemoteAddress.sin_family = AF_INET;
m_RemoteAddress.sin_addr.S_un.S_addr = inet_addr(ip);
m_RemoteAddress.sin_port = htons(port);
m_RemoteAddressLen = sizeof(m_RemoteAddress);
WRITE_LOG(LogLevel::Info, "Udp SetRemoteAddress IP[%s], Port[%d]", ip, port);
}
bool Udp::Init(bool setBroadCast)
{
m_Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (m_Socket == INVALID_SOCKET)
{
WRITE_LOG(LogLevel::Error, "Create Udp Socket Failed.");
Close();
return false;
}
if (setBroadCast)
{
BOOL bBroadcast = TRUE;
if (::setsockopt(m_Socket, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL)) == SOCKET_ERROR)
{
WRITE_LOG(LogLevel::Error, "setsockopt SO_BROADCAST Failed.");
Close();
return false;
}
}
auto ret = bind(m_Socket, (sockaddr*)&m_BindAddress, sizeof(SOCKADDR));
if (ret == SOCKET_ERROR)
{
WRITE_LOG(LogLevel::Error, "bind Failed. ret[%d]", ret);
Close();
return false;
}
WRITE_LOG(LogLevel::Info, "Udp Init Successed.");
return true;
}
void Udp::Close()
{
closesocket(m_Socket);
m_Socket = INVALID_SOCKET;
}
int Udp::SendTo(TcpEvent* tcpEvent)
{
auto sendLen = ::sendto(m_Socket, tcpEvent->ReadPos, tcpEvent->Length, 0, (sockaddr*)&m_RemoteAddress, sizeof(m_RemoteAddress));
WRITE_LOG(LogLevel::Info, "Udp SendTo:[%s:%u], Len:[%d], Ret:[%d], Buff:[%s]", inet_ntoa(m_RemoteAddress.sin_addr), ntohs(m_RemoteAddress.sin_port), tcpEvent->Length, sendLen, (unsigned char*)tcpEvent->Buff);
cout << "[";
for (auto i = 0; i < sendLen; i++)
{
printf("%02x", (unsigned char)tcpEvent->ReadPos[i]);
}
cout <<"]" << endl;
return sendLen;
}
int Udp::RecvFrom(TcpEvent* tcpEvent)
{
int recvLen = recvfrom(m_Socket, tcpEvent->Buff, BuffSize - 1, 0, (SOCKADDR*)&m_RemoteAddress, &m_RemoteAddressLen);
WRITE_LOG(LogLevel::Info, "Udp RecvFrom:[%s:%u], recvLen:[%d]", inet_ntoa(m_RemoteAddress.sin_addr), ntohs(m_RemoteAddress.sin_port), recvLen);
tcpEvent->Length = recvLen;
cout << "[";
for (auto i = 0; i < recvLen; i++)
{
printf("%02x", (unsigned char)tcpEvent->ReadPos[i]);
}
cout << "]" << endl;
return recvLen;
}
bool Udp::ZipSendTo(TcpEvent* tcpEvent)
{
WRITE_LOG(LogLevel::Info, "Udp ZipSendTo: Len:[%d], Buff:[%s]", tcpEvent->Length, tcpEvent->ReadPos);
TcpEvent* tcpEvent2 = TcpEvent::Allocate();
unsigned long destLen = BuffSize;
auto ret = compress2((unsigned char*)tcpEvent2->Buff, &destLen, (unsigned char*)tcpEvent->Buff, tcpEvent->Length, Z_BEST_SPEED);
auto result = true;
if (ret != Z_OK)
{
WRITE_LOG(LogLevel::Error, "compress Failed. ret:[%d]", ret);
result = false;
}
else
{
tcpEvent2->Length = destLen;
result = SendTo(tcpEvent2) > 0;
}
tcpEvent2->Free();
return result;
}
bool Udp::ZipRecvFrom(TcpEvent* tcpEvent)
{
TcpEvent* tcpEvent2 = TcpEvent::Allocate();
auto result = true;
int len = RecvFrom(tcpEvent2);
if (len <= 0)
{
tcpEvent2->Free();
return false;
}
unsigned long sourceLen = len;
unsigned long destLen = BuffSize - 1;
auto ret = uncompress2((unsigned char*)tcpEvent->Buff, &destLen, (const unsigned char*)tcpEvent2->Buff, &sourceLen);
if (ret != Z_OK)
{
WRITE_LOG(LogLevel::Error, "compress Failed. ret:[%d]", ret);
result = false;
}
else
{
tcpEvent->Length += destLen;
tcpEvent->Buff[tcpEvent->Length] = '\0';
WRITE_LOG(LogLevel::Info, "Udp ZipRecvFrom: Len:[%d], Buff:[%s]", tcpEvent->Length, tcpEvent->ReadPos);
}
tcpEvent2->Free();
return ret;
}
UdpCLient:
#include "Logger.h"
#include "Udp.h"
#include <iostream>
using namespace std;
void UdpTest()
{
Udp::GetInstance().SetBindAddress();
Udp::GetInstance().SetRemoteAddress("127.0.0.1", 6000);
Udp::GetInstance().Init(true);
while (true)
{
TcpEvent* tcpEvent = TcpEvent::Allocate();
cin >> tcpEvent->Buff;
tcpEvent->Length = strlen(tcpEvent->Buff) + 1;
Udp::GetInstance().ZipSendTo(tcpEvent);
tcpEvent->Free();
}
}
int main(int argc, char* argv[])
{
Logger::GetInstance().Init(argv[0]);
Logger::GetInstance().Start();
UdpTest();
Logger::GetInstance().Stop();
Logger::GetInstance().Join();
return 0;
}
UdpServer:
#include "Logger.h"
#include "Udp.h"
using namespace std;
void UdpTest()
{
Udp::GetInstance().SetBindAddress("127.0.0.1", 6000);
if (!Udp::GetInstance().Init(false))
{
return;
}
while (true)
{
TcpEvent* tcpEvent = TcpEvent::Allocate();
Udp::GetInstance().ZipRecvFrom(tcpEvent);
tcpEvent->Free();
}
}
int main(int argc, char* argv[])
{
Logger::GetInstance().Init(argv[0]);
Logger::GetInstance().Start();
UdpTest();
Logger::GetInstance().Stop();
Logger::GetInstance().Join();
return 0;
}
3.2 创建套接字
创建UDP套接字demo:
#include <sys/socket.h>
int sockfd = 0;
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
IPv4套接字地址结构:
#include <netinet/in.h>
struct in_addr
{
in_addr_t s_addr;//4字节
};
struct sockaddr_in
{
{
perror("socket");
exit(-1);
}
注意:
AF_INET:IPv4协议
SOCK_DGRAM:数据报套接字
0:选择所给定的family和type组合的系统默认值
2.4 UDP编程-发送、绑定、接收数据
2.4.1 IPv4套接字地址结构
头文件:#include <netinet/in.h>
struct in_addr
{
in_addr_t s_addr;//4字节
};
struct sockaddr_in
{
sa_family_t sin_family;//2字节
in_port_t sin_port;//2字节
struct in_addr sin_addr;//4字节
char sin_zero[8]//8字节
};
3.3 TFTP简介、通信过程
简单文件传输协议(一般用于局域网,被用于设计小文件),基于UDP,不会对用户进行有效验证。
传输格式:
- octet:二进制模式
- netascii:文本模式
提供给客户端上传和下载文件的功能。默认端口:69。
记住:会创建一个新的套接字哈
- 下载:每次收到文件 ——>客户端必须要发ACK(N) ——>不发继续发 ——>重复6次——>当数据小于512则代表是最后一次发送
- 上传:在读写作码上改成下载请求
3.4 UDP广播
发送的报文在局域网中所有的主机无条件接受,广播不能跑到另一个局域网。路由器会对广播进行封杀,只能UDP或者IP不能用TCP。
- 地址解析协议(ARP)
- 动态主机配置协议(DHCP)
- 网络时间协议(NTP)
特点:
- 处于同一子网的所有主机都必须处理数据
- UDP数据包会沿协议栈向上一直到UDP层
- 运行音视频等较高速率工作的应用,会带来大负
- 局限于局域网内使用
单拨:会一对一找地址
广播:不会,全部无条件接受