linux之socket编程
源IP地址与目的IP地址
任何主机想要进行网络通信,首先就要拥有IP地址!因为每台主机都有网络地址
就注定了有——源IP与目的IP
消息从哪里发送——发送主机的IP就是源IP
消息要到哪里——接收消息的主机的IP就是目的IP!
在IP报头里面就包含了源IP与目的IP
端口号
当我们从数据从A主机送到B主机是我们的目的么?——不是!
因为真正通信不是这两台机器!
==端口号是在传输层提供的功能,而传输层是属于内核的!内核里有对应的端口信息!——问题是这些端口信息是怎么样找到应用层曾经启动的某个进程呢?==
底层OS是如何根据port找到指定的进程?
每一个进程在操作系统内部都有PCB,我们假设port是一个unit16类型的数据!
也就是说现在的问题如何根据这个unit16类型的数据找到 task_struct这个PCB结构体!
操作系统是使用的是hash方案!——操作系统内部维护了一张基于端口号做key值的hash表,然后value就是对应的PCB的地址!
这样子就可以根据port找到这个进程,找到这个进程就可以找到这个进程的文件描述符表!找到这个文件描述符就可以找到这个文件对象!找到这个文件对象就可以找到文件对象的缓冲区,这样子我们就可以将数据放入这个缓冲区里面!——这就相当于把网络数据放在文件里面了!就可以如同读取文件一样!将数据读取出来!
==另外,一个进程可以绑定多个端口号;但是一个端口号不能被多个进程绑定==
为什么呢?——因为从端口号的进程必须是唯一的!这样子数据才能找到进程!
但是从多个端口号到一个进程也是可以的!
例如:我们经常听说软件后门,其实就是绑定了多个端口号,一个给我们用户用的!另一个是给另一个人,但是我们不知道!另一个人通过这个端口号获取信息或者发送指令
源端口和目的端口
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号。就是在描述"数据是谁发的,要发给谁";
==所以我们在网络通信的过程中,IP+port表示唯一性!如果今天client将数据发送server!那么我们要不要将自己的ip和端口发送给对方呢?——因为因为我们还要发回来!==
==服务端还要给客户端传回消息!==
这就决定了,未来发送数据的时候一定会"多发"一部分数据!——以协议的形式呈现!
认识TCP协议
在传输层有两种协议——一个是TCP,一个是UDP
TCP是Transmission Control Protocol 传输控制协议其特点有
- 传输层协议
- 有连接——在通信之间要先建立连接
- 可靠传输
- 面向字节流
认识UDP协议
UDP是User Datagram Protocol 用户数据报协议
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
网络字节序
为什么要有网络字节序
什么是网络字节序?
我们都知道,计算机是分为大端机和小端机的!而数据都是是内存里面存的!
而数据都是有自己的高权值的!例如1234,1虽然数字很小!但是权值位很高!数字也是有高位和低位的区别的!
一个整数,要开辟4字节的内存空间去存储!而地址也是有高地址和低地址之分的
我们可以选择把高权值的数据,存在高地址的地方!也可以选择放在低地址的地方!——这就是大端和小端的区别!==(大小端的存储是按照字节为单位的!不是按照bit位为单位的!)==
小端就是高权值的数据放在高地址处,低权值位的数据数据放在低地址处!
大端就是高权值的数据排放在内存的低地址端,低权值位的数据排放在内存的高地址端。
如果出现了——数据从大端机传给小端机,一个整形四个字节是按照大端来存的!发的时候也是按照大端来发的!最后小端收的是一个大端的数据!那么最后是不是会出现将数据解释反了的情况?
==但是这样不是问题!因为我们可以从算法上将其逆序就好了!——真正的问题是我们该如何判断我们收到的数据是大端还是小端的!我们是无从知道的!==
因为大小端存在的时间是很长的!所以也衍生出了很多的解决方案!但是我们可以使用一个最简单的解决方案!——==我们规定网络中的数据都是大端的!==
大端的数据那么就直接发!小端的数据那么就转换一下再发送!!——这样子就解决了我们收到的数据是大端还是小端的判断问题!
==这个按照大端存储的数据我们就称之为网络字节序!==
网络字节序的使用
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出:
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存:
- 因此,网络数据流的地址应这样规定**:先发出的数据是低地址,后发出的数据是高地址**
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据:
- 如果当前发送主机是小端,就需要先将数据转成大端:否则就忽略,直接发送即可;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行可以调用以下库函数做网络字节序和主机字节序的转换。
#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);
socket网络编程
常见的API接口
我们所写的代码都是在应用层的!所以这就是注定了!我们要调用各种的系统调用接口!
所以操作系统给我们提供了常见的接口!
// 创建 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结构
实际上在网络通信的时候,套接字的种类是很多的!
有网络套接字,原始套接字,Unix域间套接字等等
字符串转整形和整形转字符串
为什么IP的类型是unit32_t呢?——其实在我们写代码的时候我们一把会用string/字符串类型的变量来表示IP例如:“192.168.0.0”
一个IP地址是按照点十分制——这种字符串形式的点分十进制的其实没有什么意义,就是可读性比较而已好,除此之外一无是处
用一个4字节的类型是足够用来存储的!即unit32_t ip——这种整数类型的风格一般就是网络中使用的!网络是不会使用我们上面写的字符串的!因为使用字符串还要解析,转换,比较很慢!而且占用空间还大!
==但是系统会有接口会帮我们将字符串风格IP转换为整数风格的IP——也可以转回来!==
socketAPI接口详解
socket
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
accept
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
这个函数的作用就是接收一个连接!——在使用tcp的时候!就要让服务器一直进行accept,用来获取连接,等到获取了一个连接,才能进行发送信息
sockfd参数
——从这个套接字里面获取新连接
addr参数
——是一个输入输出型参数,里面有将来是谁对我们发起的连接的主机的ip地址和port
addrlen参数
——是一个输入输出型参数,表示上面的addr参数的实际大小!
返回值
函数调用成功,则返回一个整数,整数是一个已经接收连接的套接字的文件描述符
==为什么也是一个文件描述符?——sockfd也是文件描述符,这两个文件描述符有什么差别么?==
未来一个服务器,被几百个人同时连接,那么是不是这就意味着要建立几百个文件描述符?
connect
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
==这个函数的作用就是在套接字初始化一个连接——即发起连接==
sockfd参数
——要通过那个套接字发起连接,客户端的套接字
addr参数
——一个输入型参数,里面要填我们要向那个客户端发起连接的ip和port
addrlen
——就是addr参数的大小!
connect调用后如果没有绑定ip和port会自动的去绑定!
返回值
如果连接成功返回0,失败返回-1且错误码被设置!——我们可以看到如果连接成功,该函数会自动帮我们的binding的!
socket读写数据接口
recvfrom
当我们要从socket里面读取数据的时候我们就要要用到这个接口
sockfd参数
——就是我们要从呢个套接字去读取
buf参数
——就是要将数据读取到那个缓冲区里面!
len参数
——就是缓冲区的长度
flags参数
——我们一般默认为0
sendto
当我们想要给套接字发送数据的时候!我们可以使用这个接口!
sockfd参数
——就是我们想要向那个套接字发送!
buf参数
——就是要将数据读取到那个缓冲区里面!
len参数
——就是缓冲区的长度
flags参数
——我们一般默认为0