目录
4.2 struct sockaddr_in (在头文件:中)
一、预备知识
1.IP地址
1.1 是什么?
IP地址是在IP协议中, 用来标识网络中不同主机的地址。
1.2 IPv4 & IPv6
- 对于IPv4来说, IP地址是一个4字节, 32位的整数。通常使用 "点分十进制" 的字符串表示IPv4地址, 例如 123.145.67.89 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255;
- 对于IPv6来说, IP地址长度为16字节128位,是IPv4地址长度的4倍。于是IPv4点分十进制格式不再适用,采用十六进制表示。具体表示方式请看:IPv6- 百度百科
1.3源IP和目的IP
- 在IP数据报(在网络层向数据链路层传递数据时封装)的首部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址。
- 源IP地址:发送消息的主机地址。
- 目的IP地址:接收消息的主机地址。
2.端口号
2.1 是什么?
端口号是一个2字节16位的整数,用来标识一个进程。
2.2 pid 表示唯一一个进程; 端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?
- "端口号" 和 "进程pid"没有任何关系。
端口号用于网络通信,而进程pid用于进程管理,网络通信和进程管理是两个毫不相干的模块。之所以不用pid来代替端口号的功能是为了功能解耦,减少系统的耦合度。- 不是所有的进程需要端口号,但是所有的进程都需要PID。
- 一个进程可以有多个端口号; 但一个端口号只能被一个进程占用。
2.3 理解源端口号和目的端口号
- 传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号。 就是在描述 "数据是谁发的, 要发给谁"。
- 源端口号:发送消息的进程的端口号。
- 目的端口号:接收消息的进程的端口号。
3.网络通信
3.1 是什么?
网络通信的本质就是进程间通信。
3.2 为什么?
发送数据的主机由进程发出数据,接收数据的主机也要靠进程处理数据。简化后其实就是一个进程发出数据,另一个进程处理数据。所以说网络通信的本质就是进程间通信。
3.3 网络通信时如何保证IP地址+端口号能找到指定进程?
- 客户端进程给服务端进程发信息
服务端进程一般都是一经启动,避免关闭,所以服务端进程的端口号不会随意改变。所以客户端进程能根据下载软件时得到的IP地址+端口号找到服务端进程。- 服务端进程给客户端进程发信息
软件被用户打开后,客户端进程和端口号被创建,此时客户端进程要先向服务端发信息来获取数据,所以服务端进程就得到了客户端进程的IP地址+端口号,往后服务端进程能根据客户端进程的IP地址+端口号找到客户端进程。(所以我们打开软件后,通常会加载一会)
3.4 OS如何根据端口号找到指定的进程?
底层采用哈希的方式建立了端口号和进程PID或PCB之间的映射关系,当底层拿到端口号时就可以在哈希表中根据端口号找到对应的进程。
3.5 网络通信是双方的
当一台主机发送数据给另一台主机时,发送方除了要发送数据外还要把自己的 IP地址和端口号 发送给接收方,所以接收方能给发送方回数据。所以说通信是双方的。
4.TCP协议简介
- TCP(Transmission Control Protocol 传输控制协议),TCP协议是一种有连接、可靠、面向字节流的传输层通信协议。
- TCP协议是面向连接的,如果两台主机之间想要进行数据传输,那么必须要先建立连接,当连接建立成功后才能进行数据传输。
- TCP协议是可靠的,因为TCP协议注重丢包率, TCP协议花费大量开销解决数据在传输过程中出现的丢包、乱序等问题,保证不丢包。文件传输、电子邮件、网站访问一般用的就是TCP协议。
5.UDP协议简介
- UDP(User Datagram Protocol 用户数据报协议),UDP协议是一种无连接、不可靠、面向数据报的传输层通信协议。
- 使用UDP协议进行通信时无需建立连接,如果两台主机之间想要进行数据传输,那么直接将数据发送给对端主机就行了。
- UDP协议是不可靠的,因为UDP协议注重效率,没有处理在传输数据过程中小概率出现的丢包、乱序等情况。直播一般用的就是UDP协议。
6.网络字节序
6.0 大端字节序和小端字节序
- 大端字节序:是将数据的低位字节放到高地址处,高位字节放到低地址处。
(大端低地址高字节:“大弟高”)
- 小端字节序,是将数据的低位字节放到低地址处,高位字节放到高地址处。
(小端低地址低字节:“小弟弟”)
6.1 是什么?
即网络通信中共同遵守的字节序,规定为 大端字节序(低字节,高地址)。
6.2 为什么?
不同计算机的 字节序不同,如果在网络通信时不加以规定,会出现发送方以大端模式发送数据,接收方以小端模式读取数据的情况。
6.3 怎么定义?
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据。如果当前发送主机是小端, 就需要先将数据转成大端再发送,否则直接发送。
6.4 网络字节序与主机字节序之间的转换函数
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); //将主机字节序(h)转换为(to)网络字节序(n)要转化的数据是长整数(l)。 uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
- 这些函数名很好记:h表示host,n表示network,l表示32位长整数,s表示16位短整数。所以htonl表示:将主机字节序(h)转换为(to)网络字节序(n)要转化的数据是长整数(l)。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回。
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
二、socket
1.什么是socket(套接字)?
IP地址+端口号就是socket(套接字) ,用来标定某主机上的某进程。
2.为什么要有套接字?
有了套接字,不同主机的进程才能在网络中找到彼此,才能进行网络通信(进程间通信的前提是能互相看到彼此),所以说套接字是网络通信的基石。其次有了套接字,开发者才能开发出通用的网络通信接口,用户也能拿着套接字去使用这些接口。
3.套接字的主要类型
- 流套接字(SOCK_STREAM):用于读取TCP协议的数据。
- 数据报套接字(SOCK_DGRAM):用于读取UDP协议的数据。
- 原始套接字(SOCK_RAW):可以从应用层直接绕开传输层,直接去访问底层协议,所以原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接字。
从套接字的类型就可以看出网络通信遵守的传输层协议(TCP、UDP…)
4.套接字的域和地址族
要使用套接字进行通信的有两个前提:1. 套接字有相同的类型 2. 套接字处于相同的域
4.1 什么是套接字域?
域就是套接字的工作范围。用来指定套接字通信中使用的网络介质。
4.2 常见的套接字域
AF_INET / PF_INET --------- 表示套接字工作在互联网地址类型为IPv4
AF_INET6 / PF_INET6 --------- 表示套接字工作在互联网地址类型为IPv6
AF_UNIX / AF_LOCAL --------- 表示套接字工作在Unix系统内。
前缀AF_表示地址族(address family),PF_表示协议族(protocol family)
4.3 什么是地址族?
相同类型套接字地址的集合,用来表示套接字地址类型。
如AF_INET表示是IPv4地址族,即表示套接字地址类型为IPv4 。
拓】网络套接字
- 流套接字(SOCK_STREAM)流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,是因为TCP协议。流套接字只能读取TCP协议的数据。
- 数据报套接字(SOCK_DGRAM)数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP( User DatagramProtocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。数据报套接字只能读取UDP协议的数据。
拓】功能强大的套接字
套接字是非常强大的,虽然文章只介绍了套接字用于网络通信,但其实套接字可以用于几乎任何类型的进程间通信:本地通信、各种类型的网络通信等。
三、socket API
1.socket API是什么?
是提供给程序员(应用层)做网络开发所用的接口,用来实现不同主机中进程的通信。
Socket API(套接字编程接口)实际是 传输层 提供给 应用层 的编程接口,用来实现不同主机中进程的通信:传输层在网络层的基础上提供进程到进程问的逻辑通道,而应用层的进程则利用传输层向另一台主机的某一进程通信。Socket就是应用层与传输层之间的桥梁,使用Socket编程可以开发客户端和服务端应用程序,从而通过网络实现在全球范围内通信。
2.为什么要有Socket API?
socket(套接字)只是网络通信的前提,只有开发出一套通用的网络接口才能实现不同主机中进程的通信。Linux下的这套接口就是Socket API(套接字编程接口)。
3.Socket编程常见API
3.1 socket()
//创建socket_fd(套接字 文件描述符),用于TCP/UDP网络程序中的客户端 + 服务器![]()
3.2 bind()
//让 socket_fd和sockaddr_in绑定, 用于 TCP/UDP 网络程序中的 服务器。![]()
3.3 listen ()
//开始监听socket,用于 TCP 网络程序中的 服务器
3.4 accept()
//接收请求,用于 TCP 网络程序中的 服务器
3.5 connect ()
//建立连接,用于 TCP 网络程序中的 服务器
四、sockaddr
1. 是什么?
struct sockaddr(套接字地址结构体)是Linux用来保存套接字和套接字地址类型(地址族)的结构体。
2. 为什么?
使用Socket API要需要传入套接字,Linux选择用struct sockaddr保存套接字和套接字地址类型(地址族)。在Linux下使用Socket API要传入struct sockaddr*。
3. sockaddr的分类
Linux中只设置了一套Socket API(套接字编程接口),但是有不同类型的套接字(用于IPv4的套接字、用于IPv6的、用于本地通信的) ,所以使用struct sockaddr、struct sockaddr_in 、struct sockaddr_un来区分不同类型的套接字:
sockaddr、sockaddr_in 和 sockaddr_un结构体头部的16个比特位(2字节)都是一样的,这16位是用来区分套接字的地址类型(地址族)的,根据套接字的地址类型(地址族),分别定义为常数AF_INET、AF_INET6……
Socket API都用struct sockaddr *类型传参,将 sockaddr_in 或 sockaddr_un强制类型转换为sockaddr,才能被Socket API识别。对于传进来的参数 sockaddr ,函数通过前两个字节,进行判断是网络通信还是本地通信,知道这个结果后再强制类型转换回 sockaddr_in 和 sockaddr_un结构体。这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket等各种地址类型(地址族)的sockaddr结构体指针做为参数。
4.Linux中sockaddr的声明
4.1struct sockaddr(在头文件:<sys/socket.h>中)
4.2 struct sockaddr_in (在头文件:<netinet/in.h>中)
4.3 struct sockaddr_un (在头文件:<sys/un.h>中)
5.创建并填充struct sockaddr_in
4.0 导入结构体声明所在的头文件。
#include <netinet/in.h>
struct sockaddr_in ,声明在头文件:<netinet/in.h>中。
4.1 定义struct sockaddr_in变量。
struct sockaddr_in local;
- struct sockaddr_in:用来定义Ipv4和Ipv6的套接字结构体,用于网络通信。
- struct sockaddr_un :用来定义UNIX_Domain_Socket(Unix域套接字:用于同一台主机上进程间通信)。用于本地通信。
4.2 初始化结构体
bzero(&local, sizeof(local));
使用bzero() 或 memset()初始化结构体的内存空间为0 。
4.3 设置套接字地址类型 (即地址族)
local.sin_family = AF_INET;
4.4 设置端口号 (保存端口号的变量为:port,是一个2字节16位的整数)
local.sin_port = htons(port);
端口号要被对方获取,也是网络数据的一部分,所以要考虑大小端问题,使用htons函数将主机字节序转为网络字节序。
4.5 设置IP地址 (保存IP地址的变量为:ip,是一个点分十进制字符串)
local.sin_addr.s_addr = inet_addr(ip);
inet_addr()的作用:
1.将点分十进制字符串风格的IP地址 -> 4字节整数
2.ip地址也要考虑大小端:将4字节整数 -> 网络序列
6.使用sockaddr传参
Socket API只接受struct sockaddr *类型传参,所以将 sockaddr_in、 sockaddr_un强制类型转换为sockaddr,才能被Socket API识别。
对于传进来的参数 sockaddr ,函数通过前两个字节,进行判断是网络通信还是本地通信,知道这个结果后再强制类型转换回 sockaddr_in 和 sockaddr_un结构体。这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。