[linux网络]Socket预备与编程[网络·贰]

🌟 各位看官好,我是!****

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享更多人哦!

Socket编程预备

数据传输到主机是目的吗?不是的。因为数据是给人用的。怎么把数据给人用??怎么让人看到?

通过启动的 qq, 迅雷, 浏览器.而启动的qq,迅雷,浏览器都是进程。换句话说,进程是人在系统中的代表,只要把数据给进程,人就相当于就拿到了数据。

所以:数据传输到主机不是目的,是手段。到达主机内部,在交给主机内的进程,才是目的。

还记得在系统部分进程间通信或者线程间通信,他们都是利用OS资源进行通信的,而网络通信是跨主机的,同一份资源是网络啊!因此:

网络通信,本质就是进程间通信!

但是系统中,同时会存在非常多的进程,当数据到达目标主机之后,怎么转发给目标进程?这就要在网络的背景下,在系统中,标识主机的唯一性。

认识端口号

端口号(port)是传输层协议的内容.

  • 端口号是一个 2 字节 16 位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用.

标识两个进程的唯一性,在IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;

进程章节我们讲过pid能够标识进程的唯一性,为什么还要引入端口号这个东西呢?

这里讲个小故事,方便大家理解:

在学校里,学校会为每一个学生配备对应的学号,而你自身也有对应的身份证号啊!为什么学校不拿你的身份证号来标识你的唯一性呢?

那么我问你,如果身份证号变了呢?那么学校标识你这个人的唯一性方案也得改啊!这不就引起了

一个模块会联动引起另外一个模块的变化,增加耦合度啊!!!

会让系统和网络的耦合度增加,因此单独设计一套端口号.

一个端口号只能被一个进程占用,那么一个进程是否可以绑定多个端口号呢?可以的

如何理解通过端口号找到目标进程的过程?

所谓的进程不就是PCB,本质不就是找到进程的PCB嘛!0S存在一个hash表,key值就是端口号,value就是进程的PCB,就能拿着端口号查hash表找到对应进程了.

范围划分

  • 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的.(某些特殊端口号已经被占用了)
  • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.

理解Socket

  • IP 地址用来标识互联网中唯一的一台主机, port 用来标识该主机上唯一的一个网络进程
  • IP+Port 就能表示互联网中唯一的一个进程
  • IP+Port叫做套接字Socket

所以,通信的时候,本质是两个互联网进程代表人来进行通信,{srcIp,srcPort,dstIp,dstPort}这样的4元组就能标识互联网中唯二的两个进程.

传输层

传输层是属于内核的, 那么我们要通过网络协议栈进行通信, 必定调用的是传输层提供的系统调用, 来进行的网络通信。TCP/UDP是传输层要认识的两个最重要的协议

认识TCP/UDP协议

对 TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识:

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

对 UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识:

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

什么叫连接?打电话进行喂喂,通信前进行准备的过程,进行握手的过程

什么叫无连接?不需要握手,老板直接给我发文件.

可靠?不可靠?首先不能把不可靠当作UDP的缺点,而应该当成特点来看,为什么这样说呢?

可靠传输时,数据丢失、发送太快等异常时,TCP协议会自动进行重传等动作,不可靠,当把传输层交给网络层之后,就不管了

字节流 vs 数据报:想接多少水就接多少水(管道):收快递,要么是一个,二个等,每个快递都是独立的

网络字节序

我们知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

为了解决两主机是不同端的问题,网络数据流的地址这样规定:先发出的数据是低地址,后发出的数据是高地址.

同时也存在一个问题,如果主机A是小端,主机B是大端,会导致某主机收到的数据是反的啊!怎么办呢?

发送数据的时候把自己是大端还是小端告诉对方?很明显行不通,因为数据都没办法进行解析啊!

如何做呢?网络规定:统一使用大端通信.

Socket编程接口及相关接口

做网络通信本质上就是进程间通信套接字,在本主机内能否取代system V呢?可以的.

网络通信必备的四个头文件:

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>
/ / 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)

int socket(int domain, int type, int protocol);

功能:创建套接字

参数:

domain 协议族或域

  • AF_UNIX(本地套接字) Local communication unix(7)
  • AF_LOCAL(本地套接字) Synonym for AF_UNIX
  • AF_INET(IPv4 协议族) IPv4 Internet protocols ip(7)

type指的是套接字类型

  • SOCK_STREAM(流式套接字) Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data trans‐mission mechanism may be supported.
  • SOCK_DGRAM(数据报套接字) Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
  • SOCK_RAW(原始套接字) Provides raw network protocol access.

protocol默认为0即可,自动选择TCP/UDP

返回值:On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set to indicate the error.

成功返回一个套接字描述符,失败返回-1.实际上这就是一个文件描述符啊!只是写的高高在上罢了.

// 绑定端口号 (TCP/UDP, 服务器)

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

功能:将一个套接字(socket)与一个特定的本地地址(IP地址 + 端口号)绑定起来

参数:

  • socket 指的是socket返回的套接字
  • address 包含了要绑定的协议族、IP地址、端口号等信息的结构体。

常见地址结构体:

struct sockaddr_in

struct sockaddr_un

bash 复制代码
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)];
  };

/* 拼接两个字符,形成_符号
#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

/* Type to represent a port.  */
typedef uint16_t in_port_t;

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

len 第二个参数 address 所指向的结构体的**实际长度,**输出型参数

返回值:

On success, zero is returned. On error, -1 is returned, and errno is set to indicate the error.

成功返回0,失败返回-1,错误码被设置.
网络字节序和主机字节序的转换:

为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行

函数名很好记,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。

htonl:将32 位主机字节序整数 转换为32 位网络字节序整数,用于传输 32 位长度的数据

htons:将16 位主机字节序整数 转换为16 位网络字节序整数,用于传输 16 位长度的数据(常用于端口号)

in_addr_t inet_addr(const char *cp);

功能:将点分十进制的 IPv4 字符串转换为网络字节序的 32 位整数

成功时 :返回转换后的 32 位网络字节序(大端序)的 IPv4 地址。网络字节序是 TCP/IP 协议中规定的统一字节序,确保不同字节序的设备之间能正确通信。

失败时 :返回 INADDR_NONE(一个宏定义,值为 0xFFFFFFFF,即点分十进制的 255.255.255.255

char *inet_ntoa(struct in_addr in);

功能:将网络字节序的 32 位 IPv4 地址(整数)转换为人类可读的点分十进制字符串
辅助函数:

void bzero(void s.n, size_t n);

void *memset(void s.n, int c, size_t n);

功能:清零,这里的应用场景是将结构体进行清零

sockaddr结构

socket API 是一层抽象的网络编程接口,适用于各种底层网络协议.

  • IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包括 16 位地址类型, 16 位端口号和 32 位 IP 地址.
  • IPv4、 IPv6 地址类型分别定义为常数 AF_INET、 AF_INET6. 如何进行区分?所以第一个数据段必须是16位地址类型,不做强转,在底层直接拿着struct sockaddr结构体提取前16位,判断是AF_INET还是AF_UNIX.
  • socket API 可以都用 struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收 IPv4, IPv6, 以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数;