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

在操作系统中存在非常多的进程,拿到的数据报文要交给哪一个进程呢?
IP
地址用来标识主机的唯一性,有了IP
地址才能够找到唯一的主机;那也就势必要存在用来标识主机(操作系统)中唯一进程的标识 --------- 端口号
而在操作系统中存在非常多的进程,并不是每一个进程都要从网络中获取信息,那也就是说不是每一个进程都要有端口号。
在操作系统中要从网络中获取信息的进程才拥有唯一的端口号;
也就是说,端口号可以标识主机(操作系统)中唯一的网络进程。
端口号
- 端口号是一个
2
字节,16
bit位的整数;- 端口号用来标识一个进程;
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