作者前言
🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言、C++和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂
Socket编程
- **作者前言**
- Socket编程预备概念
-
- 数据传输到主机是⽬的吗
- 认识端⼝号(传输层的内容)
- [端口号VS 进程PID](#端口号VS 进程PID)
- 源端口号和目的端口号
- 端口划分
- 理解socket
- 认识TCP协议
- 认识UDP协议
- 网络字节序
- socket(套接字)
Socket编程预备概念
数据传输到主机是⽬的吗
进程是⼈在系统中的代表,只要把数据给进程,⼈就相当于就拿到了数据。
数据传输到主机不是⽬的,⽽是⼿段。到达主机内部,在交给主机内的进程 ,才是**⽬的**。
我们的上网行为无非就是两种:获取数据或者上传数据,如图:

网络通信的:就是让两个不同的主机进程进行数据交换,本质就是进程间的通信 。
让不同的进程进行通信,就需要先看到同一份资源,这个资源就是网络,前面学的进程间的通信是在同一台主机内,这里的则是两个不同主机的进程
认识端⼝号(传输层的内容)
在一台主机系统中,同时会存在⾮常多的进程,当数据到达⽬标主机之后,怎么转发给⽬标进程?
那么就需要在系统中,标识主机的网络进程唯⼀性。这个就是端口号
端口号的认识:
- 端⼝号是⼀个 2 字节 16 位的整数;
- 端⼝号⽤来标识⼀个进程, 告诉操作系统, 当前的这个数据要交给哪⼀个进程来处理;
- IP地址 + 端⼝号能够标识⽹络上的某⼀台主机的某⼀个进程;
- ⼀个端⼝号只能被⼀个进程占⽤,⼀个进程可以绑定多个端⼝号。
- 端口号是标识主机内唯一的一个网络进程
如图:

端口号怎么和进程绑定,目前我们可以理解为在传输层中会维护一个哈希表,端口号就是这个哈希表的关键码,就可以查看对应的进程。
端口号VS 进程PID
- 不是所有的进程都要进行网络通信
- 使用端口号而不使用PID,是为了和操作系统解耦合。
源端口号和目的端口号
传输层协议的数据段中有两个端⼝号, 分别叫做源端⼝号和⽬的端⼝号. 就是在描述 "数据是谁发的, 要发给谁";
端口划分
- 0 - 1023 : 知名端⼝号, HTTP, FTP, SSH 等这些⼴为使⽤的应⽤层协议, 他们的端⼝号都是固定的.
- 1024 - 65535 : 操作系统动态分配的端⼝号. 客⼾端程序的端⼝号, 就是由操作系统从这个范围分配的.
理解socket
IP地址 + 端口号:可以标识全网中唯一的网络进程
源IP + 源端口 = 源主机的唯一的网络进程
目标IP + 目的端口 = 目的主机的唯一的网络进程
总结:socket = IP + prot(端口)
认识TCP协议
TCP协议有如下特点:
• 传输层协议
• 有连接
• 可靠传输
• ⾯向字节流
认识UDP协议
UDP协议有如下特点:
• 传输层协议
• ⽆连接
• 不可靠传输
• ⾯向数据报
网络字节序
我们知道数据在内存的存储是会区分大小端的,大端:低权值数据存储在高地址处。小端:低权值据存储在低地址处。如图:

假设主机A是小端存储,主机B是大端存储,A发送数据给B,B就会把数据解析为大端,这样就把数据搞反了。
所以就有了:
TCP/IP协议规定,⽹络数据流应采⽤⼤端字节序,即低地址⾼字节,如果当前发送主机是⼩端, 就需要先将数据转成⼤端。
⽹络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是⾼地址.
系统也会提供对应的库函数,把主机字节序转换为网络字节序,接口如下:

h 表⽰ host , n 表⽰ network , l 表⽰ 32 位⻓整数, s 表⽰ 16 位短整
数。
总结: 所有发送到⽹络上的数据,都必须是⼤端的!
socket(套接字)
网络通信的本质就是进程间的通信。
前面学的system V 就是本地的进程间的通信,而socket采用的是posix标准,posix标准不仅可以网络进程间通信,还可以本地进程间通信,为此就会有不同种类的socket,也就意味着需要实现多种不一样的socket接口,但是设计者就想实现一种通信接口,不仅可以本地通信,也可以网络通信。
sockaddr结构
socket常见的一些接口:
cpp
// 创建 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);
其中sockaddr结构如下:

设计者会设置出三个结构体,如果要进行网络通信,就创建 struct sockaddr_in,如果要进行本地通信,就创建 struct sockaddr_un,而结构体sockaddr就是接口传入参数类型。为啥要设置这个结构体
原因如下:
- 当进行本地通信或者网络通信的时候只需要struct sockaddr_in变量地址 或者 struct sockaddr_un变量地址 强转为 struct sockaddr*类型,
- 接口内部就进行判断sockaddr的前两个字节地址类型是AF_INET或者AF_UNIX就会强转为对应的结构体地址类型,这个就是C++继承和多态的原理。传参就是切片
其中sockaddr_in在头文件<netinet/in.h>中,源码如下:
cpp
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); // 变量名为sin_family
in_port_t sin_port; //16bit端口号
struct in_addr sin_addr; //32位bit的ip地址
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr)
- __SOCKADDR_COMMON_SIZE
- sizeof (in_port_t)
- sizeof (struct in_addr)];
};