网络编程套接字——UDP

一、基础知识

1.区分源地址、目的地址

(1)源IP地址和目的地址:最开始的IP地址与送达数据的地址
(2)源MAC地址和目的MAC地址:相当于上一站的地址与下一站的地址,在不断地变化
socket通信,本质是进程间通信,只是是跨网络的进程间通信(如:客户端进程与服务器端进程的通信)

2.端口号

任何的网络服务与网络客户端,如果要进行正常的数据通信,必须要用端口号来唯一标识自身
(1)端口号是传输层的内容
(2)端口号是一个2字节16bit的整数
(3)端口号用来标识一个进程,告诉OS当前数据交给哪一个进程来处理
(4)IP地址+端口号:标识全网内唯一的一个进程
公网IP:唯一的标识全网内唯一的一台主机
端口号(port):标识一台主机上唯一的一个进程
(5)在同一个OS内,一个进程可以与一个端口号进行绑定,该端口号就在网络层面唯一标识一台主机上的唯一一个进程

ip+port的通信方式就叫做socket通信
进程PID VS 端口号
一台主机可能存在大量的进程(OS层面),但不是所有的进程都要对外进行网络数据请求(网络层面)(身份证号与公司员工号的关系,各有各的机制)

3.源端口号、目的端口号

源端口号:数据是谁发的
目的端口号:数据要发给谁

4.TCP、UDP(后面详细总结TCP、UDP异同)

都是传输层协议

TCP UDP
传输层协议 传输层协议
有连接 无连接
可靠传输 不可靠传输面向数据报
面向字节流 面向数据报

5.网络序列

网络上面的数据必须都是大端
(1)概念
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中;
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
规定:网络上的数据必须都是大端
(2)为什么规定是大端?
发送主机通常将发送缓冲区的数据按内存地址从低到高的顺序发出;
接收主机把从网络接到的数据一次保存在接收缓冲区中,也是按照从低到高的地址顺序保存。

二、socket

1.socket编程接口(常见API)

(1)创建socket文件描述符 (TCP/UDP, 客户端 + 服务器)

函数:int socket(int domain, int type, int protocol);
domain:域,协议家族(16位地址类型)
type:服务类型,分为两种,SOCK_STREAM(TCP),SOCK_DGRAM(UDP)
protocol:协议类别,一般设为0,默认,根据前两个字段自动推断需要的是哪一个协议

(2) 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr address,socklen_t address_len);
(3)开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
4)接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr
address,socklen_t* address_len);
(5)建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

2.sockaddr结构

网络编程套接字——UDP_第1张图片

(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结构体指针做为参数

3.简单UDP客户端/服务端通信

udp_client.hpp

#pragma once
#include
#include
#include
#include
#include
#include
#include
#include

class UdpClient
{
private:
	int sockfd;
	std::string server_ip;
	int server_port;
public:
	UdpClient(std::string _ip,int _port)
		:server_ip(_ip),server_port(_port)
	{}
	bool InitUdpClient()
	{
		sockfd = socket(AF_INET,SOCK_DGRAM,0);
		if(sockfd < 0)
		{
			std::cerr << "socket error" << std::endl;
			return false;
		}
		//客户端不需要绑定吗?(不需要)需要port吗?(需要)
	}
	void Start()
	{
		struct sockaddr_in peer;
		memset(&peer,0,sizeof(peer));
		
		peer.sin_family = AF_INET;
		peer.sin_port = htons(server_port);
		peer.sin_addr.s_addr = inet.addr(server_ip.c_str());
		
		for(;;)
		{
			std::cout << "Please enter#:" ;
			std::cin >> msg;
			sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));

			//读取消息
			char buffer[128];
			struct sockaddr_in temp;
			socklen_t len = sizeof(temp);
			ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(strucy sockaddr*)&temp,&len);
			if(size > 0) //读取成功
			{
				buffer[size] = 0;
				std::cout << buffer << std::endl;
			}
		}
	}
	~UdpClient()
	{
		if(sockfd >= 0)
			close(sockfd);
	}
};

udp_client.cc

#include"udp_client.hpp"

//3个参数:./udp_client   server_ip   server_port
int main(int agec,char * argv[])
{
	if(argc != 3)
	{
		std::cerr << "Usage:" << argv[0] << "server_ip server_port" << std::endl;
		return 1;
	}
	std::string ip = argv[1];
	int port = atoi(argv[2]);  //字符串转为整型

	UdpClient *ucli = new UdpClient(ip,port);
	ucli->InitUdpClient();

	ucli->Start();
	return 0;
}

udp_server.hpp

