前言
前四篇文章講述了Okhttp的核心原理费变,得知Okhttp是基于Socket開發(fā)的,而不是基于HttpUrlConnection開發(fā)的店煞。
其中對于客戶端來說获讳,核心有如下四個(gè)步驟:
- 1.dns lookup 把資源地址轉(zhuǎn)化為ip地址
- 2.socket.connect 通過socket把客戶端和服務(wù)端聯(lián)系起來
- 3.socket.starthandshake
- 4.socket.handshake
第五篇介紹了DNS的查詢流程。本文開始聊聊Socket中的核心原理愤诱。而socket就是所有網(wǎng)絡(luò)編程的核心云头,就算是DNS的查詢實(shí)現(xiàn)也是通過socket為基礎(chǔ)實(shí)現(xiàn)。
注意接下來涉及的內(nèi)核源碼是3.1.8淫半。
正文
Socket是什么溃槐?在計(jì)算機(jī)術(shù)語中都叫套接字。這種翻譯比較拗口且難以理解科吭。我在網(wǎng)上看到一個(gè)比較有趣的解釋昏滴,socket的在日常用法的語義為插槽。如果把語義延展開來砌溺,可以看成是兩個(gè)服務(wù)器之間的用于通信的插槽影涉,一旦通過connect鏈接起來就把兩個(gè)服務(wù)器之間的通信通道通過socket插槽鏈接起來了。
1.客戶端使用
來看看Java中的用法(這里我們只關(guān)注客戶端tcp傳輸?shù)倪壿嫞┕娣ィ话闶褂萌缦拢?/p>
- 1.聲明一個(gè)Socket對象
Socket socket = new Socket(host, port);
也可以直接想Okhttp中一樣直接使用默認(rèn)的構(gòu)造函數(shù)
Socket socket = new Socket();
- 2.如果之前沒有設(shè)置過地址蟹倾,那么此時(shí)就需要connect 鏈接對端的地址
socket.connect(address, connectTimeout)
- 獲取到socket的讀寫流,往socket中寫入數(shù)據(jù)猖闪,或者讀取數(shù)據(jù)
socket.getOutputStream().write(message.getBytes("UTF-8"));
InputStream inputStream = socket.getInputStream();
len = inputStream.read(bytes)
實(shí)際上客戶端的用法十分簡單鲜棠。
對于服務(wù)端又是怎么使用的呢?
2.服務(wù)端使用
- 1.構(gòu)建一個(gè)ServerSocket 對象后培慌,調(diào)用accept方法生成一個(gè)Socket對象
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
- 通過獲取讀取流和寫入流進(jìn)行對客戶端socket的發(fā)送的數(shù)據(jù)的讀取以及應(yīng)答豁陆。
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len, "UTF-8"));
}
OutputStream outputStream = socket.getOutputStream();
outputStream.write("...".getBytes("UTF-8"));
當(dāng)理解了整個(gè)使用后,我們依次來看看socket在底層中都做了什么吵护?
正文
我們先從客戶端Socket實(shí)例化盒音,到服務(wù)端Socket的初始化并監(jiān)聽開始考察表鳍。
1.客戶端Socket實(shí)例化
public Socket() {
setImpl();
}
void setImpl() {
if (factory != null) {
impl = factory.createSocketImpl();
checkOldImpl();
} else {
// SocketImpl!
impl = new SocksSocketImpl();
}
if (impl != null)
impl.setSocket(this);
}
Socket所有的事情都會交給這個(gè)SocksSocketImpl完成。
2.SocksSocketImpl 構(gòu)造函數(shù)與初始化
先來看看SocksSocketImpl 的UML繼承結(jié)構(gòu)
這個(gè)結(jié)構(gòu)圖中祥诽,SocketOptions
定義了接口敞亮譬圣,抽象類SocketImpl
定義了connect等核心的抽象方法,PlainSocketImpl
則定義了一個(gè)構(gòu)造函數(shù)雄坪,這一個(gè)特殊的構(gòu)造函數(shù)厘熟,這個(gè)構(gòu)造函數(shù)為PlainSocketImpl
創(chuàng)建了一個(gè)FileDescriptor
文件描述符對象。
SocksSocketImpl
無參構(gòu)造函數(shù)并不會做任何事情:
SocksSocketImpl() {
// Nothing needed
}
而setSocket中维哈,調(diào)用的是SocketImpl
中方法:
void setSocket(Socket soc) {
this.socket = soc;
}
存儲當(dāng)前的socket對象
3.Socket connect 鏈接到客戶端
上文中绳姨,當(dāng)Okhttp客戶端需要鏈接到某個(gè)地址,就會嘗試著從域名中解析出地址和端口阔挠,然后通過這兩個(gè)數(shù)據(jù)生成InetSocketAddress
對象飘庄。
open fun connectSocket(socket: Socket, address: InetSocketAddress, connectTimeout: Int) {
socket.connect(address, connectTimeout)
}
public void connect(SocketAddress endpoint, int timeout) throws IOException {
InetSocketAddress epoint = (InetSocketAddress) endpoint;
InetAddress addr = epoint.getAddress ();
int port = epoint.getPort();
checkAddress(addr, "connect");
SecurityManager security = System.getSecurityManager();
if (security != null) {
if (epoint.isUnresolved())
security.checkConnect(epoint.getHostName(), port);
else
security.checkConnect(addr.getHostAddress(), port);
}
if (!created)
createImpl(true);
if (!oldImpl)
impl.connect(epoint, timeout);
else if (timeout == 0) {
if (epoint.isUnresolved())
impl.connect(addr.getHostName(), port);
else
impl.connect(addr, port);
} else
throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)");
connected = true;
bound = true;
}
1.首先通過SecurityManager 校驗(yàn)即將鏈接的服務(wù)器地址和端口號是否合,不合法則直接拋出異常
2.如果沒有創(chuàng)建過购撼,則調(diào)用
createImpl
創(chuàng)建一個(gè)底層的Socket對象3.如果不是使用以前的Socket的對象竭宰,也就是
oldImpl
為false,那么就調(diào)用SocksSocketImpl
的connect
進(jìn)行鏈接份招。
3.1.Socket createImpl 創(chuàng)建一個(gè)底層的Socket對象
void createImpl(boolean stream) throws SocketException {
if (impl == null)
setImpl();
try {
impl.create(stream);
created = true;
} catch (IOException e) {
throw new SocketException(e.getMessage());
}
}
核心還是SocksSocketImpl
的create
方法。
3.2.AbstractPlainSocketImpl create
文件:/libcore/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java
protected synchronized void create(boolean stream) throws IOException {
this.stream = stream;
if (!stream) {
ResourceManager.beforeUdpCreate();
try {
socketCreate(false);
} catch (IOException ioe) {
...
}
} else {
socketCreate(true);
}
if (socket != null)
socket.setCreated();
if (serverSocket != null)
serverSocket.setCreated();
if (fd != null && fd.valid()) {
guard.open("close");
}
}
核心是由PlainSocketImpl
實(shí)現(xiàn)的抽象方法socketCreate
完成的狞甚。
void socketCreate(boolean isStream) throws IOException {
fd.setInt$(IoBridge.socket(AF_INET6, isStream ? SOCK_STREAM : SOCK_DGRAM, 0).getInt$());
if (serverSocket != null) {
IoUtils.setBlocking(fd, false);
IoBridge.setSocketOption(fd, SO_REUSEADDR, true);
}
}
通過IoBridge.socket
創(chuàng)建一個(gè)socket對象后锁摔,并返回這個(gè)對象對應(yīng)的文件描述符具柄設(shè)置到PlainSocketImpl
中緩存的文件描述符對象.注意這個(gè)過程寫死了AF_INET6
作為ip地址族傳入。說明必定是可以接受ipv6協(xié)議的地址數(shù)據(jù)哼审。
public static FileDescriptor socket(int domain, int type, int protocol) throws SocketException {
FileDescriptor fd;
try {
fd = Libcore.os.socket(domain, type, protocol);
return fd;
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsSocketException();
}
}
注意這個(gè)方法是一個(gè)native方法谐腰,我們?nèi)ハ旅嫖募校?br> libcore/luni/src/main/native/libcore_io_Linux.cpp
static jobject Linux_socket(JNIEnv* env, jobject, jint domain, jint type, jint protocol) {
if (domain == AF_PACKET) {
protocol = htons(protocol); // Packet sockets specify the protocol in host byte order.
}
int fd = throwIfMinusOne(env, "socket", TEMP_FAILURE_RETRY(socket(domain, type, protocol)));
return fd != -1 ? jniCreateFileDescriptor(env, fd) : NULL;
}
能看到實(shí)際上就是調(diào)用了系統(tǒng)調(diào)用socket
為套接字創(chuàng)建一個(gè)對應(yīng)的文件描述符后,返回一個(gè)Java的FileDescriptor 對象返回涩盾。
4.Socket系統(tǒng)調(diào)用
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
int retval;
struct socket *sock;
int flags;
...
flags = type & ~SOCK_TYPE_MASK;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
type &= SOCK_TYPE_MASK;
if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;
out_release:
sock_release(sock);
return retval;
}
分為2個(gè)步驟:
- 1.sock_create 創(chuàng)建一個(gè)socket 結(jié)構(gòu)體
- 2.sock_map_fd 把結(jié)構(gòu)體和fd具柄關(guān)聯(lián)起來
為了更加清晰的理解socket
結(jié)構(gòu)體的內(nèi)容我們看看這個(gè)結(jié)構(gòu)體都包含了什么字段十气?
struct socket {
socket_state state;
kmemcheck_bitfield_begin(type);
short type;
kmemcheck_bitfield_end(type);
unsigned long flags;
struct socket_wq __rcu *wq;
struct file *file;
struct sock *sk;
const struct proto_ops *ops;
};
- 1.socket_state 當(dāng)前socket的枚舉狀態(tài)
- 2.type 當(dāng)前socket對應(yīng)的類型
- 3.flag 當(dāng)前socket帶上的標(biāo)識為
- 4.socket中對應(yīng)的等待隊(duì)列
- 5.file socket對應(yīng)的文件描述符
- 6.sock 結(jié)構(gòu)體是指socket中更為核心的具體操作函數(shù)以及標(biāo)志位
- 7.proto_ops 對應(yīng)的協(xié)議操作結(jié)構(gòu)體
4.1.__sock_create 創(chuàng)建socket結(jié)構(gòu)體
int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)
{
int err;
struct socket *sock;
const struct net_proto_family *pf;
...
err = security_socket_create(family, type, protocol, kern);
if (err)
return err;
sock = sock_alloc();
...
sock->type = type;
...
rcu_read_lock();
pf = rcu_dereference(net_families[family]);
err = -EAFNOSUPPORT;
if (!pf)
goto out_release;
if (!try_module_get(pf->owner))
goto out_release;
/* Now protected by module ref count */
rcu_read_unlock();
err = pf->create(net, sock, protocol, kern);
if (err < 0)
goto out_module_put;
if (!try_module_get(sock->ops->owner))
goto out_module_busy;
module_put(pf->owner);
err = security_socket_post_create(sock, family, type, protocol, kern);
if (err)
goto out_sock_release;
*res = sock;
return 0;
...
}
1.
security_socket_create
通過SELinux進(jìn)行校驗(yàn)。SELinux本質(zhì)上就是在一個(gè)文件中寫好了每一個(gè)進(jìn)程允許做的事情春霍,當(dāng)需要讀取文件數(shù)據(jù)砸西,socket等敏感操作時(shí)候?qū)M(jìn)行一次check≈啡澹可以通過security_register 進(jìn)行注冊芹枷。- 一旦校驗(yàn)通過后,則會調(diào)用
sock_alloc
創(chuàng)建一個(gè)sock
結(jié)構(gòu)體莲趣,該并在結(jié)構(gòu)體記錄當(dāng)前type鸳慈,常見的數(shù)據(jù)類型如下:
- 一旦校驗(yàn)通過后,則會調(diào)用
enum sock_type {
SOCK_STREAM = 1,
SOCK_DGRAM = 2,
SOCK_RAW = 3,
...
}
SOCK_STREAM
是指面向數(shù)據(jù)流屬于TCP協(xié)議;SOCK_DGRAM
面向數(shù)據(jù)報(bào)文屬于UDP協(xié)議喧伞;SOCK_RAW
是指原始ip包
- rcu_dereference 進(jìn)行rcu(Read-Copy-update)模式保護(hù)當(dāng)前net_families (地址族)數(shù)組中對應(yīng)引用的指針走芋。
rcu
實(shí)際上就是我之前說過的一種特殊的線程設(shè)計(jì)绩郎,任意讀取數(shù)據(jù),寫時(shí)候需要進(jìn)行同步的方式翁逞。當(dāng)讀取的情況比較多的時(shí)候肋杖,就可以采用這種方式。 注意net_families
是指當(dāng)前的傳遞下來的domain
,也就是這是ipv4
還是ipv6
族熄攘。
- rcu_dereference 進(jìn)行rcu(Read-Copy-update)模式保護(hù)當(dāng)前net_families (地址族)數(shù)組中對應(yīng)引用的指針走芋。
4.調(diào)用
net_families
中對應(yīng)族的的create方法迈着。
在這里涉及到了幾個(gè)比較核心結(jié)構(gòu)體。
4.1.1.socket 結(jié)構(gòu)體的創(chuàng)建與socket 內(nèi)核模塊的初始化
static struct socket *sock_alloc(void)
{
struct inode *inode;
struct socket *sock;
inode = new_inode_pseudo(sock_mnt->mnt_sb);
if (!inode)
return NULL;
sock = SOCKET_I(inode);
kmemcheck_annotate_bitfield(sock, type);
inode->i_ino = get_next_ino();
inode->i_mode = S_IFSOCK | S_IRWXUGO;
inode->i_uid = current_fsuid();
inode->i_gid = current_fsgid();
inode->i_op = &sockfs_inode_ops;
this_cpu_add(sockets_in_use, 1);
return sock;
}
- 1.new_inode_pseudo 通過虛擬文件系統(tǒng)創(chuàng)建一個(gè)inode對象掩驱。注意這里是通過結(jié)構(gòu)體為名為
sock_mnt
的vfsmount
創(chuàng)建一個(gè)inode
-
SOCKET_I
從inode中創(chuàng)建socket結(jié)構(gòu)體降淮。
-
4.1.2.socket模塊的初始化以及對應(yīng)vfsmount生成原理
關(guān)于sock_mnt
初始化,在socket內(nèi)核模塊加載時(shí)候就加載好了:
static int __init sock_init(void)
{
int err;
err = net_sysctl_init();
if (err)
goto out;
skb_init();
init_inodecache();
err = register_filesystem(&sock_fs_type);
if (err)
goto out_fs;
sock_mnt = kern_mount(&sock_fs_type);
...
}
這個(gè)過程實(shí)際上就是初始化好socket對應(yīng)的虛擬文件系統(tǒng)操作,把sock_fs_type
注冊在虛擬文件系統(tǒng)中哲思,并通過kern_mount 調(diào)用操作結(jié)構(gòu)體的mount
指針掛在在系統(tǒng)中洼畅,返回mnt
結(jié)構(gòu)體掛載對象進(jìn)行操作。
static struct file_system_type sock_fs_type = {
.name = "sockfs",
.mount = sockfs_mount,
.kill_sb = kill_anon_super,
};
結(jié)合第一段代碼棚赔,可以得知帝簇,實(shí)際上整個(gè)socket內(nèi)核模塊初始化調(diào)用的就是sockfs_mount
。
static struct dentry *sockfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_pseudo(fs_type, "socket:", &sockfs_ops,
&sockfs_dentry_operations, SOCKFS_MAGIC);
}
static const struct super_operations sockfs_ops = {
.alloc_inode = sock_alloc_inode,
.destroy_inode = sock_destroy_inode,
.statfs = simple_statfs,
};
static const struct dentry_operations sockfs_dentry_operations = {
.d_dname = sockfs_dname,
};
mount_pseudo
會為當(dāng)前socket對應(yīng)的超級塊設(shè)置一套操作結(jié)構(gòu)體靠益,生成對應(yīng)的inode的時(shí)候會先調(diào)用dentry_operations
生成一個(gè)個(gè)detry(你可以看成文件路徑),接著調(diào)用sockfs_ops
的alloc_inode
生成inode丧肴。
4.1.3.sock_alloc_inode 生成對應(yīng)的inode
static struct inode *sock_alloc_inode(struct super_block *sb)
{
struct socket_alloc *ei;
struct socket_wq *wq;
ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);
if (!ei)
return NULL;
wq = kmalloc(sizeof(*wq), GFP_KERNEL);
if (!wq) {
kmem_cache_free(sock_inode_cachep, ei);
return NULL;
}
init_waitqueue_head(&wq->wait);
wq->fasync_list = NULL;
RCU_INIT_POINTER(ei->socket.wq, wq);
ei->socket.state = SS_UNCONNECTED;
ei->socket.flags = 0;
ei->socket.ops = NULL;
ei->socket.sk = NULL;
ei->socket.file = NULL;
return &ei->vfs_inode;
}
這個(gè)過程實(shí)際上從快速緩存中生成一個(gè)socket_alloc
對象,這個(gè)對象中持有inode結(jié)構(gòu)體胧后。并初始化對應(yīng)的那個(gè)等待隊(duì)列芋浮。
static inline struct socket *SOCKET_I(struct inode *inode)
{
return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}
當(dāng)需要獲取對應(yīng)的socket結(jié)構(gòu)體時(shí)候,就會通過inode
反過來查找socket_alloc
中的socket
.
struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};
4.1.4.Linux內(nèi)核網(wǎng)絡(luò)模塊初始化 net_families 地址族的注冊
知道socket結(jié)構(gòu)體是如何生成的壳快。再來關(guān)注net_families
是什么時(shí)候注冊的纸巷。地址族是什么?顧名思義就是指地址類別眶痰,比如ipv4瘤旨,ipv6等地址類型。
對應(yīng)ipv4 內(nèi)核模塊的注冊代碼:
static int __init inet_init(void)
{
struct inet_protosw *q;
struct list_head *r;
int rc = -EINVAL;
rc = proto_register(&tcp_prot, 1);
if (rc)
goto out;
rc = proto_register(&udp_prot, 1);
if (rc)
goto out_unregister_tcp_proto;
rc = proto_register(&raw_prot, 1);
if (rc)
goto out_unregister_udp_proto;
rc = proto_register(&ping_prot, 1);
if (rc)
goto out_unregister_raw_proto;
/*
* Tell SOCKET that we are alive...
*/
(void)sock_register(&inet_family_ops);
#ifdef CONFIG_SYSCTL
ip_static_sysctl_init();
#endif
/*
* Add all the base protocols.
*/
if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
pr_crit("%s: Cannot add ICMP protocol\n", __func__);
if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
pr_crit("%s: Cannot add UDP protocol\n", __func__);
if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
pr_crit("%s: Cannot add TCP protocol\n", __func__);
for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
INIT_LIST_HEAD(r);
for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
inet_register_protosw(q);
arp_init();
ip_init();
tcp_v4_init();
tcp_init();
udp_init();
udplite4_register();
ping_init();
...
ipfrag_init();
dev_add_pack(&ip_packet_type);
rc = 0;
...
}
fs_initcall(inet_init);
這一段其實(shí)就是Linux內(nèi)核對網(wǎng)絡(luò)模塊的初始化:
- proto_register 將
tcp_prot
(tcp協(xié)議),udp_prot
(udp協(xié)議),raw_prot
(原始ip包),ping_prot
( ping 協(xié)議) 結(jié)構(gòu)體注冊到全局變量proto_list
竖伯。
- proto_register 將
簡單的看看tcp_prot
結(jié)構(gòu)體內(nèi)容:
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v4_do_rcv,
.release_cb = tcp_release_cb,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.stream_memory_free = tcp_stream_memory_free,
.sockets_allocated = &tcp_sockets_allocated,
.orphan_count = &tcp_orphan_count,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock),
.slab_flags = SLAB_DESTROY_BY_RCU,
.twsk_prot = &tcp_timewait_sock_ops,
.rsk_prot = &tcp_request_sock_ops,
.h.hashinfo = &tcp_hashinfo,
.no_autobind = true,
...
};
在這個(gè)結(jié)構(gòu)體定義了tcp協(xié)議的面向網(wǎng)絡(luò)鏈接的操作符
2.
sock_register
初始化socket模塊以及協(xié)議族存哲,從嚴(yán)格意義來說在proto_register
注冊過程中已經(jīng)完成了常用協(xié)議的注冊。3.
inet_add_protocol
將icmp_protocol
,udp_protocol
,tcp_protocol
等回調(diào)協(xié)議添加到inet_protos
鏈表中黔夭。
int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol)
{
...
return !cmpxchg((const struct net_protocol **)&inet_protos[protocol],
NULL, prot) ? 0 : -1;
}
我們調(diào)tcp協(xié)議對應(yīng)的tcp_protocol
結(jié)構(gòu)體看看里面定義了什么操作函數(shù):
static const struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.no_policy = 1,
.netns_ok = 1,
.icmp_strict_tag_validation = 1,
};
主要函數(shù)handler
所對應(yīng)的tcp_v4_rcv
方法宏胯。這個(gè)方法決定了tcp協(xié)議數(shù)據(jù)從另一端到來后的操作。
- 遍歷
inetsw_array
數(shù)組本姥,把每個(gè)元素通過inet_register_protosw
方法注冊到inetsw
數(shù)組中肩袍。
- 遍歷
來看看inetsw_array
內(nèi)容:
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.flags = INET_PROTOSW_PERMANENT,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_ICMP,
.prot = &ping_prot,
.ops = &inet_dgram_ops,
.flags = INET_PROTOSW_REUSE,
},
{
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = &raw_prot,
.ops = &inet_sockraw_ops,
.flags = INET_PROTOSW_REUSE,
}
};
這個(gè)數(shù)組決定了tcp層協(xié)議的操作符,協(xié)議類型婚惫,以及協(xié)議面向外部的模塊處理方法氛赐。
舉一個(gè)tcp的例子:
- 類型 為SOCK_STREAM 代表是面向數(shù)據(jù)流
- protocol 為
IPPROTO_TCP
代表當(dāng)前協(xié)議是tcp協(xié)議 - prot 為
tcp_prot
就是tcp協(xié)議的特殊操作方法 - ops 為
inet_stream_ops
是指當(dāng)前數(shù)據(jù)流對應(yīng)的文件描述符中復(fù)寫的操作是什么 - flags 是指當(dāng)前的協(xié)議狀態(tài)魂爪。
INET_PROTOSW_PERMANENT
是指永久不變的協(xié)議,INET_PROTOSW_REUSE
是指該協(xié)議會復(fù)用端口艰管。
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
.splice_read = tcp_splice_read,
#ifdef CONFIG_COMPAT
...
#endif
};
從這個(gè)結(jié)構(gòu)體中滓侍,我們就能大致猜到整個(gè)tcp的設(shè)計(jì)框架了。必定是先找到socket文件描述符中對應(yīng)的協(xié)議文件描述符inet_stream_ops
,接著找到tcp_prot
結(jié)構(gòu)體進(jìn)行進(jìn)一步的處理牲芋。
- arp_init 對數(shù)據(jù)鏈路層的arp協(xié)議相關(guān)的鄰居表進(jìn)行初始化撩笆。
- ip_init 對網(wǎng)絡(luò)層的ip模塊的初始化,在這里初始化ip對應(yīng)的route_table 內(nèi)存缸浦。
- tcp_v4_init 與 tcp_init 可以看作一個(gè)整體都是初始化tcp模塊夕冲。
tcp_init
初始化了inet_hashinfo
結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體實(shí)際上就是用于管理分配給socket端口裂逐。該結(jié)構(gòu)體會通過一個(gè)哈希表對端口進(jìn)行一次管理歹鱼。
- tcp_v4_init 與 tcp_init 可以看作一個(gè)整體都是初始化tcp模塊夕冲。
-
tcp_init
方法則是初始化請求需要的內(nèi)存,以及inet_hashinfo
中的hash表卜高, 并計(jì)算之后每一個(gè)請求和接受的臨時(shí)緩沖區(qū)計(jì)算好大小閾值弥姻。發(fā)送緩沖區(qū)為16kb,接受緩沖區(qū)大小約為85kb掺涛。
-
- 9.
udp_init
初始化了UDP需要的內(nèi)存閾值庭敦,udplite4_register
則是注冊一個(gè)全新的UDLITE協(xié)議到內(nèi)核中。在這個(gè)過程就能看到內(nèi)核添加一個(gè)自定義協(xié)議的原始三步驟:proto_register
,inet_add_protocol
,inet_register_protosw
void __init udplite4_register(void)
{
udp_table_init(&udplite_table, "UDP-Lite");
if (proto_register(&udplite_prot, 1))
goto out_register_err;
if (inet_add_protocol(&udplite_protocol, IPPROTO_UDPLITE) < 0)
goto out_unregister_proto;
inet_register_protosw(&udplite4_protosw);
....
}
-
ping_init
對Ping協(xié)議進(jìn)行初始化
-
-
ipfrag_init
初始化inet_frags
結(jié)構(gòu)體薪缆。該結(jié)構(gòu)體實(shí)際上負(fù)責(zé)了整個(gè)數(shù)據(jù)流臨時(shí)緩沖區(qū)的分片螺捐。
-
我們把重心放在sock_register
中。
4.1.5.sock_register 注冊創(chuàng)建地址族結(jié)構(gòu)體
int sock_register(const struct net_proto_family *ops)
{
int err;
if (ops->family >= NPROTO) {
return -ENOBUFS;
}
spin_lock(&net_family_lock);
if (rcu_dereference_protected(net_families[ops->family],
lockdep_is_held(&net_family_lock)))
err = -EEXIST;
else {
rcu_assign_pointer(net_families[ops->family], ops);
err = 0;
}
spin_unlock(&net_family_lock);
return err;
}
能看到是做了一個(gè)rcu的鎖進(jìn)行保護(hù)后矮燎,把net_proto_family
注冊到net_families
數(shù)組中。
來看看注冊的對象赔癌,family
的字段為PF_INET
诞外,能通過net_families
尋找下標(biāo)為PF_INET
找到inet_family_ops
.這樣就注冊到socket模塊中。
static const struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
在socket進(jìn)行初始化的時(shí)候灾票,調(diào)用了net_families
中對應(yīng)family下的net_proto_family
峡谊,設(shè)置的是AF_INET6
的參數(shù)。說明是進(jìn)入了對應(yīng)的ipv6的內(nèi)核模塊進(jìn)行創(chuàng)建刊苍,我們來看看ipv6內(nèi)核模塊加載的核心方法既们。
這里注意,如果你去看源碼你會發(fā)現(xiàn)在Java源碼中對應(yīng)AF_INET6
是一個(gè)placeholder方法返回的默認(rèn)數(shù)值是0.實(shí)際上這個(gè)過程是JVM加載的時(shí)候設(shè)置進(jìn)去的正什。而設(shè)置的數(shù)據(jù)可以打印AF_INET6
出來 也是對應(yīng)上內(nèi)核AF_INET6
一樣的數(shù)值10
4.1.6.ipv6 ip地址族內(nèi)核模塊初始化
static int __init inet6_init(void)
{
...
err = sock_register(&inet6_family_ops);
...
...
}
module_init(inet6_init);
注冊這個(gè)結(jié)構(gòu)體:
static const struct net_proto_family inet6_family_ops = {
.family = PF_INET6,
.create = inet6_create,
.owner = THIS_MODULE,
};
說明此時(shí)注冊在socket模塊中net_families
對應(yīng)下標(biāo)是PF_INET6
的創(chuàng)建協(xié)議族方法啥纸。
我們回到最初的__sock_create
方法創(chuàng)建socket結(jié)構(gòu)體的下面這段話
err = pf->create(net, sock, protocol, kern);
就能明白,實(shí)際上就是調(diào)用了inet6_create
方法婴氮。
4.1.7.inet6_create 創(chuàng)建ipv6的協(xié)議族
文件:/net/ipv6/af_inet6.c
注意此時(shí)傳下來的參數(shù)是socket結(jié)構(gòu)體斯棒,socket->type
為SOCK_STREAM
(此時(shí)我們討論的是TCP)
static int inet6_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
struct inet_sock *inet;
struct ipv6_pinfo *np;
struct sock *sk;
struct inet_protosw *answer;
struct proto *answer_prot;
unsigned char answer_flags;
int try_loading_module = 0;
int err;
...
err = -ESOCKTNOSUPPORT;
rcu_read_lock();
list_for_each_entry_rcu(answer, &inetsw6[sock->type], list) {
err = 0;
/* Check the non-wild match. */
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP)
break;
} else {
/* Check for the two wild cases. */
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
if (IPPROTO_IP == answer->protocol)
break;
}
err = -EPROTONOSUPPORT;
}
if (err) {
...
}
err = -EPERM;
if (sock->type == SOCK_RAW && !kern && !capable(CAP_NET_RAW))
goto out_rcu_unlock;
sock->ops = answer->ops;
answer_prot = answer->prot;
answer_flags = answer->flags;
rcu_read_unlock();
WARN_ON(answer_prot->slab == NULL);
err = -ENOBUFS;
sk = sk_alloc(net, PF_INET6, GFP_KERNEL, answer_prot);
if (sk == NULL)
goto out;
sock_init_data(sock, sk);
err = 0;
if (INET_PROTOSW_REUSE & answer_flags)
sk->sk_reuse = SK_CAN_REUSE;
inet = inet_sk(sk);
inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
if (SOCK_RAW == sock->type) {
inet->inet_num = protocol;
if (IPPROTO_RAW == protocol)
inet->hdrincl = 1;
}
sk->sk_destruct = inet_sock_destruct;
sk->sk_family = PF_INET6;
sk->sk_protocol = protocol;
sk->sk_backlog_rcv = answer->prot->backlog_rcv;
inet_sk(sk)->pinet6 = np = inet6_sk_generic(sk);
np->hop_limit = -1;
np->mcast_hops = IPV6_DEFAULT_MCASTHOPS;
np->mc_loop = 1;
np->pmtudisc = IPV6_PMTUDISC_WANT;
sk->sk_ipv6only = net->ipv6.sysctl.bindv6only;
...
if (inet->inet_num) {
inet->inet_sport = htons(inet->inet_num);
sk->sk_prot->hash(sk);
}
if (sk->sk_prot->init) {
err = sk->sk_prot->init(sk);
if (err) {
sk_common_release(sk);
goto out;
}
}
...
}
- 1.遍歷保存在
inetsw6
的協(xié)議列表中對應(yīng)type的協(xié)議結(jié)構(gòu)體,賦值到answer中盾致。對應(yīng)在ipv6中的tcp協(xié)議結(jié)構(gòu)體是如下:
static struct inet_protosw tcpv6_protosw = {
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcpv6_prot,
.ops = &inet6_stream_ops`,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
};
此時(shí)socket
結(jié)構(gòu)體中的ops就被替換成了inet6_stream_ops
,如果是v4,則是替換成inet_stream_ops
.
- 2.通過
sk_alloc
方法創(chuàng)建sock結(jié)構(gòu)體荣暮,并且把tcpv6_prot
賦值給sock
結(jié)構(gòu)體.并記錄當(dāng)前的協(xié)議類型庭惜,sock_init_data
則初始化sock的的關(guān)鍵操作。
void sock_init_data(struct socket *sock, struct sock *sk)
{
skb_queue_head_init(&sk->sk_receive_queue);
skb_queue_head_init(&sk->sk_write_queue);
skb_queue_head_init(&sk->sk_error_queue);
sk->sk_send_head = NULL;
init_timer(&sk->sk_timer);
sk->sk_allocation = GFP_KERNEL;
sk->sk_rcvbuf = sysctl_rmem_default;
sk->sk_sndbuf = sysctl_wmem_default;
sk->sk_state = TCP_CLOSE;
sk_set_socket(sk, sock);
sock_set_flag(sk, SOCK_ZAPPED);
if (sock) {
sk->sk_type = sock->type;
sk->sk_wq = sock->wq;
sock->sk = sk;
} else
sk->sk_wq = NULL;
spin_lock_init(&sk->sk_dst_lock);
rwlock_init(&sk->sk_callback_lock);
lockdep_set_class_and_name(&sk->sk_callback_lock,
af_callback_keys + sk->sk_family,
af_family_clock_key_strings[sk->sk_family]);
sk->sk_state_change = sock_def_wakeup;
sk->sk_data_ready = sock_def_readable;
sk->sk_write_space = sock_def_write_space;
sk->sk_error_report = sock_def_error_report;
sk->sk_destruct = sock_def_destruct;
sk->sk_frag.page = NULL;
sk->sk_frag.offset = 0;
sk->sk_peek_off = -1;
sk->sk_peer_pid = NULL;
sk->sk_peer_cred = NULL;
sk->sk_write_pending = 0;
sk->sk_rcvlowat = 1;
sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT;
sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;
sk->sk_stamp = ktime_set(-1L, 0);
#ifdef CONFIG_NET_RX_BUSY_POLL
sk->sk_napi_id = 0;
sk->sk_ll_usec = sysctl_net_busy_read;
#endif
sk->sk_max_pacing_rate = ~0U;
sk->sk_pacing_rate = ~0U;
/*
* Before updating sk_refcnt, we must commit prior changes to memory
* (Documentation/RCU/rculist_nulls.txt for details)
*/
smp_wmb();
atomic_set(&sk->sk_refcnt, 1);
atomic_set(&sk->sk_drops, 0);
}
EXPORT_SYMBOL(sock_init_data);
struct proto tcpv6_prot = {
.name = "TCPv6",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v6_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v6_init_sock,
.destroy = tcp_v6_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v6_do_rcv,
.release_cb = tcp_release_cb,
.hash = tcp_v6_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.stream_memory_free = tcp_stream_memory_free,
.sockets_allocated = &tcp_sockets_allocated,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.orphan_count = &tcp_orphan_count,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp6_sock),
.slab_flags = SLAB_DESTROY_BY_RCU,
.twsk_prot = &tcp6_timewait_sock_ops,
.rsk_prot = &tcp6_request_sock_ops,
.h.hashinfo = &tcp_hashinfo,
.no_autobind = true,
...
};
- 3.調(diào)用了sk_prot的init方法穗酥。其實(shí)對應(yīng)就是
tcpv6_prot
的init方法护赊。
4.1.8.sk_alloc 創(chuàng)建sock結(jié)構(gòu)體
struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
struct proto *prot)
{
struct sock *sk;
sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
if (sk) {
sk->sk_family = family;
sk->sk_prot = sk->sk_prot_creator = prot;
}
return sk;
}
EXPORT_SYMBOL(sk_alloc);
這里值得注意的是sock結(jié)構(gòu)體真正進(jìn)行初始化的是通過 sk_prot_alloc
,當(dāng)初始化結(jié)束后砾跃,才對sock結(jié)構(gòu)體中的數(shù)據(jù)進(jìn)行賦值骏啰。注意在這里sock
結(jié)構(gòu)體把對應(yīng)協(xié)議proto
結(jié)構(gòu)體保存在sk_prot
字段中。
static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,
int family)
{
struct sock *sk;
struct kmem_cache *slab;
slab = prot->slab;
if (slab != NULL) {
...
} else
sk = kmalloc(prot->obj_size, priority);
...
return sk;
...
}
能看到是通過kmalloc
從高速緩沖區(qū)中初始化一段結(jié)構(gòu)體大小為obj_size
的內(nèi)存蜓席。這個(gè)大小是什么呢器一?
實(shí)際上并非是一致暴露在我們眼中sock
結(jié)構(gòu)體而是tcp6_sock
.obj_size = sizeof(struct tcp6_sock),
只是這個(gè)結(jié)構(gòu)體擁有了sock
結(jié)構(gòu)體所有的字段,且內(nèi)存結(jié)構(gòu)一致而直接轉(zhuǎn)化成sock
結(jié)構(gòu)體.
struct tcp6_sock {
struct tcp_sock tcp;
/* ipv6_pinfo has to be the last member of tcp6_sock, see inet6_sk_generic */
struct ipv6_pinfo inet6;
};
能看到這個(gè)tcp6_sock
結(jié)構(gòu)體包含了tcp_sock
真正包含sock結(jié)構(gòu)體和一個(gè)ipv6_pinfo
ipv6地址內(nèi)容的結(jié)構(gòu)體.
讓我們繼續(xù)深挖tcp_sock
的內(nèi)存結(jié)構(gòu):
文件:/include/linux/tcp.h
struct tcp_sock {
/* inet_connection_sock has to be the first member of tcp_sock */
struct inet_connection_sock inet_conn;
...
};
但是還不夠清晰厨内,繼續(xù)深挖祈秕,因?yàn)榭梢灾苯訜o縫強(qiáng)轉(zhuǎn)sock
結(jié)構(gòu)體必定包含這個(gè)結(jié)構(gòu)體在inet_connection_sock
中。
文件:/include/net/inet_connection_sock.h
struct inet_connection_sock {
/* inet_sock has to be the first member! */
struct inet_sock icsk_inet;
...
};
文件:/include/net/inet_sock.h
struct inet_sock {
/* sk and pinet6 has to be the first two members of inet_sock */
struct sock sk;
...
};
終于看到了雏胃,sock
結(jié)構(gòu)體在這里请毛。實(shí)際上這個(gè)思想想不想我們java的繼承呢?只是內(nèi)存結(jié)構(gòu)上有一定要求而已瞭亮。
4.1.9.tcpv6_prot init
文件:/net/ipv6/tcp_ipv6.c
static int tcp_v6_init_sock(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
tcp_init_sock(sk);
icsk->icsk_af_ops = &ipv6_specific;
return 0;
}
1.inet_csk 其實(shí)就是相當(dāng)于把
sock
強(qiáng)轉(zhuǎn)成inet_connection_sock
方仿。因?yàn)?code>inet_connection_sock結(jié)構(gòu)體內(nèi)存結(jié)構(gòu)前半段和sock
一致,因此可以無縫轉(zhuǎn)化统翩。2.tcp_init_sock 初始化
inet_connection_sock
結(jié)構(gòu)體
void tcp_init_sock(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
__skb_queue_head_init(&tp->out_of_order_queue);
tcp_init_xmit_timers(sk);
tcp_prequeue_init(tp);
INIT_LIST_HEAD(&tp->tsq_node);
icsk->icsk_rto = TCP_TIMEOUT_INIT;
tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT);
...
icsk->icsk_sync_mss = tcp_sync_mss;
sk->sk_sndbuf = sysctl_tcp_wmem[1];
sk->sk_rcvbuf = sysctl_tcp_rmem[1];
...
}
在這里轉(zhuǎn)化sock結(jié)構(gòu)體為tcp_sock
和inet_connection_sock
.
為tcp_sock
設(shè)置sk_buf
的緩存隊(duì)列仙蚜,設(shè)置tcp一次允許超時(shí)的時(shí)間為1000個(gè)時(shí)間鐘;初始化tcp_sock
的out_of_order_queue
隊(duì)列厂汗,初始化sk_buff_head
scok預(yù)緩沖數(shù)據(jù)包隊(duì)列
為sock
結(jié)構(gòu)體設(shè)置定時(shí)器,設(shè)置之前計(jì)算好的接受和發(fā)送緩沖區(qū)的大小委粉。
4.2.sock_map_fd 把socket結(jié)構(gòu)體和文件描述符關(guān)聯(lián)起來
static int sock_map_fd(struct socket *sock, int flags)
{
struct file *newfile;
int fd = get_unused_fd_flags(flags);
if (unlikely(fd < 0))
return fd;
newfile = sock_alloc_file(sock, flags, NULL);
if (likely(!IS_ERR(newfile))) {
fd_install(fd, newfile);
return fd;
}
put_unused_fd(fd);
return PTR_ERR(newfile);
}
核心就是調(diào)用了sock_alloc_file
方法,為socket結(jié)構(gòu)體創(chuàng)建一個(gè)file結(jié)構(gòu)體娶桦。
在看socket對應(yīng)的文件描述符的生成之前贾节,我們需要補(bǔ)充如下的知識點(diǎn):
err = register_filesystem(&sock_fs_type);
if (err)
goto out_fs;
sock_mnt = kern_mount(&sock_fs_type);
static struct file_system_type sock_fs_type = {
.name = "sockfs",
.mount = sockfs_mount,
.kill_sb = kill_anon_super,
};
從這兩個(gè)代碼段,可以得知衷畦。socket模塊在初始化時(shí)候會加載一個(gè)結(jié)構(gòu)體為file_system_type
栗涂。這個(gè)結(jié)構(gòu)體是用于注冊VFS 也就是虛擬文件系統(tǒng)的類型結(jié)構(gòu)體。
這里的含義是祈争,注冊并掛載了sockfs
虛擬文件系統(tǒng)斤程。而在下面的sock_alloc_file
所創(chuàng)建的socket文件描述符就是創(chuàng)建在這個(gè)虛擬文件系統(tǒng)中。
struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
{
struct qstr name = { .name = "" };
struct path path;
struct file *file;
if (dname) {
name.name = dname;
name.len = strlen(name.name);
} else if (sock->sk) {
name.name = sock->sk->sk_prot_creator->name;
name.len = strlen(name.name);
}
path.dentry = d_alloc_pseudo(sock_mnt->mnt_sb, &name);
...
path.mnt = mntget(sock_mnt);
d_instantiate(path.dentry, SOCK_INODE(sock));
SOCK_INODE(sock)->i_fop = &socket_file_ops;
file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
&socket_file_ops);
if (unlikely(IS_ERR(file))) {
/* drop dentry, keep inode */
ihold(path.dentry->d_inode);
path_put(&path);
return file;
}
sock->file = file;
file->f_flags = O_RDWR | (flags & O_NONBLOCK);
file->private_data = sock;
return file;
}
注意這里面sk->sk_prot_creator
是指inet6_create
中調(diào)用的sk_alloc
菩混,在此時(shí)就是指tcpv6_prot
.
注意在socket內(nèi)存文件申請時(shí)候暖释,使用socket文件系統(tǒng)中袭厂,對應(yīng)mount掛載對象生成對應(yīng)的內(nèi)存文件路徑。
核心源碼如下球匕;
static char *sockfs_dname(struct dentry *dentry, char *buffer, int buflen)
{
return dynamic_dname(dentry, buffer, buflen, "socket:[%lu]",
dentry->d_inode->i_ino);
}
文件名為socket:[inode號]
纹磺。
當(dāng)生成了socket對應(yīng)的file內(nèi)存文件后,就會保存到socket
結(jié)構(gòu)體中亮曹。
在Linux內(nèi)核初始化時(shí)候橄杨,會初始化Socket內(nèi)核模塊對應(yīng)的自定義文件系統(tǒng)(file_system_type
) sock_fs_type
結(jié)構(gòu)體。
static struct file_system_type sock_fs_type = {
.name = "sockfs",
.mount = sockfs_mount,
.kill_sb = kill_anon_super,
};
該結(jié)構(gòu)體定義了掛載函數(shù)照卦,文件系統(tǒng)名式矫,刪除數(shù)據(jù)時(shí)候?qū)Τ墘K的清理操作
static const struct super_operations sockfs_ops = {
.alloc_inode = sock_alloc_inode,
.destroy_inode = sock_destroy_inode,
.statfs = simple_statfs,
};
而這種文件系統(tǒng)實(shí)際上設(shè)置的就是對超級塊的操作。當(dāng)掛載成功后役耕,又注冊了如下的信息:
static const struct super_operations sockfs_ops = {
.alloc_inode = sock_alloc_inode,
.destroy_inode = sock_destroy_inode,
.statfs = simple_statfs,
};
/*
* sockfs_dname() is called from d_path().
*/
static char *sockfs_dname(struct dentry *dentry, char *buffer, int buflen)
{
return dynamic_dname(dentry, buffer, buflen, "socket:[%lu]",
dentry->d_inode->i_ino);
}
static const struct dentry_operations sockfs_dentry_operations = {
.d_dname = sockfs_dname,
};
static struct dentry *sockfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_pseudo(fs_type, "socket:", &sockfs_ops,
&sockfs_dentry_operations, SOCKFS_MAGIC);
}
此時(shí)就定義了文件目錄對象操作dentry_operations
這里面決定了目錄名為對應(yīng)socket:[inode]
號采转,并且設(shè)置了sockfs_ops
申請inode
的操作對象。
小結(jié)
到這里就聊完了客戶端對應(yīng)的socket是如何通過socket系統(tǒng)調(diào)用初始化的瞬痘。能看到在創(chuàng)建socket的時(shí)候故慈,內(nèi)核創(chuàng)建的結(jié)構(gòu)體嵌套層級十分多,在這里先中斷源碼解析流程框全,進(jìn)行一次總結(jié)察绷。
在Linux內(nèi)核中,無法直接訪問socket所對應(yīng)的文件描述符津辩,因?yàn)閷?yīng)socket的file_operation
是禁止的拆撼。只能通過socket系統(tǒng)調(diào)用訪問socket對應(yīng)的socket描述符。
在內(nèi)核啟動(dòng)初期喘沿,會啟動(dòng)socket內(nèi)核模塊闸度,并掛載socket內(nèi)核模塊對應(yīng)的文件系統(tǒng)。并初始化默認(rèn)的協(xié)議集蚜印。
當(dāng)Java調(diào)用了Socket.connect 方法后筋岛。一個(gè)socket對象才會開始通過SocksSocketImpl
創(chuàng)建 socket對象。
而這個(gè)方法本質(zhì)上調(diào)用還是socket
系統(tǒng)調(diào)用晒哄。將會創(chuàng)建一個(gè)復(fù)雜且龐大的結(jié)構(gòu)體。
不過我們需要記住一點(diǎn)肪获,無論怎么變都不可能脫離七層網(wǎng)絡(luò)協(xié)議寝凌。socket系統(tǒng)調(diào)用是對傳輸層
,網(wǎng)絡(luò)層
孝赫,數(shù)據(jù)鏈路層
,物理層
的封裝较木。那么相對的,生成出來的socket結(jié)構(gòu)體也是根據(jù)這一層層的設(shè)計(jì)青柄,進(jìn)行封裝的伐债。
暴露在最外層的是socket
結(jié)構(gòu)體,將會根據(jù)設(shè)置的socket類型预侯,從而找到對應(yīng)的地址族以及協(xié)議類型。
在這個(gè)過程中socket
結(jié)構(gòu)體存在如下幾個(gè)核心結(jié)構(gòu)體:
- 1.
sock
socket 持有通用的核心操作以及核心字段 - 2.
inet6_stream_ops
則是不同協(xié)議下不同的操作行為
因此整個(gè)關(guān)系如下圖:
在整個(gè)socket
通信過程峰锁,暴露向外的如上層應(yīng)用層萎馅,或者說是面向開發(fā)者來說是socket
結(jié)構(gòu)體。對于內(nèi)核來說就是sock
結(jié)構(gòu)體虹蒋。值得注意的是糜芳,這兩者之間是互相持有的.sock
通過sk_socket
找到socket
;socket
通過sk
找到sock
.