网络编程-基本概念及UDP

1.Linux下的网络编程

(1)目的

不同主机,进程间通信。

(2)解决的问题

1. 主机与主机之间物理层面必须互联互通。
2. 进程与进程在软件层面必须互联互通。

(3) 网络协议

网络通信标准。
     
    OSI七层模型:开放系统互联模型(open system interconnect)
    理论模型:
    不同体系结构设备间,网络通信的通信标准。

    应用层:要传输的数据信息,如文件传输,电子邮件等
    表示层:数据加密,解密操作,压缩,解压缩
    会话层:建立数据传输通道,   ---》会话
    传输层:传输的方式  UDP  TCP   端口号
    网络层:实现数据路由,路径规划    路由器  ip
    数据链路层:封装成帧,点对点通信(局域网内通信),差错检测   交换机  ARP
    物理层:定义物理设备标准、电气特性,比如网线,光纤等传输介质   比特流  bit  0 1
     
TCP/IP模型:应用模型

五层:
     

1. 应用层

概念:

应用层是网络体系中最高的一层,直接为用户的应用程序(如浏览器、电子邮件客户端、文件传输程序)提供网络服务。它定义了数据格式和通信规则,以便不同主机上的应用进程能够交换信息。

主要协议及用法:

HTTP:

概念:超文本传输协议,是万维网的数据通信基础。

用法:客户端(浏览器)向服务器发送请求,服务器响应后返回网页、图片、视频等资源。是一种明文传输的协议。

HTTPS:

概念:安全的超文本传输协议,在HTTP的基础上加入了SSL/TLS加密层。

用法:用于需要安全传输的场景,如网上支付、登录认证等。浏览器地址栏会显示锁形标志。

FTP:

概念:文件传输协议,用于在网络上进行双向文件传输。

用法:用户通过FTP客户端连接到FTP服务器,可以上传、下载、删除、重命名文件。需要建立控制连接(TCP 21端口)和数据连接(TCP 20端口)。

TFTP:

概念:简单文件传输协议,是一个轻量级的FTP。

用法:常用于局域网内或设备(如路由器、交换机)的固件升级和配置备份/恢复。它使用UDP 69端口,没有复杂的认证和交互命令,速度快但不可靠。

MQTT:

概念:消息队列遥测传输协议,是一种轻量级、基于发布/订阅模式的物联网通信协议。

用法:适用于网络带宽低、设备资源有限的场景。例如,智能家居中的传感器将数据(如温度)发布到一个主题(Topic),服务器或其他设备订阅该主题即可接收信息。

DNS:

概念:域名系统,是互联网的“电话簿”。

用法:将人类可读的域名(如 www.baidu.com)解析为机器可读的IP地址(如 180.101.49.12)。当你在浏览器输入网址时,第一步就是发起DNS查询。

2. 传输层

概念:

传输层负责为两台主机上的应用程序提供端到端的通信服务。它通过端口号来标识不同的应用程序,从而实现数据流的定向(哪个数据包是给哪个程序的)。

主要协议及用法:

TCP:

概念:传输控制协议,是一种面向连接的、可靠的、基于字节流的协议。

用法:可靠性:通过三次握手建立连接、确认机制、重传机制、流量控制和拥塞控制来保证数据不丢失、不重复、按序到达。

适用场景:要求数据完整性的应用,如网页浏览(HTTP)、文件传输(FTP)、电子邮件(SMTP)。

UDP:

概念:用户数据报协议,是一种无连接的、不可靠的协议。

用法:高效性:发送数据前不需要建立连接,没有TCP的那些复杂机制,因此延迟更低、速度更快。

适用场景:对实时性要求高、可容忍少量数据丢失的应用,如视频会议、语音通话、在线直播(QUIC/HTTP3)、DNS查询、TFTP。

3. 网络层

概念:

网络层负责将数据包从源主机经过多个网络(路由选择)传输到目标主机。它通过IP地址来标识网络中的设备,实现主机到主机的通信。

