关注

深入了解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协议规定,网络数据流应采用大端字节序。即高字节低地址

所以,在将数据传输到网络前,要先进行字节序的转换(主机字节序 -> 网络字节序);如果当前主机是小端机,就要将数据先转换为大端,再传输到网络;如果当前主机是大端机,就可以直接传输到网络。

当然,字节序的转换不需要我们自己去实现;我们可以直接调用库函数,做主机字节序和网络字节序的转换

#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常用接口:

//创建套接字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>的头文件下。

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
struct in_addr
  {
    in_addr_t s_addr;
  };

struct sockaddr_in结构中,看到了IP地址(sin_addr)、也看到了端口号(sin_port);

那第一个标志字段呢?

其标志字段就是__SOCKADDR_COMMON (sin_);

这是一个宏定义:

#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

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/LH__1314/article/details/151429032

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--