文章目录
IP与端口号
- IP
每台主机都有自己的IP地址,所以当数据从一台主机传输到另一台主机就需要IP地址。报头中就会包含源IP和目的IP
源IP地址:发送数据报那个主机的IP地址,目的IP地址:想发送到的那个主机的IP地址
我们把数据从一台主机传递到另一台主机不是真正目的,真正通信的不是这两个机器,其实是这两台机器上面的软件
应用层不止一个软件。公网IP标识了一台唯一的主机,那么数据就可以由一台主机传递到另一台主机。但是有这么多的软件(进程),怎么保证软件A发送的被软件B接收呢?也就是说用什么来标识主机上客户或者服务进程的唯一性呢?
为了更好的表示一台主机上服务进程的唯一性,用端口号port标识服务进程、客户端进程的唯一性。
- 端口号
由上面可以知道:
标识一个进程有pid,那么为什么还需要端口号port呢?
一个端口号只能被一个进程占用,但是一个进程可以绑定多个端口号
底层OS如何根据port找到指定的进程——uint16(端口号)——task_struct——哈希
我们在网络通信的过程中,IP+port标识唯一性,IP有源IP和目的IP,port也有源端口号和目的端口号。所以我们在发送数据的时候也要把自己的IP和端口号发送过去,因为数据还要被发送回来。所以发送数据的时候一定会多出一部分数据(以协议的形式呈现)
TCP/UDP协议
我们用的套接字接口一定会使用传输层协议,不会绕过传输层去调用下面的协议。而传输层的协议分为TCP协议和UDP协议
- TCP协议
TCP(Transmission Control Protocol 传输控制协议) 特点:
UDP(User Datagram Protocol 用户数据报协议) 特点:
注意:
网络字节流
我们知道内存中的多字节数据相对于内存地址有大端和小端之分。
现在就出现一种情况:可能一个大端机用大端的方式发送数据到一个小端机。如果现在跨网络我们也不知道数据到底是大端和小端:
如何定义网络数据流的地址:
把数据转化成大端的工作可不需要我们自己来做,那太麻烦了,可以调用库函数做网络字节序和主机字节序的转换即可:
#include <arpa/inet.h>
// 主机序列转网络序列
uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);
// 网络序列转主机序列
uint16_t ntohs(uint16_t netshort);
uint32_t ntohl(uint32_t netlong);
如果是其他数据类型呢?不用担心,未来网络发送的时候所有数据都是字符串;发送数据时使用的原生接口对于字符串信息自动转化
socket套接字接口
- socket常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听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的结构体类型,这个我们需要了解一下👇
- sockaddr的结构体
套接字种类是比较多的。常见的有三种:
网络套接字主要运用于跨主机之间的通信,也能支持本地通信,而域间套接字只能在本地通信。而原始套接字可以跨过传输层(TCP/IP协议)访问底层的数据。这些套接字应用场景完全不同,所以我们想用就得用三套不同的接口。而为了方便,设计者只设计了一套接口,就可以通过不同的参数,解决所有网络或者其他场景下的通信问题。
这里举两个具体的套接字类型:sockaddr_in(inet,网络通信)
与sockaddr_un(unix,域间套接)
struct sockaddr_in {
short int sin_family; // 地址族,一般为AF_INET
unsigned short int sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IP地址
unsigned char sin_zero[8]; // 用于填充,使sizeof(sockaddr_in)等于16
};
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* 带有路径的文件名 */
};//通过同一个文件的路径来让进程看到同一份资源