主要协议及用法:

IP协议:

概念:网际协议,是网络层最核心的协议,定义了数据包(称为IP数据报)的结构和寻址方法。

IPv4:

用法:使用32位地址(如 192.168.1.1),地址空间即将耗尽。是当前互联网的主流协议。

IPv6:

用法:使用128位地址(如 2001:0db8:85a3::8a2e:0370:7334),提供了几乎无限的地址空间,同时提供了更好的安全性和效率,是未来的发展趋势。

ICMP:

概念:互联网控制消息协议,用于网络设备之间发送控制消息(如错误报告、查询信息)。

用法:常用的 ping 命令和 tracert(Windows)/ traceroute(Linux)命令就是基于ICMP协议,用来测试网络连通性和诊断网络故障。

4. 数据链路层

概念:

数据链路层负责在同一个局域网内,通过MAC地址(物理地址)在设备之间传输和校验数据帧。它负责将网络层交下来的IP数据包组装成帧,并在相邻网络节点(如交换机、网卡)之间透明地传输。

主要协议及用法:

ARP:

概念:地址解析协议。

用法:在局域网内,通过目标设备的IP地址来查询其MAC地址。例如,计算机A想发给计算机B,它只知道B的IP,就会在局域网内广播一个ARP请求:“谁的IP是B的IP?请告诉A你的MAC地址”。B收到后会单播回复自己的MAC地址。A随后将B的IP和MAC对应关系缓存到本地的ARP表中。

PPP:点对点协议,常用于宽带拨号上网(ADSL)。

Ethernet:以太网协议,是当前最主流的局域网技术。

5. 物理层

概念:

物理层是整个网络体系的基础,它规定了为传输数据所需要的物理和电气特性。它负责在物理介质上透明地传输比特流(即0和1的信号),不关心数据的含义和结构。

eg:

如果把整个网络通信比作寄送快递:

应用层:就是你写的信的内容。

传输层:将信装入信封,写上收件人和寄件人的姓名(端口号),并选择快递服务(如顺丰-可靠/TCP,或普通信件-不可靠/UDP)。

网络层:在信封上写上收件人和寄件人的家庭地址(IP地址)。

数据链路层:负责在同一个小区或街道内,根据门牌号(MAC地址) 将信件投递到下一个中转站(如交换机、路由器)。

物理层:就是运送信件的卡车、公路、铁路和飞机,以及规定信件包装箱的材质、尺寸。它只负责把“箱子”从一个地方运到另一个地方,不关心箱子里装的是什么。


四层:应用层、传输层、网络层、网络接口层

(4)IP协议

    网络层:
        IP协议

        192.168.1.128
        IPv4    32位
        IPv6    128位
       eg: 
        192.168.1.140         (用户表示形式)   点分十进制   
        11000000 10101000 00000000 01000011    (计算机存储形式) 32bits
        
        IP地址 = 网络位 + 主机位 
        eg:
        192.168.0.121/24
        24:网络位的位数
        
        网络位:该IP地址位于哪个网段(局域网)内
        主机位:这个网段(局域网)第几台主机

        子网掩码:
            如:255.255.255.0
            11111111.11111111.11111111.00000000
            用来区分IP地址的网络位和主机位,搭配IP地址使用。
            子网掩码是1的部分对应IP地址的网络位
            子网掩码是0的部分对应IP地址的主机位

        192.168.1.0
        网段号:
            IP地址网络位不变,主机位全为0,则为该IP地址的网段号
            192.168.1.3
            255.255.0.0
            192.168.0.0
            位于
            192.168.1.0 网段内(网段内的IP能直接通信)
        
                    192.168.1.3
                    255.0.0.0

        广播号:
            192.168.1.255
            IP地址网络位不变,主机位全为1,则为该IP地址的广播号
            192.168.1.3
            255.255.255.0
            广播号:
            192.168.1.255(向广播号发送信息,所有局域网内IP都能收到此信息)
           eg: 
            feiQ VNC
            192.168.1.255
        网关地址:
            192.168.1.1

