IP地址与端口号
了解了IP地址,在网络中用来表示主机的唯一性;
数据报文由源主机通过网络传输到目标主机后,目的主机拿到这个数据报文要如何处理呢?
我们使用QQ聊天、浏览器浏览网页,是如何获取消息的呢?
QQ和浏览器都是进程,也就是说只有将数据交给进程,我们才能看到这些信息。

在操作系统中存在非常多的进程,拿到的数据报文要交给哪一个进程呢?
IP地址用来标识主机的唯一性,有了IP地址才能够找到唯一的主机;那也就势必要存在用来标识主机(操作系统)中唯一进程的标识 --------- 端口号
而在操作系统中存在非常多的进程,并不是每一个进程都要从网络中获取信息,那也就是说不是每一个进程都要有端口号。
在操作系统中要从网络中获取信息的进程才拥有唯一的端口号;
也就是说,端口号可以标识主机(操作系统)中唯一的网络进程。
端口号
- 端口号是一个
2字节,16bit位的整数;- 端口号用来标识一个进程;
IP地址 + 端口号就能够标识网络上的某一台主机的某一个进程;- 一个端口号只能够被一个进程使用。
端口号范围
0-1023:知名端口号,HTTP、FTP、SSH等广为使用的应用层协议,它们端口号都是固定的;
1024-65535:操作系统动态分配的端口号;客户端程序的端口号,就是由操作系统从该范围内分配的。
端口号与进程ID
学习过操作系统,我们知道,在操作系统中pid可以用来标识唯一进程;这里的端口号也可以用来标识操作系统中的唯一进程;
进程
ID属于系统的概念,也可以用来标识唯一进程;但是并没有使用进程ID来作为这里标识唯一进程的标识符;(如果使用进程ID作为这里的标识符,会让系统进程管理和网络强耦合)
此外:一个进程可以绑定多个端口号,一个端口只能绑定一个进程
源IP / 端口号和目的IP / 端口号
传输层协议(TCP和UDP)的数据段中存在两个概念:源IP/端口号、目的IP/端口号;
简单来说源IP/端口号表示的是谁发的、目的IP/端口号表示发给谁的。
在TCP/UDP编程中会遇到这两个概念。
socket 编程基础
1. socket套接字
了解了IP 和端口号,我们知道IP 可以标识网络中的唯一主机;端口号可以表示主机中的唯一进程。
所以,IP+端口号就可以标识网络中的唯一进程。
所以,只要知道
srcip、srcport(源IP和源端口号);detip、dstport(目的IP和目的端口号)就可以标识网络中唯二的两个进程。而网络通信的本质就是:进程间通信
套接字socket : ip + port
2. 传输层协议
了解操作系统,了解了网络协议栈,我们知道,传输层是属于操作系统内核的,那我们要通过网络协议栈进行通信,就势必要调用传输层通过的系统调用。
传输层协议主要有两种:TCP协议和UDP协议
TCP协议
- 有连接:在进行通信之前,通信双方必须先建立连接。
- 可靠传输:
TCP协议能够保证数据有序、完整、无差错从发送方传到接收方。- 面向字节流
UDP协议
- 无连接
- 不可靠
- 面向数据报
这里简单了解一个TCP、UDP协议,后序再深入研究。
3. 网络字节序
在之前学习C语言时,曾了解到内存中的多字节数据相对于内存地址有大端和小端之分;
小端存储: 数据的低位字节存储在内存的低地址处;高位字节存储在内存的高地址处。
大端存储:数据的低位字节存储在内存的高地址处;高位字节存储在内存的低地址处。
磁盘文件的多字节数据相对于文件中的偏移地址也有大端小端之分。
那通过网络通信的主机中,既有大端存储、也有小端存储。
那网络是不是要统一规定存储呢?
TCP/IP协议规定,网络数据流应采用大端字节序。即高字节低地址所以,在将数据传输到网络前,要先进行字节序的转换(主机字节序 -> 网络字节序);如果当前主机是小端机,就要将数据先转换为大端,再传输到网络;如果当前主机是大端机,就可以直接传输到网络。
当然,字节序的转换不需要我们自己去实现;我们可以直接调用库函数,做主机字节序和网络字节序的转换
c
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
htonl:32位主机字节序 --> 32位网络字节序
htons:16位主机字节序 --> 16位网络字节序
ntohl:32位网络字节序 --> 32位主机字节序
ntohs:16位网络字节序 --> 16位主机字节序
如果当前主机是大端存储,这些函数就什么都不做,然后返回;
如果当前主机是小端存储,这些函数就会将参数转为对应的大小端,然后返回。
4. socket相关接口
socket常用接口:
c
//创建套接字socket(文件描述符)
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);
这里简单了解一下
socket的接口,在后续编程中详细学习。
上述就是socket常用的接口,有TCP也有UDP相关的。
我们可以发现,这些接口的绝大部分都存在struct sockaddr*的参数,那这个struct sockaddr是什么呢?
在操作系统中,除了struct sockaddr还存在struct sockaddr_in和struct sockaddr_un三种结构;

这三种结构,第一个字段都是16位地址类型,其中struct sockaddr_in的该字段是AF_INET表示网络通信;struct sockaddr_un的该字段是AF_UNIX表示本地通信。
struct sockaddr_in还存在两个字段:
16位(2字节)端口号32位(4字节)IP地址
struct sockaddr_un用来进行本地通信,其原理类似于system V的命名管道通信。其另外一个字段是
108字节的文件名。
这里主要了解struct sockaddr_in,网络通信(也可以进行被本地通信)
那struct sockaddr_in在<netinet/in.h>的头文件下。
c
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr)
- __SOCKADDR_COMMON_SIZE
- sizeof (in_port_t)
- sizeof (struct in_addr)];
};
sin_port指的就是端口号,其类型是in_port_t也就是uint16_t;struct in_addr sin_addr,表示的是IP地址;struct in_addr就是对in_addr_t的封装,而in_addr就是uint32_t。
c
struct in_addr
{
in_addr_t s_addr;
};
在struct sockaddr_in结构中,看到了IP地址(sin_addr)、也看到了端口号(sin_port);
那第一个标志字段呢?
其标志字段就是
__SOCKADDR_COMMON (sin_);这是一个宏定义:
c#define __SOCKADDR_COMMON(sa_prefix) \ sa_family_t sa_prefix##family这个宏定义的作用就是:将传进来的字段加上
family后缀,在struct sockaddr_in中进行宏替换过后就变成了sin_family(也就是标志字段)
除此之外,在struct sockaddr_in中还存在一个字段,其作用就是填充struct sockaddr_in的大小。

了解了struct sockaddr_in,但是socket相关接口其中的参数是struct sockaddr*啊,那在调用时该如何调用呢?
这里
struct sockaddr、struct sockaddr_in和struct sockaddr_un中,第一个字段都是16位地址类型,也就是标志位;所以,在调用时传参时只需要进行强制类型转换,在函数内部就可以通过第一个字段就可以判断出传进来的是
struct sockaddr_in还是struct sockaddr_un;再分别执行不同的代码。
所以,这里struct sockaddr、struct sockaddr_in和struct sockaddr_un就像继承关系一样;
sockaddr是基类,sockaddr_in和sockaddr_un是派生类

通过第一个字段16位地址类型来判断是struct sockaddr_in还是struct sockaddr_un。
本篇文章到这里就结束了,感谢支持
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws