您现在的位置是:首页 >技术教程 >linux【网络编程】之网络套接字预备网站首页技术教程

linux【网络编程】之网络套接字预备

阿浩啊z 2024-06-14 17:17:00
简介linux【网络编程】之网络套接字预备

一、必备知识

【网络基础】中我们提到了IP地址,接下来了解一下网络通信中其他方面的知识

1.1 端口号

  1. 端口号是一个2字节16位的整数;
  2. 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  3. 一个端口号只能被一个进程占用

通信原理
在这里插入图片描述

  1. (公网)IP唯一标识一台主机,这样两台主机就可以发送接收数据,但是还需要区分数据发给那个软件

  2. 各自主机上的进程由端口号(port)唯一标识

  3. IP+端口号:表示该主机对应的服务进程在全网中是唯一的进程

软件之间的通信转换成进程,网络通信的本质就是进程间通信,客户端进程和服务端进程通过网络资源进行通信

1.2 端口号方面疑问及解决方案

  1. 不同主机上的端口号能一样吗?
    可以,IP保证了公网中主机的唯一性,port保证了主机内部的进程唯一性
  2. 端口号与进程之间的联系?
    同一台主机一个端口号只能被一个进程占用 ;一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定(os找不到)
  3. 进程已经有PID为什么还要有端口号?
    a.为了系统与网络解耦
    b.为了客户端迅速找到服务器进程–>IP+port不能随便改变(PID太容易被改变)
    c.不是所有进程都要提供网络服务,需要进行网络服务的拥有端口号
  4. 进程绑定port就变成了网络服务进程,OS是如何根据port找到对应的进程?
    底层OS维护了一张哈希表,根据port值找到对应的进程

二、TCP/UDP协议

这里仅仅是提一下,后面会结合实际、代码详细分析
TCP协议(传输控制协议)

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议(用户数据报协议)

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

不可靠传输:如发送数据时出现了丢包的情况、或者数据被重复传递了(传递了多份)、或者网络出现了问题等等造成的后果就叫做不可靠。所以传输层就是用来解决可靠性的一个协议。

可靠是需要成本的,往往在维护和编码上都比较复杂;而不可靠没有成本,使用起来也简单。所以要分场景使用。

三、网络字节流

内存中的多字节数据相对于内存地址有大端和小端之分

  • 大端存储:低位高地址
  • 小端存储:低位低地址

如果一个大端机用大端的方式发送数据到一个小端机,网络需要识别发送方式,于是
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节

  1. 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  2. 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可
  1. 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;(这里不管低地址处放的是高位还是低位)
  2. 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  3. 网络数据流的地址规定:先发出的数据是低地址,后发出的数据是高地址

网络中接收和发送都是先低地址再高地址,解释数据的时候以大端存储来解释

网络字节序和主机字节序的转换可以直接调用库函数

#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);

h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

四、socket编程

IP地址+端口号(port)能够标识该主机上的唯一进程:ip和端口号就叫为套接字

4.1 认识接口

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
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);

4.2 浅析sockaddr结构

常见的套接字有三种:网络套接字,原始套接字,unix域间套接字
网络套接字主要运用于跨主机之间的通信,也能支持本地通信;域间套接字只能在本地通信;原始套接字可以跨过传输层(TCP/IP协议)访问底层的数据,为了方便使用,设计者只用了一套接口(小伙伴看到这里应该会想到一直常见的实现方式:多态!!!)

在这里插入图片描述
上图可以看到sockaddr_in和sockaddr_un是两个不同的通信场景。区分它们就用前2个字节:16位地址类型协议家族的标识符(代表是本地通信还是网络通信),但是我们选择用sockaddr这个结构体

比如要进行网络通信,虽然参数是const struct sockaddr *addr,但实际传递进去的却是sockaddr_in结构体(类型不一样,注定要进行强制类型转换)。
在函数读取sockaddr前两个字节判断是什么通信类型然后再强转回去。

综上:可以把sockaddr看成基类,把sockaddr_in和sockaddr_un看成派生类,构成了多态
本篇文章为接下来网络程序模拟实现做铺垫,接口的详细认识及sockaddr会结合代码细细讲解,关注我,为你带来更多知识

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。