深入了解linux网络—— 网络编程基础

IP地址与端口号

了解了IP地址,在网络中用来表示主机的唯一性;

数据报文由源主机通过网络传输到目标主机后,目的主机拿到这个数据报文要如何处理呢?

我们使用QQ聊天、浏览器浏览网页,是如何获取消息的呢?

QQ和浏览器都是进程,也就是说只有将数据交给进程,我们才能看到这些信息。

在操作系统中存在非常多的进程,拿到的数据报文要交给哪一个进程呢?

IP地址用来标识主机的唯一性,有了IP地址才能够找到唯一的主机;

那也就势必要存在用来标识主机(操作系统)中唯一进程的标识 --------- 端口号

而在操作系统中存在非常多的进程,并不是每一个进程都要从网络中获取信息,那也就是说不是每一个进程都要有端口号。

在操作系统中要从网络中获取信息的进程才拥有唯一的端口号;

也就是说,端口号可以标识主机(操作系统)中唯一的网络进程。

端口号

  1. 端口号是一个2字节,16bit位的整数;
  2. 端口号用来标识一个进程;
  3. IP地址 + 端口号就能够标识网络上的某一台主机的某一个进程;
  4. 一个端口号只能够被一个进程使用。

端口号范围

0-1023:知名端口号,HTTPFTPSSH等广为使用的应用层协议,它们端口号都是固定的;

1024-65535:操作系统动态分配的端口号;客户端程序的端口号,就是由操作系统从该范围内分配的。

端口号与进程ID

学习过操作系统,我们知道,在操作系统中pid可以用来标识唯一进程;这里的端口号也可以用来标识操作系统中的唯一进程;

进程ID属于系统的概念,也可以用来标识唯一进程;但是并没有使用进程ID来作为这里标识唯一进程的标识符;(如果使用进程ID作为这里的标识符,会让系统进程管理和网络强耦合)

此外:一个进程可以绑定多个端口号,一个端口只能绑定一个进程

源IP / 端口号和目的IP / 端口号

传输层协议(TCPUDP)的数据段中存在两个概念:源IP/端口号、目的IP/端口号;

简单来说源IP/端口号表示的是谁发的、目的IP/端口号表示发给谁的。

TCP/UDP编程中会遇到这两个概念。

socket 编程基础

1. socket套接字

了解了IP 和端口号,我们知道IP 可以标识网络中的唯一主机;端口号可以表示主机中的唯一进程。

所以,IP+端口号就可以标识网络中的唯一进程。

所以,只要知道srcipsrcport(源IP和源端口号);detipdstport(目的IP和目的端口号)就可以标识网络中唯二的两个进程。

而网络通信的本质就是:进程间通信

套接字socket : ip + port

2. 传输层协议

了解操作系统,了解了网络协议栈,我们知道,传输层是属于操作系统内核的,那我们要通过网络协议栈进行通信,就势必要调用传输层通过的系统调用。

传输层协议主要有两种:TCP协议和UDP协议

TCP协议

  1. 有连接:在进行通信之前,通信双方必须先建立连接。
  2. 可靠传输:TCP协议能够保证数据有序、完整、无差错从发送方传到接收方。
  3. 面向字节流

UDP协议

  1. 无连接
  2. 不可靠
  3. 面向数据报

这里简单了解一个TCPUDP协议,后序再深入研究。

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_instruct 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 sockaddrstruct sockaddr_instruct sockaddr_un中,第一个字段都是16位地址类型,也就是标志位;

所以,在调用时传参时只需要进行强制类型转换,在函数内部就可以通过第一个字段就可以判断出传进来的是struct sockaddr_in还是struct sockaddr_un;再分别执行不同的代码。

所以,这里struct sockaddrstruct sockaddr_instruct sockaddr_un就像继承关系一样;

sockaddr是基类,sockaddr_insockaddr_un是派生类

通过第一个字段16位地址类型来判断是struct sockaddr_in还是struct sockaddr_un

本篇文章到这里就结束了,感谢支持

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

相关推荐
chlk1231 天前
Linux文件权限完全图解:读懂 ls -l 和 chmod 755 背后的秘密
linux·操作系统
舒一笑1 天前
Ubuntu系统安装CodeX出现问题
linux·后端
改一下配置文件1 天前
Ubuntu24.04安装NVIDIA驱动完整指南(含Secure Boot解决方案)
linux
BingoGo1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
深紫色的三北六号1 天前
Linux 服务器磁盘扩容与目录迁移:rsync + bind mount 实现服务无感迁移(无需修改配置)
linux·扩容·服务迁移
SudosuBash2 天前
[CS:APP 3e] 关于对 第 12 章 读/写者的一点思考和题解 (作业 12.19,12.20,12.21)
linux·并发·操作系统(os)
哈基咪怎么可能是AI2 天前
为什么我就想要「线性历史 + Signed Commits」GitHub 却把我当猴耍 🤬🎙️
linux·github
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端