#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define DEFAULT 8081
class UdpServer
{
private:
	//std::string ip;
	int sockfd;
	int port;
public:
	UdpServer(int _port = DEFAULT):port(_port),socket(-1)//,ip(_ip)
	{}
	//初始化
	bool InitUdpServer()
	{
		sockfd = socket(AF_INET, SOCK_DGRAM, 0);  //AF_INET是利用ipv4,也可以使用其它机制
		if(socket < 0)  //创建套接字失败
		{
			std::cerr << "socket error" << std::endl;  
			return false;
		}
		std::cout << "socket create success,sockfd:" << sockfd << std::endl;  //文件描述符是3,0 1  2 默认分配了,从3开始
		
		//绑定,即:文件和网络关联起来
		//sockaddr_in结构体,是bind()函数里的一个参数,需要填充信息
		//此结构体主要包括:地址类型,端口号,IP地址
		struct sockaddr local;
		memset(&local,'\0'.sizeof(local));
		local.sin_family = AF_INET;
		local.sin_port = htons(port);   //port需要发送到网络中,需要设置为网络序列
		//local.sin_addr.s.addr = inet_addr(ip.c_str());  //整数ip转换成4字节ip,inet_addr为C语言接口,则调用使用ip.cstr()(C++风格的转换成C语言字符串以'\0'结尾的)
		local.sin_addr.s_addr = INADDR_ANY;  //绑定0
		
		if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
		{
			std::cerr << "bind error" << std::endl;
			return false;
		}
		std::cout << "bind success" << std::endl;
		return true;
	}
	//启动
	void Start
	{
#define SIZE 128  //定义的缓冲区大小
		char buffer[SIZE] = 0;
		//dup2(sockfd,1);  //重定向
		
		for(;;)
		{
			//读取数据
			struct sockaddr_in peer;
			socklen_t len = sizeof(peer);
			ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
			if(size > 0)  //读取成功
			{
				buffer[size] = 0;
				int _port = ntohs(peer.sin_port);  //网络序列转成主机序列
				std::string _ip = inet_ntoa(peer.sin_addr);  //网络字节IP转换成字符串IP
				std::cout << _ip << ":" << port << "#" << buffer << std::endl;
				
				//其余想做的任务
				std::string cmd = buffer;
				std::string result;
				if(cmd == "ls")
				{
					int pipes[2];
					pipe(pipes);
					pid_t id = fork();
					if(id == 0)
					{
						//child
						close(pipes[0]);
						dup2(pipes[1],1);
						execl("/usr/bin/ls","ls","-a","-l","-i",nullptr);
						exit(-1);
					}
					close(pipes[1]);  //父进程关闭写
					char c;
					while(1)
					{
						if(read(pipes[0],&c,1) > 0)
						{
							result.push_back(c);
						}
						else
						{
							break;
						}
					}
					wait(nullptr);
				}
				std::string echo_msg;
				if(result.empty())
				{
					echo_msg += buffer;
					echo_msg = "server get!";
				}
				else
				{
					echo_msg = result;
				}



				//服务器返回内容
				//std::string echo_msg = "server get!";
				//echo_msg += buffer;
				sendto(sockfd,echo_msg.c_str(),echo_msg.size(),0,(struct sockaddr*)&peer,len);
			}
			else
			{
				std::cerr << "recvfrom error" << std::endl;
			}
		}
	}
	~UdpServer()
	{
		if(sockfd >= 0)
			close(sockfd);
	}	
};

udp_server.cc

#include"udp_server,hpp"
 
 //udp_server ip port  
int main(int argc, char *argv[])
{
	if*(argc != 2)
	{
		std::cerr << "Usage:" << argv[0] << "port" << std::endl;
		return 1;
	}
	std::string ip = "127.0.0.1";  //127.0.0.1是本地环回ip,标识本主机
	int port = argv[1];
	UdpServer *svr = new UdpServer(ip,port);
	svr->InitUdpServer();

	svr->Start();  //服务器启动
	return 0;
} 

udp_server.cc外网访问

#include"udp_server,hpp"
 
 //udp_server ip port  
 //INADDR_ANY->0
int main(int argc, char *argv[])
{
	if*(argc != 2)
	{
		std::cerr << "Usage:" << argv[0] << "port" << std::endl;
		return 1;
	}
	//std::string ip = "127.0.0.1";  //127.0.0.1是本地环回ip,标识本主机
	int port = argv[1];
	//UdpServer *svr = new UdpServer(ip,port);
	UdpServer *svr = new UdpServer(port);
	svr->InitUdpServer();

	svr->Start();  //服务器启动
	return 0;
} 

注意:
(1)netstat:查看当前网络状态
参数:-nlup

参数 作用
n 能显示成数字就显示成数字
l list
tp tcp
up udp
(2)端口号通常是16位,IPV4通常是32比特位的数据
(3)服务器需要绑定,客户端不需要绑定
Ⅰ.服务器是为了给别人提供服务,别人需要知道ip和端口,端口要总所周知,绑定后不能轻易改变
Ⅱ.客户端不绑定,因为客户端访问server,端口号只要是唯一的即可,需要和特定的client进程强相关,client端口可以动态设置
Ⅲ.client需要port,sendto类似的接口,client直接在OS层面会自动给client获取一个唯一的端口
Ⅳ.云服务器的ip,是由对应的云厂商提供的,这个ip不能直接被绑定(直接绑定需要虚拟机或自定义安装的Linux),如果需要bind,需要让外网访问,需要bind 0(INADDR_ANY为0,即服务器可以接受来自任何client的请求)

网络编程套接字——UDP_第2张图片

Ⅳ.云服务器的ip,是由对应的云厂商提供的,这个ip不能直接被绑定,如果需要bind,需要让外网访问,需要bind 0,INADDR_ANY为0

你可能感兴趣的:(计算机网络,网络,udp,网络协议)