【草履虫10天学会C++网络编程】 UDP

发布于:2022-12-03 ⋅ 阅读:(134) ⋅ 点赞:(0)

学习路线:

  1. 计算机网络概念,七层模型和四层模型,协议(tcp,ip)mac地址 ip地址 port子网掩码...
  2. 数据包的资格组包和拆包流程tcp/udp特点
  3. udp编程 编程准备(字节序,端口,IP大小端转换的函数)udp API发送接收数据
  4. udp-tftp编程 udp广播(只允许在局域网广播)
  5. tcp编程 客户端和服务端流程 三次握手四次挥手
  6. tcp高并发服务器 多进程多线程服务器 select,poll,epoll实现的tcp服务器 epoll+线程池
  7. 手写web服务器
  8. 网络通讯过程
  9. 原始套接字(黑客)自己组底层的数据包,收一帧完整的数据包
  10. web-html 写个网页

目录

学习路线:

1、TCP/IP协议特点

1.1 网络分层结构

1. 2  IP(因特网互联协议)协议简介(网络协议)

1.3 MAC地址、IP地址、Netmask

1.4 端口

1.5 组包拆包

1.6 协议格式

IP报头:

tcp报头

2、网络开发模式

2.1 tcp和udp的区别

2.2 字节序

 2.3 网络编程

 2.4 网络字节序转API

2.5 地址转换函数

2.6 网络编程接口 socket

2.7 网络编程接口 socket

3. UD 

3.1 代码编写

3.2  创建套接字

创建UDP套接字demo:

3.3 TFTP简介、通信过程

3.4 UDP广播


1、TCP/IP协议特点

这是一个协议(多种协议的集合)

1.1 网络分层结构

因为网络在通讯过程中是非常复杂的事情,我们要将网络分层一片片小的事情,把每一层做好了就可以发送了。上一层对下一层,或者下一层对上一层是屏蔽的。

 OSI开发系统互联模型(七层)

  1. 物理层:各种网络接口的类型,传输速率等等的一些物理特性。
  2. 数据链路层:负责对数据帧的收发。
  3. 网路层:负责判断数据包是否是该主机接受的,通过IP地址,用来让数据包能够最大可能的到达主机。
  4. 传输层:将数据和程序通讯的桥梁,通过端口,提供进程之间的联系。
  5. 会话层:保持连接。
  6. 表示层:数据解压解码等。
  7. 应用层:应用程序。

4层模型(常见)

  1. 链路层:设备(网卡)对设备的收发。
  2. 网络层:主机到主机的收发。
  3. 传输层:进程到进程的数据收发(每个应用程序都有自己的端口)
  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

网络通信要解决的是不同主机进程间的通信:

  1. 首要解决的网络进程标识问题
  2. 以及多重协议识别问题

 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)

特点:

  1. 处于同一子网的所有主机都必须处理数据
  2. UDP数据包会沿协议栈向上一直到UDP层
  3. 运行音视频等较高速率工作的应用,会带来大负
  4. 局限于局域网内使用

 

 单拨:会一对一找地址

广播:不会,全部无条件接受

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到