1.socket
1.1 ip & port
1.2 字节序
大端字节序 & 小端字节序
主机字节序 & 网络字节序
1.3 tcp与udp协议的特点
1.4 udp编程流程
1.5 tcp编程流程
单执行流的编程流程
多进行的编程流程
多线程的编程流程
字节序
1.字节序:CPU对内存的访问顺序
小端字节序:低位放低地址
大端字节序:地位放高地址
**主机字节序:**指的是机器本身的字节序,如果是大端,则主机字节序就是大端,如果是小端,主机字节序就是小端
**网络字节序:**规定网络当中传输的字节序使用打断
意味着:如果是小端机器在传输数据的时候,需要将数据转化成为大端字节序进行传输,对端机器默认传输过来的数据是大端字节序的
**tips:**通常字节序和计算机的架构有关系,最常见的X_86体系架构是小端机器
问题:怎么验证自己的机器是大端机器还是小端机器?
union Data
{
int a;
char b;
};
a = 1;
if(1 == b){
//小端
}
if(1 != b)
{
//大端
}
结论:
1.不管是网络报头部分的IP或者port,还有要传输的真实源数据,都是需要进行转化成为网络字节序来传输的
2. 当通信双方都是小端机器的时候,对于网络报头当中的IP和port还是必须遵守网络字节序的格式进行传输,否则网络链路上面的转发设备就无法正确的转发该条数据
3. 因为双方都是小端机器,我们传输数据可以不用进行字节序的转换
4.当前95%的机器都是小端机器
主机字节序如何转换成为网络字节序:
ip, port
ip: uint32_t
uint32_t htonl(uint32_t hostlong);
port:uint16_t
uint16_t htons(uint16_t hostshort);
网络字节序如何转换成为主机字节序:
ip, port
ip:
uint32_t ntohl(uint32_t netlong);
port:
uint16_t ntohs(uint16_t netshort);
TCP与UDP的区别
TCP协议:面向连接,可靠传输,面向字节流
面向连接:TCP通信双方在发送给数据之前,需要先建立连接,才能够发送数据
可靠传输:TCP保证传输的数据是可靠有序的到达对端
面向字节流:
1.对于传输的数据之间是没有明显的数据边界的
2.对于接收方而言,在可以接收数据的情况下,可以接收任意字节的数据
UDP协议:无连接,不可靠,面向数据报
无连接:UDP通信双方在发送数据之前,是不需要进行沟通的,客户端只需要直到服务端的IP和端口,就直接可以发送数据了
不可靠:不保证数据是可靠达到对端的,并且不保证数据是按序到达
面向数据报: UDP对于应用层和传输层数据递交的时候,都是整条数据交付的
创建套接字:
1.接口
int socket(int domain, int type, int protocol);
domin:地址域
指定网络层到底使用什么协议
AF_INET:ipv4版本的ip协议
AF_INET6:ipv6版本的协议
type:套接字类型
SOCK_DGRAM:用户数据报套接字
SOCK STREAM:流式套接字
指定传输层使用什么协议
protocol:协议
SOCK_DGRAM:
1. 指定为0,表示采用默认协议,默认为UDP协议
2. IPPROTO_UDP(17)
SOCK STREAM:
1.指定为0,表示采用默认协议,默认为TCP协议
2. IPPROTO_UDP(6)
eg:
UDP: socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
TCP: socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
2.返回值
返回一个套接字操作句柄,本质上是一个文件描述符
返回值大于等于0,则为创建成功
返回值小于0,则为创建失败
ll /proc/[pid]/fd
创建套接字的意义:
前提:一台设备要进行网络通信,一定是需要有一个网卡设备来发送和接收数据的
ether 00:0c:29:b2:ae:6a
每一个网卡设备都是有一个MAC地址,称之为物理地址
每一块网卡设备当中的MAC地址一定是唯一的
物理地址占用6个字节
将进程和网卡进行绑定,进程可以从网卡当中接收数据,也可以通过网卡进行发送数据
绑定地址信息:
1.绑定IP和端口
服务器既然想要提供某些服务,必须让客户端知道,怎么讲数据传输给服务端,而网络当中传输数据的始胡,是按照IP地址和port来进行传输
细分:
在整个网络链路当中的传输,ip地址起到了作用
当数据到达目标主机之后,是根据端口号来识别到底是哪个进程的数据
2.接口
int bind (int sockfd, const struct sockaddr* addr, socklen_t addrlen);
sockfd:套接字描述符(socket函数的返回值)
addr:告诉操作系统内核,当前进程要绑定的地址信息是啥
addrlen:绑定的地址信息长度是多少
struct scckaddr
{
sa_family_t sa_family;//地址域, (AF_INET, AF_INT6)2字节
char sa_data[14];//14字节
}
总体占用16个字节
注意:struct sockaddr 这个结构体是一个通用的地址信息结构,并不是某一个具体的地址信息结构
3.IPV4版本的IP协议的地址信息结构
struct sockaddr_in //16字节
{
_SOCKADDR_COMMON(SIN_);//地址域 占2个字节(AF_INET, AF)_INET6)
//typedef uint16_t in_port_t;
in_port_t sin_port; /* Port number */ //占用2个字节
struct in_addr sin_addr; /*Internet address */ //占用4字节
/*Pad to size of struct sockaddr*/
unsigned char sin_zero[sizeof(struct sockaddr) ,SOCKADDR_COMMON_SIZE, sizeof (in_port_t), sizeof(struct in_addr)];
};
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
结论:
1.当我们使用IPV4版本的IP协议,写网络程序,绑定地址信息的时候,需要填充struct sockaddr_in结构体来保存服务器的IP和侦听的端口
2.在使用bind函数的时候,需要将struct sockaddr_in 结构体强转为struct sockaddr结构体
3.对于struct sockaddr_in和struct sockadd这两个结构体而言,前面两个字节都表示地址域,当我们强转struct sockaddr_in结构体传入内核之后,内核时通过前面两个字节(地址域),来确定后续的空间如何进行读取
4.struct sockaddr结构体是地址信息通用结构体
AF_UNIX:本机上面的进程减通信
5.好处:bind函数不需要针对特定的协议,准备不同类型的参数,只需要准备一个通用的地址信息结构即可
addrlen:地址信息长度
sizeof(struct sockaddr_in)//16
sizeof(struct sockaddr_un)//110
——————————————网络通信前期的工作就完成了———————————————
UDP-socket数据收发
sendto
recvfrom
UDP通信代码
in_addr_t inet_addr(const char* cp);
cp:类型char*,接收字符串
返回值:uint32_t ==> 返回的整数是已经从主机字节序转换成为网络字节序的
验证:
1.sockfd是不是文件描述符?
ll /proc/[pid]/fd
2.是否绑定成功端口了?
netstat -anp ==> 可以查看当前操作系统端口占用情况
udp 172.16.99.129:19999 0.0.0.0:*
当前19999端口是被udp协议所占用
当前服务端侦听ip和端口
表示能够接收客户端ip地址和端口
0.0.0.0:表示任意IP
//*表示任意端口
bind:Address already in use
想绑定的端口,已经被其他进程所绑定了,绑定失败了
1.UDP发送接口
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:消息接收方的地址信息结构,(要将数据发送到哪里去)
addrlen:地址信息长度
2.接收接口
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
sockfd:套接字描述符
buf:缓冲区,接收回来的数据存放的缓冲区
read(fd, buf, sizeof(buf) - 1)
len: 最大接收能力
flags: 0 阻塞接收
src_addr:消息发送方的地址信息结构
本质上是一个出参,recvfrom函数进行填充数据结构
作用:可以知道发送方的地址信息结构,便于回复应答
addrlen:输入输出型参数
1.一方面:告诉recvfrom函数,我们准备的地址信息结构的长度
2.二方面:recvfrom函数告诉我们发送方具体的地址信息长度
3.关闭套接字
int close(int fd)
1.客户端真实的绑定的ip和端口
0.0.0.0 :34088
0.0.0.0 :绑定本机的任意一块网卡
2.服务端拿到的IP和端口
65535 FFFFFFFF ==> 65535
3.对比是否一致
不一致,sendto
客户端:创建套接字,发送数据,接收数据,关闭套接字
服务端:创建套接字,绑定地址信息,接收数据,发送数据,关闭套接字
udp.hpp =>head-only风格,定义和实现在一起
创建套接字,接收,发送,关闭,绑定
cli.cpp
=> 包含udp.hpp
svr.cpp
=> 包含udp.hpp