IP地址的划分:
        (1)A类地址:

                范围:1.0.0.0 - 126.255.255.255
                子网掩码:255.0.0.0          126*2^24        
                用于管理大规模网络
                
                私有IP地址:10.0.0.0 - 10.255.255.255
            
                127.0.0.0   回环地址
        (2)B类地址:
                范围:128.0.0.0 - 191.255.255.255
                子网掩码:255.255.0.0         2^16
                管理大中规模网络
                
                私有IP地址:172.16.0.0 - 172.31.255.255
            
        (3)C类地址:
                范围:192.0.0.0 - 223.255.255.255
                子网掩码:255.255.255.0        2^8
                管理中小规模网络
                
                私有IP地址:192.168.0.0 - 192.168.255.255
                
        (4)D类地址:
                224.0.0.0 - 239.255.255.255
                组播和广播使用
                
        (5)E类地址:
                240.0.0.0 - 255.255.255.254
                用来进行实验

        公有IP:由电信公司直接分配,并需要付费的IP地址, 可以直接访问internet
        私有IP:不能直接访问internet的ip地址
                
        节省ip地址

(5)网络端口号

    端口号:16位的整形数据(unsigned short)0-65535
    端口号功能:标记同一主机上的不同网络进程

    分类:
        1)任何TCP/IP实现所提供的服务都用1-1023之间的端口号。
            http : 80
            FTP: 20/21
            TFPT: 69
            HTTPS: 443
                   
        2)端口号从1024-49151是被注册的端口号,被IANA指定为特殊服务使用。
                MQTT:1883/8883
        3)从49152-65535是动态或私有端口号。

(6)网络配置

1.  ping  ip地址/域名
        查看当前主机和IP/域名所对应的这台主机网络是否联通
        ping www.baidu.com

2. ifconfig 
       在Linux查看当前主机的IP地址
       ipconfig
       在Windows上查看当前主机的IP地址

3. 网络配置
       1)虚拟机--》设置--》网络适配器---》桥接模式
       2)编辑--》虚拟网络编辑器--》更改设置--》VMnet0---》桥接至--》当前PC正在上网的网卡上--》应用--》确定
       3)修改网络配置文件
             sudo vim /etc/network/interfaces

             auto lo
             iface lo inet loopback

             auto ens33 
             iface ens33 inet dhcp

        4)重启网络服务
             sudo /etc/init.d/networking restart
        5) 测试
              ping www.baidu.com

2.网络协议--UDP

(1)基本概念

UDP:传输层
              用户数据报协议(User Datagram Protocol)

     1)网络编程模型
           B/S模型:browser/server(浏览器/服务器)
                    1. 客户端是通用的客户端(浏览器)
                    2. 一般只做服务器开发
                    3. 客户端要加载的数据均来自服务器
           C/S模型:client/server(客户端/服务端)
                    1. 客户端是一个专用的客户端
                    2. 服务器和客户端都需开发
                    3. 客户端可保存资源,本地加载,无需所有数据都请求服务器

     2)UDP编程流程

           套接字:文件描述符
                          网络通信时,应用层可操作的端口。

(2)函数接口

int socket(int domain, int type, int protocol);
      功能:创建通信的套接字
      参数:
              domain:网络层使用什么协议族
                      AF_INET:IPv4
                      AF_INET6:IPv6
             type:规定传输层的协议
                      SOCK_DGRAM : UDP协议
                      SOCK_STREAM:TCP协议
                      SOCK_RAW :原始套接字
             protocol :0 按照默认协议方式创建
      返回值:
                 成功:套接字
                 失败:-1

       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

       功能:向网络套接字发送数据
       参数:
                 sockfd:套接字
                 buf: 要发送的数据的首地址
                 len:要发送的字节数
                 flags: 0:按照默认方式发送
                 dest_addr:接收方的地址信息(IP+端口号)
                 addrlen:接收方地址的大小
       返回值:
                 成功:实际发送的字节数
                 失败:-1

