概念回顾
MAC地址仅需要在同一个局域网下唯一,就可以保证不会出现通讯问题。
通信的目的是两台机器上的应用软件要通信。即客户端进程和服务端进程要获取这个数据,借助主机来完成通信。故将数据在主机间转发仅仅是手段,机器收到后,需要将数据交付给指定的进程!(即两台机器通信是为了完成进程间通信)。主机如何将数据交给指定进程呢?通过端口号(标定主机上唯一的网络进程)!端口号是传输层协议的内容。
IP和端口号标识公网唯一进程
源IP和目的IP
在IP数据包头部中,有两个IP地址,分别叫做源IP地址和目的IP地址。但是我们光有IP地址还不可以完成通信,还需要有一个其他的标识来区分出–端口号。
端口号
端口号是一个2字节16位的整数;端口号用来标识唯一一个公网内的进程,能告诉操作系统当前的这个数据要交给哪一个进程来处理。
IP地址 + 端口号能够标识全网内的某一台主机的某一个进程。
一个端口号只能被一个进程占用。一个进程可以绑定多个端口号,但是一个端口号只能被一个进程绑定。
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述 “数据是谁发的, 要发给谁”
任何一个发出的报文必须有源/目标IP和源/目标port,用于找到指定主机和该主机上的唯一进程。
IP+port==>套接字。故网络通信也称为socket套接字编程。socket API是一层抽象的网络编程接口。
pid和端口号的区别和联系
pid+端口号可以标识同一台主机里进程的唯一性。进程pid和端口号分开设计是为了实现解耦,再者不是所有的进程都要进行网络通信,只有网络进程需要端口号。
传输层协议
TCP协议-安全
Transmission Control Protocol 传输控制协议
特点:有连接;可靠传输;面向字节流。可靠是需要大量数据编码处理和技术支持的。
UDP协议-简单
User Datagram Protocol 用户数据报协议
特点:无连接;不可靠传输;面向数据报。可以容忍数据丢包的场景采用UDP,简单方便使用。
网络字节序
小端:高地址存放高位。
多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据。如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略, 直接发送即可。
网络字节序和主机字节序的转换函数
#include <arpa/inet.h>
//将主机字节序转网络字节序
uint32_t htonl(uint32_t hostlong);//host to newwork long uint16_t htons(uint16_t hostshort);
//将网络字节序转成主机字节序
uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);
网络编程
网络套接字接口标准是基于POSIX标准的。常见的套接字有域间socket(本主机内的进程间通信方式,struct sockeraddr_un)、原始socket(用于各种编写网络工具,允许数据传输跳过传输层/网络层)、网络socket(struct sockeraddr_in)。
虽然套接字编程有以上3种不同的应用场景,但是在系统中只设计了一套接口。网络socket的地址格式是struct sockeraddr_in,域间socket的地址格式是struct sockeraddr_un,但是在这套网络编程的接口参数中,只接收struct socketaddr
这个格式,底层是系统帮我们做了转换【先读取前2个字节,如果地址类型是AF_INET就转换成sockeraddr_in,地址类型是AF_UNIX就转换成sockeraddr_un】。
通用类型的套接字结构,就是struct socketaddr
。【有点像void*的作用】
socket常见接口
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
两个结构
sockaddr结构
为了网络套接字和域间套接字共同使用,相当于一个基类。
struct sockaddr {sa_family_t sa_family; //通过判断16位地址类型AF_INET->使用网络套接字,AF_UNIX->使用域间套接字char sa_data[14];//14字节,包含套接字中的目标地址和端口信息
}
虽然socket api常见接口里的参数之一是sockaddr*,但我们真正在基于IPv4编程时,使用的数据结构是sockaddr_in
; 这个结构里主要有三部分信息:地址类型,端口号,IP地址。
sockaddr_in结构
struct sockaddr_in {short sin_family; // 2 字节 ,地址族,e.g. AF_INET, AF_INET6unsigned short sin_port; // 2 字节 ,16位TCP/UDP 端口号 e.g. htons(3490),struct in_addr sin_addr; // 4 字节 ,32位IP地址char sin_zero[8]; // 8 字节 ,不使用
};
in_addr用来表示一个IPv4的IP地址,其实就是一个32位的整数。
typedef uint32_t in_addr_t;
struct in_addr{int_addr_t s_addr;
}
人: “192.168.1.3” 方便看和理解的是点分十进制字符串风格的IP地址,每个区域的取值是[0,255]即一个字节,故IP地址是4字节,32位。对于系统来说,网络只需要4字节来表示IP就足够了。
故我们还需要进行转换(点分十进制 <–> 4字节)。
联系和区别
二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16
位端口号和32位IP地址。IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6。只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
socket API可以都用struct sockaddr *类型表示,在使用的时候需要强制转化成sockaddr_in; 这样的好
处是程序的通用性,可以接收IPv4,IPv6,以及UNIX Domain Socket各种类型的sockaddr结构体指针做为
参数。
2、虽然sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:
程序员不应操作sockaddr,sockaddr是给操作系统用的;程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。
一般的用法为:
程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数。