网络编程:基本概念udp
1. OSI 模型
OSI 模型 ===》开放系统互联模型 ==》分为7层:
理想模型 ==》尚未实现
tftp
b /etc/passwd
a /etc/123
应用层
表示层 加密解密 gzip
会话层 网络断开,连接状态,keep-close keep-alive
传输层tcp udp 协议 文件 视频,音频
网路层ip NAT
链路层 交换机 数据的格式化 帧 校验
物理层 100Mb/8 Gbits 100MB 同轴电缆 10Gb 2.4G 5G
从下往上,下最基础
2. TCP/IP模型
TCP/IP模型 ==》网际互联模型 ==》分为4层:
实用模型 ===》工业标准
tcp/ip协议栈
应用层 ====》应用程序
传输层 ====》端口号tcp udp(使用哪种方式传输,tcp可靠,udp实时性高)
网络层 ====》IP 地址(如何找到对方、主机)
接口层 ====》网卡 驱动 1GB
pcap ,,,
3. TCP/IP协议族
www.taobao.com ---> 192.168.0.19
www.voa.com vpn
DNS 域名解析(一般以省为单位)
DHCP(动态主机配置协议)
应用层: HTTP(网页) TFTP(简单近距离传输文件) FTP(互联网远距离传输)
SNMP(管理和监视网络设备、系统和应用程序) DNS ...
传输层: TCP UDP 56k猫
网络层: IP ICMP(ping) RIP OSPF IGMP ...
接口层: ARP(地址解析协议) RARP ... ip--->mac
arp,,,,
192.160.0.112
一、 网络基础
网络基础 ===》A B C D E 类
010 3333344444
IP地址 == 网络位 + 主机位
IP地址的分类: 点分十进制 ipv4 712934
A类: 超大规模性网络
8 8 8 8
1.0.0.0 - 126.255.255.255 126.1.1.1
网络号 126.1.1.2
255.0.0.0 (子网掩码)
私有:
10.0.0.0 - 10.255.255.255
127.0.0.1
B类: 大中规模型网络
128.0.0.0 - 191.255.255.255
128.2.1.2 128.2.7.2
255.255.0.0
私有:
172.16.0.0 - 172.31.255.255
C类: 中小规模型网络
192.0.0.0 - 223.255.255.255
255.255.255.0
私有:
192.168.0.0 - 192.168.255.255
静态路由
192.168.0.0
192.168.0.1 网关gate
192.168.0.255 (发广播,所有局域网的人都能收到)
D类: 组播和广播
224.0.0.0 - 239.255.255.255(群聊发,不面向所有人,不限局域网)
192.168.0.255 == 255.255.255.255(只能是局域网,一人发,所有人收)
235.1.2.3
192.168.1.0
192.168.0.1 网关
192.168.1.255 广播
E类: 实验
240.0.0.0 - 255.255.255.255
C 类网络:
ip地址的前三组是网络地址,第四组是主机地址。
二进制的最高位必须是: 110xxxxx开头
十进制表示范围: 192.0.0.0 -223.255.255.255
默认网络掩码: 255.255.255.0
网络个数: 2^24 个 约 209 万个
主机个数: 2^8 个 254 个+2 ===》1 是网关 1是广播
私有地址: 192.168.x.x 局域网地址。
sudo vim /etc/network/interfaces "配置网络"
sudo /etc/init.d/networking restart
sudo reboot
192.168.0.0
192.168.0.1 route
192.168.0.255 boardcast
801.n.g
单机上网的配置:
1、有网络接口并插入网线。
2、有ip地址
3、配置网络设置
ip(修改临时ip): ifconfig ethX X.X.X.X/24 up ifconfig ens33 192.168.0.13/24(子网掩码) up 255.255.255.0
网关: route add default gw x.x.x.x
DNS: vi /etc/resolv.conf ==>nameserver 8.8.8.8
测试:ping www.baidu.com
netstat -anp(可以查看计算机的网络状态)
二、网络接口
1. socket 套接字(文件描述符——关联网络设备) ==》BSD socket ==》用于网络通信的一组接口函数。socket api application interface函数接口
2. ip+port 地址+端口===》地址用来识别主机
端口用来识别应用程序
port 分为 TCP port / UDP port 范围都是: 1-65535
约定1000 以内的端口为系统使用。
http 80 www.baidu.com
3306
telnet 21
ssh 22
三、网络字节序
大端存储(低位在高内存——网络设备)
小端 0x12345678(低位在低内存——主机)
12 00
00 12
192.168.0.12(大端)“最高位 存储在 最前面”
12.0.168.192(小端)
四、UDP(用户数据报)
1、特性: 无链接 不可靠 大数据
2、框架: C/S模式
server:socket() ===>bind()===>recvfrom()===>close()
client:socket() ===>bind()===>sendto() ===>close()
注意:socket()的参数需要调整。
socket(PF_INET,SOCK_DGRAM,0);
bind() 客户端(c)是可选的,服务器端(s)是必选的。
1.socket
int socket(int domain, int type, int protocol);
功能:程序向内核提出创建一个基于内存的套接字描述符
参数:domain 地址族,PF_INET == AF_INET ==>互联网程序
PF_UNIX == AF_UNIX ==>单机程序
type 套接字类型:
SOCK_STREAM 流式套接字 ===》TCP
SOCK_DGRAM 用户数据报套接字===>UDP
SOCK_RAW 原始套接字 ===》IP
protocol 协议 ==》0 表示自动适应应用层协议。
返回值:成功 返回申请的套接字id
失败 -1;
2.bind
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
功能:如果该函数在服务器端调用,则表示将 参数1相关的文件描述符文件 与参数2指定的接口地址关联,用于从该接口接受数据。
如果该函数在客户端调用,则表示要将 数据从参数1所在的描述符中 取出 并从 参数2所在的接口设备上 发送出去。
注意:如果是客户端,则该函数可以省略,由默认接口发送数据。
参数:sockfd 之前通过socket函数创建的文件描述符,套接字id
my_addr 是物理接口的结构体指针。表示该接口的信息
struct sockaddr 通用地址结构
{
u_short sa_family; 地址族
char sa_data[14]; 地址信息
};
转换成网络地址结构如下:
struct _sockaddr_in ///网络地址结构
{
u_short sin_family; 地址族
u_short sin_port; ///地址端口
struct in_addr sin_addr; ///地址IP
char sin_zero[8]; 占位
};
struct in_addr
{
in_addr_t s_addr;
}
socklen_t addrlen: 参数2 的长度。
返回值:成功 0
失败 -1;
3.发送接收函数:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:用于UDP协议中向对方发送数据。
参数:sockfd 本地的套接字id
buff 本地的数据存储,一般是要发送的数据
len 要发送的数据长度
flags 要发送数据方式,0 表示阻塞发送
dest_addr: 必选,表示要发送到的目标主机信息结构体
addrlen :目标地址长度
返回值:成功 发送的数据长度
失败 -1;
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:用于UDP协议中获取对方发送的数据。
参数:sockfd 本地的套接字id
buff 要存储数据的内存区,一般是数组或者动态内存
len 要获取的数据长度,一般是buff的大小
flags 获取方式,0 阻塞
src_addr 可选,表示对方的地址信息结构体,如果为NULL,表示不关心对方地址
addrlen 对方地址信息结构体大小,如果对方地址是NULL,则该值也为NULL
返回值:成功 接收到的数据长度
失败 -1;
4.关闭
close() ===>关闭指定的套接字id;
注意
1.数据与数据有边界
2.收发次数要对应
3.recvfrom 会阻塞
4.sento 不会阻塞(recvfrom不收,不影响sento发)
c找s
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h> typedef struct sockaddr *SA; int main(int argc, char *argv[])
{ int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建一个UDP套接字 if(-1 == sockfd) // 检查socket调用是否失败 { perror("socket"); // 打印错误信息 exit(1); // 退出程序 } // 定义服务器地址和客户端地址结构体(尽管客户端地址通常在recvfrom中填充) struct sockaddr_in ser, cli; bzero(&ser, sizeof(ser)); // 初始化服务器地址结构体 bzero(&cli, sizeof(cli)); // 初始化客户端地址结构体(但这里实际上不需要提前初始化) // 设置服务器地址结构体 ser.sin_family = AF_INET; ser.sin_port = htons(50000); // 端口号转换为网络字节序 ser.sin_addr.s_addr = inet_addr("192.168.203.128"); // 服务器IP地址 // 将套接字绑定到服务器地址和端口 int ret = bind(sockfd, (SA)&ser, sizeof(ser)); if(-1 == ret) { perror("bind"); // 绑定失败时打印错误信息 exit(1); // 退出程序 } socklen_t len = sizeof(cli); // 用于recvfrom的客户端地址长度 // 无限循环接收数据并发送响应 while(1) { char buf[512] = {0}; // 准备接收数据的缓冲区 // 接收数据,注意这里&len是必需的,因为recvfrom会修改它以反映实际接收到的客户端地址长度 recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len); time_t tm; time(&tm); // 获取当前时间 // 这里有一个问题:buf被用于接收数据,然后又被用于存储新的字符串(包括原始数据和时间戳) // 这会导致原始数据被覆盖。应该使用另一个缓冲区来存储最终的发送数据 sprintf(buf, "%s %s", buf, ctime(&tm)); // 将时间戳附加到接收到的数据上 // 发送数据回客户端,但注意len在这里已经被recvfrom修改,表示客户端地址的长度 // 对于sendto,我们应该使用sizeof(cli)或重新初始化len sendto(sockfd, buf, strlen(buf), 0, (SA)&cli, len); // 这里使用len可能不是最佳实践 } // 注意:由于程序进入了一个无限循环,下面的close和return语句实际上永远不会被执行 close(sockfd); // 正常情况下应该关闭套接字 return 0; // 程序正常结束
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h> // 定义一个类型别名SA,指向sockaddr结构体
typedef struct sockaddr *SA;
// 注意:它尝试定义一个函数指针类型而不是简单的结构体指针。 int main(int argc, char *argv[])
{ // 创建一个UDP套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(-1 == sockfd) { perror("socket"); // 如果创建套接字失败,打印错误信息 exit(1); // 退出程序 } // 初始化sockaddr_in结构体,用于指定服务器地址和端口 struct sockaddr_in ser; bzero(&ser, sizeof(ser)); // 将结构体内存清零 ser.sin_family = AF_INET; // 使用IPv4地址 // 将端口号从主机字节序转换为网络字节序 ser.sin_port = htons(50000); // 将点分十进制的IP地址字符串转换为网络字节序的整数 ser.sin_addr.s_addr = inet_addr("192.168.203.128"); // 无限循环,发送数据并尝试接收响应 while(1) { char buf[512] = "hello,this is udp test"; // 准备发送的数据 // 发送数据到指定的服务器地址和端口 sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser)); // 清空缓冲区,准备接收数据 bzero(buf, sizeof(buf)); // 尝试接收数据,但源地址和端口被设置为NULL,这通常是不正确的 // 在实际应用中,应该提供一个sockaddr_in结构体来接收源地址和端口信息 recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL); // 打印接收到的数据(但在这个例子中,由于recvfrom的源地址和端口被设置为NULL,它可能不会按预期工作) printf("buf is %s\n", buf); // 等待1秒后再发送下一个数据包 sleep(1); } // 注意:由于程序进入了一个无限循环,下面的close和return语句实际上永远不会被执行 close(sockfd); // 关闭套接字 return 0; // 程序正常结束
}