文章目录
- 1. IP地址
- 2. 端口号
- 3. 初识TCP协议 && UDP协议
- 4. 网络字节序
- 5. socket创建API
举个例子:
《西游记》中,唐僧要去取件,总是说从“东土大唐”来,前往“西天”拜佛求经,从哪里来,到哪里去,这一直都是不变的。
这里的“东土大唐”就是源IP地址,“西天”就是目的IP地址
在路上,一直发生变化的是上一站从哪来和下一站到哪去,比如说上一站在“女儿国”,下一站要去“火焰山”,这里的依据就是最终要去哪里
这里一直发生变化就叫做**mac
地址**
IP地址存在的意义就是,它能够指导我们进行路径规划。现在主流IP
地址是IPv4
,它是4字节、32比特位的整数,例如192.168.1.1
它能标定特定主机的唯一性。
当2台主机不在同一个子网的时候,那就需要把数据交给先给路由器,让路由器进行重新“路线规划”。
“重新路线规划”之后再交给下层,即上图的令牌环驱动程序(这里只是一个例子),加上了令牌环的报头,可是这对于上层来说,它们收到报文的时候,这个报头已经去掉了。
所以有了IP协议(工作在IP层的路由器)的存在,底层的差异被屏蔽了,一切皆是IP报文。
IP地址 Vs Mac地址:
IP
地址(尤其是目的IP
),一般都不是不会改变的;而
Mac
地址,出局域网之后,源和目的都会被丢弃,让路由器重新选择
网络协议栈中下三层,主要解决的是数据安全可靠传输到远端机器上(主机 -> 主机)
用户使用应用层软件完成数据的发送和接收
用户要使用这个软件,就得先把这个软件启动起来,这本质上就是进程,也就是进程A向进程B发起数据请求,这是进程通信,只不过它们使用了网络协议栈。
我们在传输层收到信息,它要向上交付给应用层,比如说应用层有很多软件:微信、QQ、淘宝什么的,应用层和传输层就需要协商一种方案,也就是端口号,传输层报文里面携带这个端口号,就能知道将数据交付给哪个应用。
端口号无论是对于客户端还是服务端,都能唯一标识该主机上的一个网络应用层的进程
在公网上,IP
能标识唯一一台主机,而端口号能标识该主机的唯一进程,所以IP
+Port
能标识全网唯一一个进程
这种
IP
+Port
的通信方式,我们叫做socket
在主机里面,进程
pid
能标识进程的唯一性,那为什么还要有port
呢?站在技术层面,用
pid
来标识网络应用的唯一性,是可以实现的;但是pid
是系统层面的,如果系统层面改了,那网络层面也需要作出改变,所以给网络单独设计一套规则,这也就能进行解耦。
2台主机进行通信,是要绑定端口号的
传输层内部,操作系统会实现一个哈希表,里面存的是task_struct*
,当我们绑定端口号的时候,如果发现这个端口号没有在哈希表中,则可以绑定,将这个进程pcb
的地址放进去;如果哈希表中有了,则表明这个端口号已被占用,需要换一个。
tcp(Transmission Control Protocol)
和udp(User Datagram Protocol)
协议都是传输层的协议的,而传输层是离用户最近的,所以一般以通信为目的的代码都是传输层提供的接口,传输层提供的协议就是tcp
和udp
。
tcp
叫传输控制协议:
在数据传输之前要保证通信信道的通畅
udp
叫用户数据报协议:
这里的可靠和不可靠,并不是褒义贬义词,而是一个描述特征词汇
内存多字节数据对于内存都有高低地址之分,如何存储就有了不同意见,即大端存储还是小端存储,这也说不上哪种更好。所以对于网络,就要考虑这个问题,如果大端机向小端机发送数据,那么小端在数据解析的时候,就会发送解析错误。
对于数据的存储,有兴趣可以查看此篇文章:数据的存储
计算机出现的比网络早,计算机对于大小端的争论没得出结果,网络为了不影响自己,于是直接规定网络当中的数据全部为大端,所以对于小端机,发送数据前,要先将小端转大端。
为了网络程序的可移植性,下面库函数做网络字节序和主机字节序的转换
#include
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位短整数- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回
// 创建 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);
套接字编程种类:
域间套接字编程
同一个机器内(本地通信)
原始套接字编程
绕过传输层,使用底层的接口,一般用来编写一些网络工具
网络套接字编程
使用传输层,进行用户间的网络通信
理论上不同种类的套接字,是需要三套接口的,可是设计者直接设置成了一套(统一抽象化),要保证接口统一,参数类型必须是统一的,即struct sockaddr*
每个接口的前2个字节是可以进行判断的
if(address->type == AF_INET)
{
//网络
}
else if(address->type == AF_UNIX)
{
//本地
}
这其实就是多态的体现,只是之前C语言并没这个东西,所以我们用的时候强转即可