man 7 ip  ------》查询此形参          const struct sockaddr *dest_addr,
           struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr sin_addr;   /* internet address */
           };

           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };

int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

功能:绑定自己的IP地址和端口号
参数:
         sockfd:套接字
         addr:需要绑定的地址
         addrlen:地址大小
返回值:
      成功:0
      失败:-1


ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

功能:从套接字上接收数据
参数:
         sockfd:套接字
         buf:存放接收数据的内存首地址
         len:希望接收的字节数
         flags:0 :按照默认方式接收(阻塞)
         src_addr:发送方的地址信息
         addrlen:发送发地址的指针
返回值:
       成功:实际接收到的字节数
       失败:-1

网络字节序:大端               
主机字节序:小端

uint32_t htonl(uint32_t hostlong);          主机转网络
uint16_t htons(uint16_t hostshort);         主机转网络
uint32_t ntohl(uint32_t netlong);           网络转主机
uint16_t ntohs(uint16_t netshort);          网络转主机

 in_addr_t inet_addr(const char *cp);
功能:
      将字符串IP地址转换成二进制IP地址形式

 char *inet_ntoa(struct in_addr in);
功能:
    将二进制ip转换成字符串

eg:客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>


int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);
    seraddr.sin_addr.s_addr = inet_addr("192.168.0.181");



    while (1)
    {
        char buff[1024] = {0};
        fgets(buff, sizeof(buff), stdin);
        buff[strlen(buff) - 1] = '\0';

        ssize_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
        if(cnt < 0)
        {
            perror("send error");
            return -1;
        }
        
        printf("cnt = %ld\n", cnt);
    }

    

    close(sockfd);

    return 0;
}

eg:服务端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>


int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    struct sockaddr_in seraddr;
    seraddr.sin_addr.s_addr = inet_addr("192.168.0.181");
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);

    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if(ret < 0)
    {
        perror("bind error");
        return -1;
    }

    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);


    while (1)
    {
        char buff[1024] = {0};
        int cnt = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&cliaddr, &clilen);
        if(cnt < 0)
        {
            perror("recvform error");
            return -1;
        }
        printf("[%s:%d]cnt = %d, buff = %s\n",  inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port),cnt, buff);
    }
    
    close(sockfd);

    return 0;
}

(3)UDP的特点

① 面向数据包

② 无需建立连接

③ 尽最大努力交付,不安全、不可靠(数据丢包、乱序)

④ 可现实一对一、一对多的传输

⑤ 机制简单,资源开销小,数据实时性高

如何避免UDP丢包:

①发送方以较慢的速度发送数据,让接收方有足够的时间处理数据

②模仿TCP的机制:应答机制

(4)例题

①使用UDP实现全双工聊天

#include "head.h"

#define SER_PORT 50000
#define SER_IP   "192.168.0.179"


struct sockaddr_in seraddr;

int init_udp_cli()
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);

	return sockfd;
}



void *send_msg(void *arg)
{
	char buff[1024] = {0};
	int sockfd = *((int *)arg);

	while (1)
	{
		fgets(buff, sizeof(buff), stdin);
		ssize_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
		if (cnt < 0)
		{
			perror("sendto error");
			break;
		}
	}
	
	return NULL;
}

void *recv_msg(void *arg)
{
	char buff[1024] = {0};
	int sockfd = *((int *)arg);

	while (1)
	{
		memset(buff, 0, sizeof(buff));
		ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
		if (cnt < 0)
		{
			perror("recvfrom error");
			break;
		}
		printf("B-->A : %s\n", buff);
	}

	return NULL;
}


