粉丝不过w
2.1 字节序,地址转换
2.1.1字节序概述
字节序概念:
多字节数据的存储顺序
分类:
小端格式:低位字节数据存储在低地址
大端格式:高位字节数据存储在低地址
注意:
LSB:低地址
MSB:高地址
确定主机字节序程序:
#include <stdio.h>
int main(int argc, char *argv[])
{
union
{
short s;
char c[sizeof(short)];
}un;
un.s = 0x0102;
if((un.c[0] == 1) && (un.c[1] == 2))
{
printf("un.s = %x H\n", un.s);
printf("un.c[0] = %d\n",un.c[0]);
printf("un.c[1] = %d\n",un.c[1]);
printf("big-endian\n");
}
else if((un.c[0] == 2) &&(un.c[1] == 1))
{
printf("un.s = %x H\n", un.s);
printf("un.c[0] = %d\n",un.c[0]);
printf("un.c[1] = %d\n",un.c[1]);
printf("little-endian\n");
}
return 0;
}
特点:
- 网络协议指定通讯字节序——大端
- 多字节数据处理时考虑字节序
- 运行在主机的进程互相通信,不用考虑字节序
- 异构计算机通信,需要转换字节序为网络字节序
2.1.2 htonl函数
/*
*功能:
* 将32位主机字节序数据转换成网络字节序数据
*参数:
* hostint32:待转换的32位主机字节序数据
*返回值:
* 成功:返回网络字节序的值
*/
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32);
2.1.3 htons函数
/*
*功能:
* 将16位主机字节序数据转换网络字节序数据
*参数:
* uint16_t:unsigned short int
* hostint16:待转换16位主机字节序数据
*返回值:
* 成功:返回网络字节序的值
*/
#include <arpa/inet.h>
uint16_t htons(uint16_t hostint16);
2.1.4 ntohl函数
/*
*功能:
* 将32位网络字节序数据转换为主机字节序数据
*参数:
* uint32_t:usigned int
* netint32_t:待转换32位网络字节序数据
*返回值:
* 成功:返回主机字节序的值
*/
#include <arpa/inet.h>
uint32_t ntohl(uint32_t netint32);
2.1.5 ntohs函数
/*
*功能:
* 将16位网络字节序数据转换主机字节序数据
*参数:
* uint16_t:unsigned short int
* netint16:待转换的16位网络字节序数据
*返回值:
* 成功:返回主机字节序的值
*/
#include <arpa/inet.h>
uint16_t ntohs(uint16_t netint16);
示例:
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int a = 0x01020304;
short int b = 0x0102;
printf("htonl(0x%08x) = 0x%08x \n",a,htonl(a));
printf("htons(0x%04x) = 0x%04x \n",b,htons(b));
return 0;
}
结果:
2.1.6 地址转换函数
2.1.6.1 inet_pton函数
字符串IP地址转整型数据
/*
*功能:
* 将点分十进制数串转换32位无符号整型
*参数:
* family :协议族
* strptr:点分十进制数串
* addrptr:32位无符号整型的地址
*返回值:
* 成功: 1
* 失败: 其他
*/
#include <arpa/inet.h>
int inet_pton(int family,const char *strptr,void *addrptr);
例:
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
char ip_str[] = "172.20.226.11";
unsigned int ip_uint = 0;
unsigned char *ip_p = NULL;
inet_pton(AF_INET,ip_str,&ip_uint);
printf("ip_uint = %d\n",ip_uint);
ip_p = (unsigned char *)&ip_uint;
printf("ip_uint = %d.%d.%d.%d\n",*ip_p ,*(ip_p + 1) ,*(ip_p +2) ,*(ip_p + 3));
return 0;
}
2.1.6.2 inet_ntop函数
整型数据转换字符串格式IP地址
/*
*功能:
* 将 32 位无符号整数转换成点分十进制数串
*参数:
* family: 协议族
* addrptr: 32 位无符号整数
* strptr: 点分十进制数串
* len: strptr 缓冲区长度
* len 的宏定义
* #define INET_ADDRSTRLEN 16 //for ipv4
* #define INET_ADDRSTRLEN 46 //for ipv6
*return:
* 成功:字符串的首地址
* 失败:NULL
*/
#include <arpa/inet.h>
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[] = {172,20,223,75};
char ip_str[16] = "NULL";
inet_ntop(AF_INET,(unsigned int *)ip,ip_str,16);
printf("ip_str = %s\n",ip_str);
return 0;
}
2.2 UDP介绍 编程流程
2.2.1 UDP概述
UDP协议
面向无连接的用户数据报协议,在传输数据前不用建立连接,目的主机的传输层收到UDP报文,不用做任何确认
UDP特点
- 比TCP速度快
- 简单请求/应答应用程序用UDP
- 海量数据传输不用UDP
- 广播和多播应用用UDP
UDP应用
DNS(域名解析),NFS(网络文件系统),RTP(流媒体)
2.2.2 网络编程接口socket
网络通信要解决的是不同主机进程间的通信
- 网络间进程标识问题
- 多重协议的识别问题
socket应用
提供不同主机的进程间通信
socket特点
- socket 为套接字
- 文件描述符,代表一个通信通道的一个端点
- read, write,close函数对套接字进行网络数据的收取和发送等操作
2.2.3 UDP编程 C/S架构
2.3UDP编程-创建套接字
2.3.1 创建socket套接字
/*
*function:
* 创建用于网络通信的socket套接字(描述符)
*parameter:
* family: 协议族 (AF_INET AF_INET6 PACKET)
* type: 套接字类 (SOCK_STREAM SOCK_DGRAM SOCK_RAM)
* protocol: 协议类别 (0 IPPROTO_TCP IPPROTO_UDP)
*return:
* 套接字
*note:
* 创建套接字,系统不分配端口
* 创建套接字默认属性为主动,主动发起服务的请求;当服务器,修改为被动
*/
#include <sys/socket.h>
int socket(int family,int type,int protocol);
2.3.2 创建UDP套接字demo
/*
*note:
* AF_INET: IPv4协议
* SOCK_DGRAM: 数据报套接字
* 0: family 和 type 组合的系统默认值
*/
int sockfd = 0;
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
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字节
};
2.4.2 通用套接字地址结构
为了不同格式地址传入套接字,地址强制转换为通用套接字地址格式
/*
*note:
* 以上3个结构在Linux系统中已定义
*/
#include <netinet/in.h>
struct sockaddr
{
sa_family_t sa_family; //2字节
char sa_data[14]; //14字节
};
2.4.3 俩种地址结构使用场合
定义源地址和目的地址结构时,用struct sockaddr_in
例:
struct sockaddr_in my_addr;
函数传入地址结构要用 struct sockaddr 进行强制转换
例:
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
2.4.4 发送数据——sendto函数
/*
*function:
* 向to结构体指针指IP,发送UDP数据
*parameter:
* sockfd: 套接字
* buf: 发送数据缓冲区
* nbytes: 发送数据缓冲区的大小
* flags: 0
* to: 指向目的主机地址结构体的指针
* addrlen:to指向的内容长度
*note:
* 通过to和addrlen确定目的地址
* 可发送0长度的UDP数据包
*return:
* 成功:发送数据的字符数
* 失败: -1
*/
ssize_t sendto(int sockfd,
const void *buf,
size_t nbytes,
int flags,
const struct sockaddr *to,
socklen_t addrlen);
2.4.5 向“网络调试助手”发送消息
/*
*与网络调试助手配合使用
* send data to UDP server 192.168.43.76:8080!
* sb
* cpucode
* ^C
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
unsigned short port = 8080;
char *server_ip = "192.168.43.76";
int sockfd;
struct sockaddr_in dest_addr;
/* 指定server信息 */
if(argc > 1) //服务器ip地址
{
server_ip = argv[1];
}
if(argc > 2) //服务器端口
{
port = atoi(argv[2]);
}
sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建UDP套接字
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
/* 填充目的server的信息 */
bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(port);
inet_pton(AF_INET,server_ip,&dest_addr.sin_addr);
printf("send data to UDP server %s:%d!\n",server_ip,port);
while(1) //发送数据到server
{
char send_buf[512] = " ";
fgets(send_buf,sizeof(send_buf),stdin); //获取输入
send_buf[strlen(send_buf)-1] = '\0';
sendto(sockfd,
send_buf,
strlen(send_buf),
0,
(struct sockaddr *)&dest_addr,
sizeof(dest_addr));
}
close(sockfd);
return 0;
}
2.4.6 绑定bind函数
UDP网络收取数据的条件:
确定IP地址
确定port
怎么完成条件:
接收端:
bind函数,完成地址结构与 socket套接字的绑定,IP和port就固定
发送端:
sendto函数,指定接收端的IP,port,发数据
/*
*function:
* 本地协议地址与sockfd绑定
*parameter:
* sockfd: socket套接字
* myaddr: 特定协议的地址结构指针
* addrlen:地址结构的长度
*return:
* 成功:返回 0
* 失败:其他
*/
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
2.4.7 bind示例
/*
*note:
* INADDR_ANY 通配地址 为 0
*/
#include <sys/socket.h>
int err_log = 0;
unsigned short port = 8000;
struct sockaddr_in my_addr;
bzero(&my_addr,sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
err_log =bind(sockfd,(struct sockaddr *)&my_addr,sizeof(my_addr));
if(err_log != 0)
{
perror("bind");
close(sockfd);
exit(-1);
}
2.4.8 接收数据—recvfrom函数
/*
*function:
* 接收UDP数据,把源地址信息保存在form指向的结构中
*note:
* 通过from和addrlen参数存放数据来源信息 from和addrlen可以为NULL,表示不保存数据来源
*return:
* 成功:接收的字符数
* 失败:-1
*/
ssize_t recvfrom(int sockfd, //套接字
void *buf, //接收数据缓冲区
size_t nbytes, //接收数据缓冲区的大小
int flags, //套接字标志
struct sockaddr *from, //源地址结构体指针,保存数据
socklen_t *addrlen); //from指向内容的长度
2.4.9 接收“网络调试助手”的数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
unsigned short port = 8080;
int sockfd;
struct sockaddr_in my_addr;
int err_log;
if(argc > 1) //修改本程序的端口
{
port = atoi(argv[1]);
}
sockfd =socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0) //创建套接字
{
perror("socket");
exit(-1);
}
//填充本程序的信息
bzero(&my_addr,sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
printf("Binding server to port %d\n",port);
err_log = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if(err_log != 0) //绑定本程序要使用的信息
{
perror("bind");
close(sockfd);
exit(-1);
}
printf("receive data ...\n");
while(1) //收取数据
{
int recv_len;
char recv_buf[512] = " ";
struct sockaddr_in client_addr;
char cli_ip[INET_ADDRSTRLEN] = " ";
socklen_t cliaddr_len = sizeof(client_addr);
recv_len = recvfrom(sockfd,
recv_buf,
sizeof(recv_buf),
0,
(struct sockaddr *)&client_addr,
&cliaddr_len);
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip,INET_ADDRSTRLEN);
printf("\nip:%s,port:%d\n",cli_ip ,ntohs(client_addr.sin_port));
printf("data(%d):%s\n",recv_len,recv_buf);
}
close(sockfd);
return 0;
}
2.5 UDP编程-client,server
2.5.1 C/S架构回顾
2.5.2 UDP客户端注意点
- 本地IP:本地端口
- 目的IP:目的端口
- 客户端,只设置目的IP,目的端口
bzero(&dest_addr, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080);
inet_pton(AF_INET,"172.20.223.75",&dest_addr.sin_addr);
客户端的本地IP,本地port是调用sendto的时候linux底层自动给客户端分配
分配端口是随机,每次运行的port都不一样
2.5.3 UDP服务器注意点
- 服务器要bind固定,是因为本地port要固定,不能随机
- 服务器可以主动给客户端发数据
- 客户端可以用bind,客户端的本地端口就固定,一般不这样做