目录
【字节序】
1.大小端的概述和判断
字节序是多字节看成一个整体的时候存储的顺序,是由系统决定的
字节序分为:大端存储和小端存储
小端存储:数据的低字节位存储于低地址位
大端存储:数据的高字节位存储与低地址位
判断系统的大小端
因为每一个系统到底是大端存储还是小端存储是未知的,所以我们需要进行判断。思路就是通过判断一个数据通过一个字节大小的指针来读取到的数据是什么
#include <stdio.h>
int main(int argc, char const *argv[])
{
int num = 0x00000001;
char* data = (char*)#
if( *data )
{
printf("小端存储\n");
}
else
{
printf("大端存储\n");
}
return 0;
}
【网络字节序和主机字节序】
1.异构计算机传输存在的问题
假如一个主机想要通过网络和另一台主机进行通讯,那么需要先把数据发送到网络上,网络上的字节序又称为网络字节序,默认为大端存储。假如主机A是大端存储,主机B是小端存储,那么主机B接收到的数据就全部是反过来的了
2.网络字节序和主机字节序冲突解决
网络字节序为大端存储,那么发送数据的时候,就需要将发送的数据转为大端存储的格式,接受的时候,把数据由网络字节序的大端存储转化为相应主机字节序
也就是发送的时候,主机字节序转为网络字节序,接受的时候,网络字节序转为主机字节序
【字节序转换函数】
因为主机字节序可能和网络字节序不同,所以发送和接受都需要进行字节序的转换,使用如下函数进行此操作
1.主机字节序转网络字节序(发送数据)
#include <arpa/inet.h>
//IP地址使用
uint32_t htonl(uint32_t hostlong);
//端口号使用
uint16_t htons(uint16_t hostshort);
功能:
将IP地址由主机字节序转为网络字节序
将端口号由主机字节序转为网络字节序
参数
IP地址
端口号
返回值
转化后的字节序
2.网络字节序转主机字节序
#include <arpa/inet.h>
//IP地址使用
uint32_t ntohl(uint32_t netlong);
//端口使用
uint16_t ntohs(uint16_t netshort);
功能:
将IP地址由网络字节序转为主机字节序
将端口号由网络字节序转为主机字节序
参数
IP地址
端口号
返回值
转化后的字节序
【地址转换函数】
我们平常看到的网络IP地址,比如说192.569.4.6这种叫做点分十进制位,计算机是不会识别的,计算机识别的是一个无符号的32位地址,点分十进制位只是为了让我们看起来更加的直观。
1.将点分十进制转化为32位无符号整型
#include <arpa/inet.h>
int inet_pton(int family, const char *src, void *dst);
功能
将一个点分十进制位转为计算机可识别的无符号三十二位整型
参数
family:地址族
AF_INET:IPv4 常用
AF_INET6:IPv6
src:点分十进制串
dst:32位无符号整型地址
返回值
成功:1
失败:其他
1.1将点分十进制转为计算机识别的IP
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
//定义一个32位无符号整型来接受
unsigned int ip;
char my_ip[] = "192.166.4.6";
inet_pton(AF_INET, my_ip, &ip);
printf("转化前的点分十进制位为 %s ,转化后的32位无符号为 %u ",my_ip,ip);
return 0;
}
2.将32位无符号整型转化为点分十进制
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
功能
将32位无符号整型转为点分十进制
参数
af:地址族
src:32位无符号数
dst:点分十进制数串
size:点分十进制串长度(一般使用IPv4 也就是16)
#define INET_ADDRSTRLEN 16 //ipv4
#define INET6_ADDRSTRLEN46 //ipv6
返回值
成功:字符串首地址
失败:NULL
2.1计算机识别IP向点分十进制的转换
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
//定义一个32位无符号数据
//使用一个连续的无符号字符表示,更直观
unsigned char add[4] = {192,58,4,3};
//接受点分十进制串
char data[17] = "";
inet_ntop(AF_INET,add,data,16);
printf("转化后的点分十进制串为:%s",data);
return 0;
}
【UDP编程概述】
1.socket
1.socket也称套接字
2.socket是一个文件描述符,代表了通信的一端
3.类似对文件的操作一样,可以使用类似read、write、close等函数对socket套接字进行网络数据的收取和发送等操作
4.使用socke()得到套接字
个人理解认为,socket就是两个主机之间想要通过网络进行通信的桥梁,就是打开了两个跨网络进程之间的通道。两边均存在socket,互相通信
2.UDP编程流程
3.创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
在创建套接字的时候,系统是不会分配端口的,如果没有通过bind绑定端口,那么发送的时候系统会随机分配端口给这个套接字
功能
创建一个套接字用于网络通信
参数
domain:地址族 AF_INET、AF_INET6、PF_PACKET等
type:套接字类 SOCK_STREAM、SOCK_DGRAM、SOCK_RAw等
SOCK_STREAM:TCP
SOCK_DGRAM:UDP
protocol:协议类别 0、IPPROTO_TCP、IPPROTO_UDP等
返回值
套接字
4.sendto发送UDP信息
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能
向dest_addr结构体中指明IP和端口的进程发送信息
参数
sockfd:套接字
buf:发送的数据
len:发送数据的大小
flags:一般为0
dest_addr:目的主机地址结构体
addrlen:dest_addr指向内容的大小
返回值
成功:发送数据的字节数
失败:-1
4.1IPv4地址结构体
#include <netinet/in.h>
struct sockaddr_in {
short int sin_family; // 地址族,一般为AF_INET IPv4
unsigned short int sin_port; // 端口号
struct in_addr sin_addr; // IP
unsigned char sin_zero[8]; // 使sizeof(sockaddr_in)为16
};
struct in_addr {
unsigned long s_addr; // IP
};
4.2通用套接字地址结构
sendto的形参中dest_addr的类型是struct sockaddr,这个类型是一个通用类型,其目的是为了同时兼容IPv4以及IPv6,所以为什么协议族要放在前两个字节并且IPv4结构体还需要后面填充8个字节,都是为了兼容这个结构体。
struct sockaddr {
sa_family_t sin_family; //地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
IPv4和IPv6的结构体前两个字节均为地址族,后面十四个字节,IPv4代表端口、IP、以及填充,IPv6则代表自身数据
4.3使用UDP发送信息
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//创建套接字
//形参 IPv4 套接字类选择UDP 协议类别
int socked_fd = socket(AF_INET,SOCK_DGRAM,0);
if( socked_fd < 0 )
{
perror("socket");
}
printf("socket = %d \n",socked_fd);
//发送的数据
char data[] = "hello!!!!!!!!!!!!!!!!!!!!!!";
//创建地址结构体
struct sockaddr_in sockadd;
//清零
memset(&sockadd,0,sizeof(sockadd));
//给地址结构体赋值
//需要把主机字节序转化为网络字节序也就是大端存储的格式,因为网络端都是大端存储
sockadd.sin_family = AF_INET; //IPv4
sockadd.sin_port = htons(8080); //端口
//将点分十进制位转化为32位无符号整型在转化为网络字节序
// unsigned int addr;
// inet_pton(AF_INET,"192.168.x.xxx",&addr);
// sockadd.sin_addr.s_addr = htonl(addr);
inet_pton(AF_INET,"192.168.x.xxx",&sockadd.sin_addr.s_addr);
//发送
int status = sendto(socked_fd,data,strlen(data),0,(struct sockaddr*)&sockadd,sizeof(sockadd));
printf("发送字节 = %d \n",status);
//关闭
close(socked_fd);
return 0;
}
5.bind给套接字绑定固定IP和端口
如果没有使用bind对套接字的IP以及端口进行绑定,那么默认使用本机IP以及随机的端口,但是服务器就必须绑定,因为服务器是为了让客户端连接的,没有端口号的话,客户端无法准确的连接进去
给套接字绑定的步骤为:
1.创建套接字
2.定义地址结构体并赋值(IP以及端口)
3.bind绑定
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能
给一个套接字绑定端口和IP
参数
sockfd:套接字
addr:地址结构体指针
addrlen:地址结构体指针指向空间大小
返回值
成功:0
失败:其他
5.1套接字使用bind绑定
//给套接字绑定端口和IP
//创建目标地址结构体
struct sockaddr_in my_sockadd;
memset(&my_sockadd,0,sizeof(my_sockadd));
my_sockadd.sin_family = AF_INET; //IPv4
my_sockadd.sin_port = htons(1145); //端口
//即使不绑定本机ID,在发送的时候也会自动绑定本机ID,只是端口是随机的
//inet_pton(AF_INET,"192.168.x.xxx",&my_sockadd.sin_addr.s_addr); //本机ID
int bind_res = bind( socked_fd , (struct sockaddr*)&my_sockadd , sizeof(my_sockadd) );
if( 0 == bind_res )
printf("绑定成功\n");
6.recvfrom接受数据(阻塞)
recvfrom只负责接受数据,但是必须绑定端口和IP,否则不知道目标IP以及端口,无法对其进行发送
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能
从其他主机接受数据,并将发送主机的信息存放于src_addr当中
参数
sockfd:套接字
buf:接收数据缓冲区
len:缓冲区大小
flags:套接字标志,一般为0
src_addr:用来保存发送数据的主机IP 端口等信息的结构体指针
addrlen:接收到的长度,注意此形参为指针
注意
如果src_addr以及addrlen设置为NULL,则不保存发送主机信息
返回值
成功:接收到的字符数
失败:-1
6.1使用recvfrom接受数据
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//创建套接字
//形参 IPv4 套接字类选择UDP 协议类别
int socked_fd = socket(AF_INET,SOCK_DGRAM,0);
if( socked_fd < 0 )
{
perror("socket");
}
printf("socket = %d \n",socked_fd);
//给套接字绑定端口和IP
//创建当前主机的地址结构体以用来接受
struct sockaddr_in my_sockadd;
memset(&my_sockadd,0,sizeof(my_sockadd));
my_sockadd.sin_family = AF_INET; //IPv4
my_sockadd.sin_port = htons(1145); //端口
inet_pton(AF_INET,"192.168.x.xxx",&my_sockadd.sin_addr.s_addr); //本机ID
int bind_res = bind( socked_fd , (struct sockaddr*)&my_sockadd , sizeof(my_sockadd) );
if( 0 == bind_res )
printf("绑定成功\n");
//接受
//接受缓冲区,网络中使用无符号数据类型
unsigned char buf[1500] = "";
//接受发送方的地址结构结构体
struct sockaddr_in rec_sockadd;
//接受发送长度
socklen_t len;
printf("等待接受...\n");
int rec_res = recvfrom( socked_fd , buf , sizeof(buf) , 0 , (struct sockaddr*)&rec_sockadd , &len );
//网络层字节序向主机层字节序的转换
char my_ip[16] = "";
//将32位无符号位转为点分十进制位
inet_ntop( AF_INET , &rec_sockadd.sin_addr.s_addr , my_ip , 16 );
printf("接收到的数据为 %s \n",buf);
printf("接收方的IP地址为 %s ,端口为 %hd", my_ip , ntohs(rec_sockadd.sin_port) );
//关闭
close(socked_fd);
return 0;
}