linux【网络编程】之网络套接字
- 一、必备知识
- 1.1 端口号
- 1.2 端口号方面疑问及解决方案
- 二、TCP/UDP协议
- 三、网络字节流
- 四、socket编程
- 4.1 认识接口
- 4.2 浅析sockaddr结构
一、必备知识
在【网络基础】中我们提到了IP地址,接下来了解一下网络通信中其他方面的知识
1.1 端口号
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- 一个端口号只能被一个进程占用
通信原理
-
(公网)IP唯一标识一台主机,这样两台主机就可以发送接收数据,但是还需要区分数据发给那个软件
-
各自主机上的进程由端口号(port)唯一标识
-
IP+端口号:表示该主机对应的服务进程在全网中是唯一的进程
软件之间的通信转换成进程,网络通信的本质就是进程间通信,客户端进程和服务端进程通过网络资源进行通信
1.2 端口号方面疑问及解决方案
- 不同主机上的端口号能一样吗?
可以,IP保证了公网中主机的唯一性,port保证了主机内部的进程唯一性 - 端口号与进程之间的联系?
同一台主机一个端口号只能被一个进程占用 ;一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定(os找不到) - 进程已经有PID为什么还要有端口号?
a.为了系统与网络解耦
b.为了客户端迅速找到服务器进程–>IP+port不能随便改变(PID太容易被改变)
c.不是所有进程都要提供网络服务,需要进行网络服务的拥有端口号 - 进程绑定port就变成了网络服务进程,OS是如何根据port找到对应的进程?
底层OS维护了一张哈希表,根据port值找到对应的进程
二、TCP/UDP协议
这里仅仅是提一下,后面会结合实际、代码详细分析
TCP协议(传输控制协议)
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
UDP协议(用户数据报协议)
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
不可靠传输:如发送数据时出现了丢包的情况、或者数据被重复传递了(传递了多份)、或者网络出现了问题等等造成的后果就叫做不可靠。所以传输层就是用来解决可靠性的一个协议。
可靠是需要成本的,往往在维护和编码上都比较复杂;而不可靠没有成本,使用起来也简单。所以要分场景使用。
三、网络字节流
内存中的多字节数据相对于内存地址有大端和小端之分
- 大端存储:低位高地址
- 小端存储:低位低地址
如果一个大端机用大端的方式发送数据到一个小端机,网络需要识别发送方式,于是
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
- 不管这台主机是大端机还是小端机, 都会按照这个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);
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会结合代码细细讲解,关注我,为你带来更多知识