网络编程本质
整理自《Linux内核源代码情景分析》,以unix_socket为例
struct socket {
socket_state state; /* 初始值为SS_UNCONNECTED */
unsigned long flags;
struct proto_ops *ops;
struct inode *inode; /* 取得一个inode结构是取得一个socket结构的必要条件,socket创建时会调用sock_map_fd,与文件描述符挂钩 */
struct fasync_struct *fasync_list;
struct file *file;
struct sock *sk; /* 底层数据结构,非常大, sock面向底层驱动程序,socket面向进程和系统调用 */
wait_queue_head_t wait;
short type; /* socket类型,SOCK_STREAM, SOCK_RAW, SOCK_DGRAM */
unsigned char passcred;
}
sys_socket()——创建插口
unix_socket *unix_socket_table[ UNIX_HASH_SIZE + 1 ];
将插口sock结构挂在unix_socket_table的最后一个队列中,unix_socket_table[ UNIX_HASH_SIZE ],用杂凑值作下标(0 ~ UNIX_HASH_SIZE-1)不会访问到该队列。
sock结构中包含有receive_queue接收队列与write_queue发送队列,队列中的元素为sk_buff,每个数据包都占一个sk_buff
接收到一个packet时,packet头部包含目标插口地址,根据该地址杂凑值找到相应队列,并扫描队列找到匹配的sock结构
sys_bind()——指定插口地址
将sock结构转移到相应的队列中。
对于常规的路径名地址,需要所创建的文件的i节点号码唯一(如果文件系统中已存在具有相同路径名的文件,则bind失败,否则新建文件,该文件在进程exit后依然存在,需要unlink()或者remove())
对于抽象地址,需要根据抽象地址的杂凑值在unix_socket_table的相应队列里检查该地址是否已经存在
TCP要求四元组唯一,但多数实现(包括Linux)强制实施更严格的约束:如果主机上有任何可匹配到本地端口的TCP连接,则本地端口不能被重用(即对bind()的调用)
启用SO_REUSEADDR可解放这个限制,使得更接近TCP的需求。大多数TCP服务器应开启该选项
sys_listen()——设定server插口
改变当前的状态为TCP_LISTEN
设定几个参数,如最大队列长度max_ack_backlog,
设置credentials,具体就是进程的用户号、组号、进程号
struct sock *sk = sock->sk;
...
sk->max_ack_backlog = backlog;
sk->state = TCP_LISTEN;
sk->peercred.pid = current->pid;
sk->peercred.uid = current->uid;
sk->peercred.gid = current->gid;
sys_accept()——接受连接请求
阻塞,在接受新连接或超时时返回
通过sock_alloc()分配新的socket结构,但并不分配与socket配对的sock结构,这是由请求方在connect()时通过sk_buff结构传过来的
Q:代码中未对接收到的报文,即sk_buff结构作检查,如何确定是连接请求报文?
A:只有连接请求报文会挂入到server插口的receive_queue中,数据报文只能挂入到建立了连接的新插口的receive_queue中
sys_connect()——请求连接
分配一个sock结构,并将该地址通过连接请求报文发送给server方
server方接受后记录server方传过来的地址
标记当前状态为已连接
0.0.0.0, 127.0.0.1, 127.0.0.2, 本机IP四者区别:
环回的特征是数据包不经过IP层以下的协议栈,直接被拷贝到环回地址socket的缓冲区中
首先假设本机有多个网卡:eth0 :192.168.0.1 eth1:192.168.1.1 lo: 127.0.0.1
1. 监听0.0.0.0创建Socket,那么无论使用127.0.0.1或127.0.0.2或本机ip都可以建立tcp连接,也就是不论通过127.0.0.1或127.0.0.2或192.168.0.1、192.168.1.1都能连接成功。
0.0.0.0建立tcp连接的时候也可以通过绑定IP_ADDR_ANY来实现。
2. 但是监听127.0.0.1,创建Socket,那么用本机地址或127.0.0.2建立tcp连接不成功,反过来也是如此;也就是,监听时采用的地址为192.168.0.1,就只能用192.168.0.1进行连接。IP层会过滤掉不同的IP。
3. 监听localhost? localhost是个域名,性质跟 “www.baidu.com” 差不多。不能直接绑定套接字,必须先gethostbyname转成IP才能绑定
那么问题来了,环回地址必须是127.0.0.1么?
答案:不是必须!IPv4 的环回地址是保留地址之一 127.0.0.1。尽管只使用 127.0.0.1 这一个地址,但地址 127.0.0.0 到 127.255.255.255 均予以保留。此地址块中的任何地址都将环回到本地主机中。此地址块中的任何地址都绝不会出现在任何网络中。
可以做一个简单的测试,用ssh root@127.2.3.4 然后登录看看是不是还是本机?不用修改ip,随意一个此范围内长度ip地址均可以ping通,并且通过ssh登录到本机。
如下图,lo地址为127.0.0.1,netmask为255.0.0.0,所有与netmask掩码运算的结果 = inet & netmask都被视为环回地址。
环回的特征是数据包不经过IP层以下的协议栈,直接被拷贝到环回地址socket的缓冲区中
ip输出函数先检查地址是不是环回地址
1.如果是环回地址 直接交给环回驱动程序处理 返回ip输入函数
2.如果不是环回地址 检查是不是广播或者多播地址。如果是广播地址或者组播地址数据报复制一份传给环回接口。然后在送到以太网上,因为广播和多播包含主机本身
3.如果不是广播或者多播地址 再检查是不是本机地址 如果是本机地址 则交给环回驱动程序处理,环回驱动程序返回给ip输入函数
从上面可以看出 ping127.0.0.1 数据包是不经过网卡的, ping本机则是需要经过网卡的
所以,
1.ping 127.0.0.1是检查TCP/IP协议栈是否正常。
2.ping 本地IP 是检查你网卡、配置是否正常。
网络相关配置文件
#eth0网卡的IP相关参数
/etc/sysconfig/network-scripts/ifcfg-eth0
#主机名,修改主机名后需要重启电脑
/etc/sysconfig/network
#DNS IP
/etc/resolv.conf
#私有IP对应的主机名
/etc/hosts
#TCP/IP上的各种协议对应的端口
/etc/services
#定义IP数据包协议的相关数据
/etc/protocols
网络配置命令
#重启网络的所有参数,会主动读取所有的网络配置文件,命令同service network restart
/etc/init.d/network restart
#查看网络接口
ifconfig
#启动(关闭)eth0接口
ifup eth0 (ifdown eth0)
#查看路由表
#找到最佳匹配后,从接口发送到下一个网关。
#掩码为0.0.0.0表示默认路由
#网关为0.0.0.0表示未设置网关,通常可直接交付
#网关与接口不需要在同一网段, 如PPPoE
#ref: https://www.zhihu.com/question/54007586
route -n