int main(int argc, const char *argv[])
{
	pthread_t tid[2];

	int sockfd = init_udp_cli();
	if (sockfd < 0)
	{
		return -1;
	}
	
	sendto(sockfd, "hello", 5, 0, (struct sockaddr *)&seraddr, sizeof(seraddr));


	pthread_create(&tid[0], NULL, send_msg, &sockfd);
	pthread_create(&tid[1], NULL, recv_msg, &sockfd);

	
	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	
	close(sockfd);

	return 0;
}
#include "head.h"


#define SER_PORT 50000
#define SER_IP "192.168.0.179"


struct sockaddr_in cliaddr;
socklen_t clilen;

int init_udp_ser()
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);

	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}
	
	return sockfd;
}


void *send_msg(void *arg)
{
	
	char buff[1024] = {0};
	int sockfd = *((int *)arg);

	while (1)
	{
		fgets(buff, sizeof(buff), stdin);
		size_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&cliaddr, clilen);
		if (cnt < 0)
		{
			perror("sendto error");
			break;
		}
	}
	return NULL;
}

void *recv_msg(void *arg)
{
	char buff[1024] = {0};
	int sockfd = *((int *)arg);

	while (1)
	{
		ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
		if (cnt < 0)
		{
			perror("recvfrom error");
			break;
		}
		printf("A--->B: %s\n", buff);
	}

	return NULL;
}


int main(int argc, const char *argv[])
{
	pthread_t tid[2];
	char buff[1024] = {0};

	clilen = sizeof(cliaddr);

	int sockfd = init_udp_ser();
	if (sockfd < 0)
	{
		return -1;
	}
	
	recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&cliaddr, &clilen);
	
	pthread_create(&tid[0], NULL, send_msg, &sockfd);
	pthread_create(&tid[1], NULL, recv_msg, &sockfd);

	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	return 0;
}

②使用UDP实现图片的传输

#include "head.h"

#define SER_PORT 50000
#define SER_IP   "192.168.0.179"


struct sockaddr_in seraddr;

int init_udp_cli()
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);

	return sockfd;
}


int send_file(int sockfd, const char *filename)
{
	char buff[1024] = {9};
	int fd = open(filename, O_RDONLY);
	if (fd < 0)
	{
		perror("open file error");
		return -1;
	}
	
	off_t len = lseek(fd, 0, SEEK_END);
	lseek(fd, 0, SEEK_SET);
	printf("len = %ld\n", len);

	sendto(sockfd, &len, sizeof(len), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
	
	while (1)
	{
		size_t cnt = read(fd, buff, sizeof(buff));
		if (cnt <= 0)
		{
			break;
		}
		sendto(sockfd, buff, cnt, 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
		usleep(1);
	}
	
	return 0;
}



int main(int argc, const char *argv[])
{
	if (argc < 2)
	{
		printf("Usage : ./a.out <sendfile>\n");
		return -1;
	}

	pthread_t tid[2];
	int sockfd = init_udp_cli();
	if (sockfd < 0)
	{
		return -1;
	}
	
	
	send_file(sockfd, argv[1]);
	
	close(sockfd);

	return 0;
}
#include "head.h"


#define SER_PORT 50000
#define SER_IP "192.168.0.179"



int init_udp_ser()
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);

	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}
	
	return sockfd;
}


int recv_file(int sockfd, const char *filename)
{
	char buff[1024] = {0};
	int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
	if (fd < 0)
	{
		perror("open file error");
		return -1;
	}
	off_t len = 0;
	recvfrom(sockfd, &len, sizeof(len), 0, NULL, NULL);
	printf("len = %ld\n", len);


	while (1)
	{
		ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
		write(fd, buff, cnt);
		len -= cnt;
		if (0 == len)
		{
			break;
		}
	}

	return 0;
}


int main(int argc, const char *argv[])
{

	if (argc < 2)
	{
		printf("Usage : ./a.out <recvfile>\n");
		return -1;
	}

	int sockfd = init_udp_ser();
	if (sockfd < 0)
	{
		return -1;
	}
	
	recv_file(sockfd, argv[1]);


	close(sockfd);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值