《Linux/UNIX系統編程手冊》第56章 SOCKET:介紹

關鍵詞:node

 

1. socket基礎

一個典型的客戶端/服務器場景中,應用程序使用socket進行通訊的方式以下:服務器

  • 各個應用程序建立一個socket。socket是一個容許通訊的設備,兩個應用程序都須要用到它。
  • 服務器將本身的socket綁定到一個衆所周知的地址上是的客戶端可以定位到它的位置。

關鍵socket API包括如下下幾種:網絡

  • socketr()建立一個新的socket。
  • bind()將一個socket綁定到一個地址上。一般服務器須要使用這個調用來將其socket綁定到一個衆所周知的地址上使得客戶端可以定位到該socket上。
  • listen()容許一個流socket接受來自其餘socket的接入鏈接。
  • accept()在一個監聽流上接受來自一個對等應用程序的鏈接,並可選地返回對等socket的地址。
  • connect()創建與另外一個socket之間的鏈接。
  • read()/write()/close()基於socket的讀寫和關閉。
  • recv()/send()/recvfrom()/sendto()分別經過socket發送或接收數據,相似read()/write()可是功能更豐富。

1.1 socket API介紹

1.1.1 socket domain

socket存在於一個通訊domain中:識別出一個socket的方法(socket地址格式);通訊範圍(是統一主機不一樣應用之間;仍是一個網絡鏈接的不一樣主機上應用之間)。數據結構

domain都是以AF_開頭,表示Address Family;PF_開頭的表示Protocol Family。less

在socket.h中定義以下,能夠看出AF_和PF_基本一對一。dom

/* Protocol families.  */
#define PF_UNSPEC    0    /* Unspecified.  */
#define PF_LOCAL    1    /* Local to host (pipes and file-domain).  */
#define PF_UNIX        PF_LOCAL /* POSIX name for PF_LOCAL.  */
#define PF_FILE        PF_LOCAL /* Another non-standard name for PF_LOCAL.  */
#define PF_INET        2    /* IP protocol family.  */
#define PF_AX25        3    /* Amateur Radio AX.25.  */
#define PF_IPX        4    /* Novell Internet Protocol.  */
#define PF_APPLETALK    5    /* Appletalk DDP.  */
#define PF_NETROM    6    /* Amateur radio NetROM.  */
#define PF_BRIDGE    7    /* Multiprotocol bridge.  */
#define PF_ATMPVC    8    /* ATM PVCs.  */
#define PF_X25        9    /* Reserved for X.25 project.  */
#define PF_INET6    10    /* IP version 6.  */
...
#define PF_MAX        44    /* For now..  */

/* Address families.  */
#define AF_UNSPEC    PF_UNSPEC
#define AF_LOCAL    PF_LOCAL
#define AF_UNIX        PF_UNIX
#define AF_FILE        PF_FILE
#define AF_INET        PF_INET
#define AF_AX25        PF_AX25
#define AF_IPX        PF_IPX
#define AF_APPLETALK    PF_APPLETALK
#define AF_NETROM    PF_NETROM
#define AF_BRIDGE    PF_BRIDGE
#define AF_ATMPVC    PF_ATMPVC
#define AF_X25        PF_X25
#define AF_INET6    PF_INET6
...
#define AF_MAX        PF_MAX

經常使用的AF_有AF_UNIX、AF_INET、AF_INET6三種。socket

  • AF_UNIX domain容許在同一主機上的應用程序之間進行通訊。
  • AF_INET domain容許在使用IPv4網絡鏈接起來的主機上的應用程序之間進行通訊。
  • AF_INET6 domain容許在使用IPv6網絡鏈接起來的主機上的應用程序之間進行通訊。

1.1.2 socket type

每一個socket實現都至少提供了兩種socket:流和數據報。函數

/* Types of sockets.  */
enum __socket_type
{
  SOCK_STREAM = 1,        /* Sequenced, reliable, connection-based
                   byte streams.  */
 SOCK_DGRAM = 2,        /* Connectionless, unreliable datagrams
                   of fixed maximum length.  */
  SOCK_RAW = 3,            /* Raw protocol interface.  */
  SOCK_RDM = 4,            /* Reliably-delivered messages.  */
  SOCK_SEQPACKET = 5,        /* Sequenced, reliable, connection-based,
                   datagrams of fixed maximum length.  */
  SOCK_DCCP = 6,        /* Datagram Congestion Control Protocol.  */
  SOCK_PACKET = 10,        /* Linux specific way of getting packets
                   at the dev level.  For writing rarp and
                   other similar things on the user level. */
  /* Flags to be ORed into the type parameter of socket and socketpair and
     used for the flags parameter of paccept.  */

  SOCK_CLOEXEC = 02000000,    /* Atomically set close-on-exec flag for the
                   new descriptor(s).  */
  SOCK_NONBLOCK = 00004000    /* Atomically mark descriptor(s) as
                   non-blocking.  */
};

流socket(SOCK_STREAM)提供了一個可靠的雙向的字節流通訊信道。post

流socket的正常工做須要一對相互鏈接的socket,所以流socket一般被稱爲面向鏈接的。atom

 

數據報socket(SOCK_DGRAM)容許數據以被稱爲數據報的消息的形式進行交換。

數據報socket是更通常的無鏈接socket概念,一個數據報socket在使用時無需與另外一個socket鏈接。 

在Internet domain中,數據報socket使用UDP,流socke通則使用TCP。

1.1.3 struct sockaddr

各類socket domain使用了不一樣的地址格式,對於各類socket domain都須要定義一個不一樣的結構類型來存儲socket地址。

然而因爲bind()調用適用於全部socket domain,所以他們必需要可以接受任意類型的地址結構。

爲此,socket API定義了一個通用的地址結構struct sockaddr。

這個類型的惟一用途是將各類domain特定的地址結構轉換成單個類型以供socket各個參數使用。

/* Structure describing a generic socket address.  */
struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];        /* Address data.  */
  };

這個結構是全部domain特定的地址結構的模板,其中每一個地址結構均以與sockadr結構中的sa_family打頭。

經過sa_family字段值足以肯定存儲在這個結構的剩餘部分中地址大小和格式了。

1.1.4 socket():建立一個socket

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
    Returns file descriptor on success, or –1 on error

domain指定了socket通訊domain,經常使用的有AF_UNIX、AF_INET、AF_INET6;type指定了socket類型,經常使用的有SOCK_STREAM、SOCK_DGRAM;protocol通常被指爲0。

socket()在成功時會返回一個引用在後續調用中會用到的新建立的socket文件描述符;錯誤則返回-1。

1.1.5 bind():將socket綁定到地址

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    Returns 0 on success, or –1 on error

sockfd是由socket()返回的文件描述符。

addr是指向指定socket綁定到的地址的結構體指針,傳輸參數的類型取決於socket domain。

addrlen參數指定了地址結構的大小。

其中addr傳入到內核,最終被不一樣socket domain的proto_ops->bind()調用的時候,會被強制轉換成不一樣數據結構。

好比AF_UNIX、AF_INET、AF_INET6對應的地址結構體分別爲struct sockaddr_un、struct sockaddr_in、struct sockaddr_in6

#define UNIX_PATH_MAX    108

struct sockaddr_un {
    __kernel_sa_family_t sun_family; /* AF_UNIX */---------------------填入AF_UNIX。
    char sun_path[UNIX_PATH_MAX];    /* pathname */--------------------本地socket的路徑。
};

#define __SOCK_SIZE__    16        /* sizeof(struct sockaddr)    */
struct sockaddr_in {
  __kernel_sa_family_t    sin_family;    /* Address family        */---填入AF_INET。
  __be16        sin_port;    /* Port number            */--------------端口號。
  struct in_addr    sin_addr;    /* Internet address        */---------IPv4的地址。

  /* Pad to size of `struct sockaddr'. */
  unsigned char        __pad[__SOCK_SIZE__ - sizeof(short int) -
            sizeof(unsigned short int) - sizeof(struct in_addr)];
};
#define sin_zero    __pad        /* for BSD UNIX comp. -FvK    */

struct sockaddr_in6 {
    unsigned short int    sin6_family;    /* AF_INET6 */---------------填入AF_INET6。
    __be16            sin6_port;      /* Transport layer port # */
    __be32            sin6_flowinfo;  /* IPv6 flow information */
    struct in6_addr        sin6_addr;      /* IPv6 address */
    __u32            sin6_scope_id;  /* scope id (new in RFC2553) */
};

1.1.6 listen():監聽接入鏈接

#include <sys/socket.h>
int listen(int sockfd, int backlog);
    Returns 0 on success, or –1 on error

listen()將sockfd引用的流socket標記爲被動,這個socket後面會被用來接受來自其餘socket鏈接。

若是客戶端在服務器調用accept()以前調用connect(),這將會產生一個未決的鏈接。

內核必須記錄全部未決的鏈接請求,在後續accept()就可以處理這些請求。

backlog參數容許限制這種未決鏈接數量。在這個限制內的鏈接請求會當即成功。以外的鏈接請求就會阻塞直到一個未決鏈接被接受,並從未決隊列中刪除爲止

1.1.7 accept():接收鏈接

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    Returns file descriptor on success, or –1 on error

accept()用在sockfd引用的監聽流socket上接受一個接入鏈接。

若是在accept()時不存在未決的鏈接,那麼調用就會阻塞直到有鏈接請求到達爲止

 

理解accept()的關鍵點是它會建立一個新socket,而且這是這個新socket會與執行connect()的對等socket進行鏈接

accept()返回結果是已經鏈接的socket文件描述符,其會保持打開狀態,而且能夠被用來接受後續的鏈接

addr參數指向了一個用來返回socket地址的結構。

addrlen在調用以前必需要將其初始化爲addr指向的緩衝區大小;返回以後被設置成實際被複制進緩衝區中的數據的字節數。

1.1.8 connect():(客戶端)鏈接到(服務器)對等socket

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    Returns 0 on success, or –1 on error

connect()將sockfd引用的socket鏈接到地址經過addr和addrlen指定的監聽socket上。

其中addr和addrlen參數指定方式與bind()對應參數指定方式相同。

1.1.9 sendto()/recvfrom():交換數據報

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
    Returns number of bytes received, 0 on EOF, or –1 on error
ssize_t sendto(int sockfd, const void *buffer, size_t length, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
    Returns number of bytes sent, or –1 on error

flags是一個位掩碼,控制着特定的IO特性。

src_addr和addrlen用來獲取或指定與之同行的對等socket地址。

對於recvfrom()來講,src_addr和addlen會返回用來發送數據報的遠程socket地址。若是不關心,能夠將src_addr和addrlen都指定爲NULL。

對於sendto()來講,dest_addr和addrlen制定了數據報發送到的socket地址。

1.1.10 read()/write()/close():讀、寫、關閉socket文件

若是做爲客戶端,socket()返回的sockfd,在connect以後就就能夠對sockfd進行read()/write()/close()操做。

若是做爲服務端,在accept()以後產生新的sockfd,以後sockfd就保持打開狀態。能夠對其進行read()/write()/close()操做。

1.2 流socket

 

1.3 數據報socket

 

2. 從socket API到系統調用socketcall()

上面介紹了一系列socket相關API,可是這些C函數並無對應的系統調用。

下面就看看這些scoket API是如何轉到內核調用的,以socket()爲例。

int
__socket (int fd, int type, int domain)
{
#ifdef __ASSUME_SOCKET_SYSCALL
  return INLINE_SYSCALL (socket, 3, fd, type, domain);
#else
  return SOCKETCALL (socket, fd, type, domain);----------根據SOCKETCALL()定義可知,socket經過鏈接符號便變成其對應的call,做爲socketcall()的第一個參數。 #endif
}
libc_hidden_def (__socket)-------------------------------對libc以外屏蔽__socket()函數訪問。
weak_alias (__socket, socket)----------------------------若是沒有定義socket()函數,那麼對socket()的調用將會轉到調用__socket()。

2.1 SOCKETCALL():

從下面SOCKETCALL()宏定義可知,最終是經過socketcall()系統調用實現的。

具體對應connect()對應的是SOCKOP_socket,即socketcall()系統調用的第一個參數爲1。

在socketcall()系統調用中,根據第一個參數執行對應的操做。

因此下面SOCKOP_對應的socket API都是經過socketcall()實現的,而後在socketcall()裏面進行處理。

#define SOCKOP_invalid        -1
#define SOCKOP_socket        1
#define SOCKOP_bind        2
#define SOCKOP_connect        3
#define SOCKOP_listen        4
#define SOCKOP_accept        5
#define SOCKOP_getsockname    6
#define SOCKOP_getpeername    7
#define SOCKOP_socketpair    8
#define SOCKOP_send        9
#define SOCKOP_recv        10
#define SOCKOP_sendto        11
#define SOCKOP_recvfrom        12
#define SOCKOP_shutdown        13
#define SOCKOP_setsockopt    14
#define SOCKOP_getsockopt    15
#define SOCKOP_sendmsg        16
#define SOCKOP_recvmsg        17
#define SOCKOP_accept4        18
#define SOCKOP_recvmmsg        19
#define SOCKOP_sendmmsg        20

#define __SOCKETCALL1(name, a1) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [1]) { (long int) (a1) }))
...
#define __SOCKETCALL6(name, a1, a2, a3, a4, a5, a6) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [6]) { (long int) (a1), (long int) (a2), (long int) (a3), \
                       (long int) (a4), (long int) (a5), (long int) (a6) }))


#define SOCKETCALL(name, args...)                    \
  ({                                    \
    long int sc_ret = __SOCKETCALL (SOCKOP_##name, args);        \
    sc_ret;                                \
  })

2.2 weak_alias()

weak_alias()是一個宏,其目的是爲函數添加一個「弱」別名,與「強」符號進行區分。

若是調用函數對應的函數無「強」符號對應的函數,則會調用該別名對應的函數。所謂「強」符號的函數名就是普通聲明定義的函數對應的函數名。

這裏若是沒有定義connect()函數,調用connect()實際就會轉到__socket()。

# define weak_alias(name, aliasname) _weak_alias (name, aliasname)
# define _weak_alias(name, aliasname) \
  extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));

2.3 libc_hidden_def()

libc_hidden_def()的定義在libc-symbols.h中。

# define libc_hidden_def(name) hidden_def (name) 

3. socketcall及socket系統調用分析

能夠說socketcall()是全部socket調用的入口,socketcall()根據call的值switch-case到對應的函數中。這些函數和單獨系統調用基本一致。

下面就先來分析一下socketcall()函數。

#define SYS_SOCKET    1        /* sys_socket(2)        */
#define SYS_BIND    2        /* sys_bind(2)            */
#define SYS_CONNECT    3        /* sys_connect(2)        */
...
#define SYS_SENDMMSG    20        /* sys_sendmmsg(2)        */

/* Argument list sizes for compat_sys_socketcall */
#define AL(x) ((x) * sizeof(u32))
static unsigned char nas[21] = {----------------------------------------nas[]將call id做爲下標,獲得對應系統調用參數的總大小。這裏是上面call id和系統調用的一座橋樑。
    AL(0), AL(3), AL(3), AL(3), AL(2), AL(3),
    AL(3), AL(3), AL(4), AL(4), AL(4), AL(6),
    AL(6), AL(2), AL(5), AL(5), AL(3), AL(3),
    AL(4), AL(5), AL(4)
};

COMPAT_SYSCALL_DEFINE2(socketcall, int, call, u32 __user *, args)
{
    u32 a[AUDITSC_ARGS];
    unsigned int len;
    u32 a0, a1;
    int ret;

    if (call < SYS_SOCKET || call > SYS_SENDMMSG)-----------------------判斷call範圍,從SYS_SOCKET到SYS_SOCKET。 return -EINVAL;
    len = nas[call];----------------------------------------------------根據call id獲取args大小。 if (len > sizeof(a))
        return -EINVAL;

    if (copy_from_user(a, args, len))
        return -EFAULT;

    ret = audit_socketcall_compat(len / sizeof(a[0]), a);---------------未定義CONFIG_AUDITSYSCALL直接返回0。 if (ret)
        return ret;

    a0 = a[0];
    a1 = a[1];

    switch (call) {
    case SYS_SOCKET:
        ret = sys_socket(a0, a1, a[2]);
        break;
    case SYS_BIND:
        ret = sys_bind(a0, compat_ptr(a1), a[2]);
        break;
    case SYS_CONNECT:
        ret = sys_connect(a0, compat_ptr(a1), a[2]);
        break;
...
    default:
        ret = -EINVAL;
        break;
    }
    return ret;
}

結合socketcall()系統調用的實現和API單獨系統調用,能夠看出二者的實現是一致的。

3.1 sys_socket()

struct socket在內核中表示一個socket,struct sock在網絡層表示一個socket。

struct socket {
    socket_state        state;-----------------------------表示當前socket的狀態。

    kmemcheck_bitfield_begin(type);
    short            type;---------------------------------對應enum socket_type,等於sys_socket()傳入的type參數。
    kmemcheck_bitfield_end(type);

    unsigned long        flags;

    struct socket_wq __rcu    *wq;

    struct file        *file;
    struct sock        *sk;
    const struct proto_ops    *ops;------------------------是family和type二者綜合的操做函數集。
};

sys_socket()建立一個struct socket,根據family從net_families[]找到對應協議族;而後在從type找到具體使用哪一種類型struct proto_ops。

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;---------------------------------------------經過SOCK_TYPE_MASK將傳入的type分開,一部分是flags,另外一部分是0~15之間的socket type。 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

    retval = sock_create(family, type, protocol, &sock);----------------根據family/type/protocol建立一個struct socket。 if (retval < 0)
        goto out;

    retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));-------爲新建立的struct socket分配一個文件描述符。 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;
}

int sock_create(int family, int type, int protocol, struct socket **res)
{
    return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}

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;
...
    if (family == PF_INET && type == SOCK_PACKET) {
        pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
                 current->comm);
        family = PF_PACKET;
    }

    err = security_socket_create(family, type, protocol, kern);
    if (err)
        return err;

    sock = sock_alloc();--------------------------------------------------建立一個struct inode和struct socket,並將二者綁定起來。 if (!sock) {
        net_warn_ratelimited("socket: no more sockets\n");
        return -ENFILE;    /* Not exactly a match, but its the
                   closest posix thing */
    }

    sock->type = type;----------------------------------------------------設置socket類型。

#ifdef CONFIG_MODULES
    if (rcu_access_pointer(net_families[family]) == NULL)
        request_module("net-pf-%d", family);
#endif

    rcu_read_lock();
    pf = rcu_dereference(net_families[family]);---------------------------net_families使用下標對應AF_XXX,經過net_families[family]能夠得到對應struct net_proto_family。
    err = -EAFNOSUPPORT;
    if (!pf)
        goto out_release;

    if (!try_module_get(pf->owner))
        goto out_release;

    rcu_read_unlock();

    err = pf->create(net, sock, protocol, kern);--------------------------調用具體AF_XX對應的create成員,好比AF_UNIX對應unix_create()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;
...
}

net_families[]保存了全部AF_XXX對應的struct net_proto_family。

這些struct net_proto_family經過sock_register()註冊,經過sock_unregister()去註冊。

static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;

int sock_register(const struct net_proto_family *ops)
{
    int err;
...
    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);---------------主要就是講struct net_proto_family賦給net_families[]。
        err = 0;
    }
    spin_unlock(&net_family_lock);
return err;
}

void sock_unregister(int family)
{
    BUG_ON(family < 0 || family >= NPROTO);

    spin_lock(&net_family_lock);
    RCU_INIT_POINTER(net_families[family], NULL);
    spin_unlock(&net_family_lock);

    synchronize_rcu();
}

下面以AF_UNIX爲例,看看不一樣type的處理。

static int __init af_unix_init(void)
{
...
 sock_register(&unix_family_ops);
...
}

static const struct net_proto_family unix_family_ops = {
    .family = PF_UNIX,
    .create = unix_create,
    .owner    = THIS_MODULE,
};

static int unix_create(struct net *net, struct socket *sock, int protocol,
               int kern)
{
    if (protocol && protocol != PF_UNIX)
        return -EPROTONOSUPPORT;

    sock->state = SS_UNCONNECTED;

    switch (sock->type) {-------------------------------------------------能夠看出AF_UNIX僅支持SOCK_STREAM、SOCK_STREAM、SOCK_STREAM、SOCK_STREAM幾種形式type。 case SOCK_STREAM:
        sock->ops = &unix_stream_ops;
        break;
    case SOCK_RAW:
        sock->type = SOCK_DGRAM;
    case SOCK_DGRAM:
        sock->ops = &unix_dgram_ops;
        break;
    case SOCK_SEQPACKET:
        sock->ops = &unix_seqpacket_ops;
        break;
    default:
        return -ESOCKTNOSUPPORT;
    }
    return unix_create1(net, sock, kern) ? 0 : -ENOMEM;--------------------建立並初始化struct sock。
}

static const struct proto_ops unix_stream_ops = {
    .family =    PF_UNIX,
    .owner =    THIS_MODULE,
    .release =    unix_release,
...
    .set_peek_off =    unix_set_peek_off,
};

static const struct proto_ops unix_dgram_ops = {
    .family =    PF_UNIX,
    .owner =    THIS_MODULE,
    .release =    unix_release,
...
    .set_peek_off =    unix_set_peek_off,
};

static const struct proto_ops unix_seqpacket_ops = {
    .family =    PF_UNIX,
    .owner =    THIS_MODULE,
    .release =    unix_release,
...
    .set_peek_off =    unix_set_peek_off,
};

unix_create1()函數分配而且初始化struct sock,而後做爲struct socket的成員sk。

static struct sock *unix_create1(struct net *net, struct socket *sock, int kern)
{
    struct sock *sk = NULL;
    struct unix_sock *u;

    atomic_long_inc(&unix_nr_socks);
    if (atomic_long_read(&unix_nr_socks) > 2 * get_max_files())
        goto out;

    sk = sk_alloc(net, PF_UNIX, GFP_KERNEL, &unix_proto, kern);
    if (!sk)
        goto out;

    sock_init_data(sock, sk);
    lockdep_set_class(&sk->sk_receive_queue.lock,
                &af_unix_sk_receive_queue_lock_key);

    sk->sk_allocation    = GFP_KERNEL_ACCOUNT;
    sk->sk_write_space    = unix_write_space;
    sk->sk_max_ack_backlog    = net->unx.sysctl_max_dgram_qlen;
    sk->sk_destruct        = unix_sock_destructor;
    u      = unix_sk(sk);
    u->path.dentry = NULL;
    u->path.mnt = NULL;
    spin_lock_init(&u->lock);
    atomic_long_set(&u->inflight, 0);
    INIT_LIST_HEAD(&u->link);
    mutex_init(&u->iolock); /* single task reading lock */
    mutex_init(&u->bindlock); /* single task binding lock */
    init_waitqueue_head(&u->peer_wait);
    init_waitqueue_func_entry(&u->peer_wake, unix_dgram_peer_wake_relay);
    unix_insert_socket(unix_sockets_unbound(sk), sk);
out:
    if (sk == NULL)
        atomic_long_dec(&unix_nr_socks);
    else {
        local_bh_disable();
        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
        local_bh_enable();
    }
    return sk;
}

綜上所述,sys_socket() 主要完善內核中struct socket結構體,尤爲是struct proto_ops結構體。而後返回對應文件描述符給用戶空間。

後續關於socket的API都是經過文件描述符找到內核中對應的struct socket,而後調用struct proto_ops中成員來完成工做。 

3.2 sys_bind()

sys_bind()經過入參fd找到內核中表示socket對應的struct socket。

而後調用struct socket->ops->bind()進行umyaddr和fd綁定。在AF_UNIX和SOCK_STREAM狀況下,調用unix_bind()。

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);-------------------------根據fd找到對應的struct socket結構體。以fs爲索引從當前進程的文件描述符表files_struct中找到對應的file實例,而後從file實例中的private_data成員中獲取socket實例。 if (sock) {
        err = move_addr_to_kernel(umyaddr, addrlen, &address);
        if (err >= 0) {
            err = security_socket_bind(sock,
                           (struct sockaddr *)&address,
                           addrlen);
            if (!err)
                err = sock->ops->bind(sock,
                              (struct sockaddr *)
                              &address, addrlen);-------------------------------調用struct socket->ops->bind()完成地址與socket的綁定,進而和fd綁定。
        }
        fput_light(sock->file, fput_needed);
    }
    return err;
}

static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
    struct fd f = fdget(fd);
    struct socket *sock;

    *err = -EBADF;
    if (f.file) {
        sock = sock_from_file(f.file, err);
        if (likely(sock)) {
            *fput_needed = f.flags;
            return sock;
        }
        fdput(f);
    }
    return NULL;
}

3.3 sys_listen()

相似sys_bind(),sys_listen()也是一樣的經過fd找到struct socket,而後調用struct socket->ops->listen()完成主要工做。

在AF_UNIX和SOCK_STREAM狀況下,調用unix_listen()。

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
    struct socket *sock;
    int err, fput_needed;
    int somaxconn;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
        if ((unsigned int)backlog > somaxconn)
            backlog = somaxconn;

        err = security_socket_listen(sock, backlog);
        if (!err)
            err = sock->ops->listen(sock, backlog);

        fput_light(sock->file, fput_needed);
    }
    return err;
}

3.4 sys_connect()

服務器端socket使用bind()來綁定IP和端口,客戶端使用connect()讓系統自動選擇IP和端口。

核心也是調用struct socket->ops->connect()。

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
        int, addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);----------------------------------經過文件描述符fd找到對應的socket實例。 if (!sock)
        goto out;
    err = move_addr_to_kernel(uservaddr, addrlen, &address);
    if (err < 0)
        goto out_put;

    err =
        security_socket_connect(sock, (struct sockaddr *)&address, addrlen);--------------將socket地址從用戶空間拷貝到內核。 if (err)
        goto out_put;

    err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,-----------------調用connect()成員函數,對於AF_UNIX和SOCK_STREAM即調用unix_stream_connect()。
                 sock->file->f_flags);
out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}

3.5 sys_accept()/sys_accept4()

sys_accept()做爲accept()在內核中的實現,返回一個新的句柄,創建新的操做上下文。

SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen, int, flags)
{
    struct socket *sock, *newsock;
    struct file *newfile;
    int err, len, newfd, fput_needed;
    struct sockaddr_storage address;

    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))--------------------------------------不容許使用這兩個flags。 return -EINVAL;

    if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);--------------------------------根據fd找到struct socket。 if (!sock)
        goto out;

    err = -ENFILE;
    newsock = sock_alloc();------------------------------------------------------------建立一個新的struct socket。 if (!newsock)
        goto out_put;

    newsock->type = sock->type;--------------------------------------------------------新的socket類型和socket層操做。
    newsock->ops = sock->ops;

    /*
     * We don't need try_module_get here, as the listening socket (sock)
     * has the protocol module (sock->ops->owner) held.
     */
    __module_get(newsock->ops->owner);

    newfd = get_unused_fd_flags(flags);-------------------------------------------------分配一個空閒的文件句柄。 if (unlikely(newfd < 0)) {
        err = newfd;
        sock_release(newsock);
        goto out_put;
    }
    newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);---------爲新建立的struct socket分配一個文件描述符。 if (IS_ERR(newfile)) {
        err = PTR_ERR(newfile);
        put_unused_fd(newfd);
        sock_release(newsock);
        goto out_put;
    }

    err = security_socket_accept(sock, newsock);
    if (err)
        goto out_fd;

    err = sock->ops->accept(sock, newsock, sock->file->f_flags);-------------------------對於AF_UNIX和SOCK_STREAM則是調用unix_accept()。 if (err < 0)
        goto out_fd;

    if (upeer_sockaddr) {----------------------------------------------------------------若是accept須要返回對端socket地址,調用newsock->ops->getname()獲取struct sockaddr並返還給用戶空間upeer_sockaddr。 if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
                      &len, 2) < 0) {
            err = -ECONNABORTED;
            goto out_fd;
        }
        err = move_addr_to_user(&address,
                    len, upeer_sockaddr, upeer_addrlen);
        if (err < 0)
            goto out_fd;
    }

    /* File flags are not inherited via accept() unlike another OSes. */

    fd_install(newfd, newfile);---------------------------------------------------------以newfd爲索引,把newfile加入當前進程的文件描述符標files_struct中。
    err = newfd;

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
out_fd:
    fput(newfile);
    put_unused_fd(newfd);
    goto out_put;
}

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen)
{
    return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

因此sys_accept ()主要做用就是:建立新的socket和inode並初始化完成;調用原socket->ops->accept();保存新建立socket的地址到用戶空間。

3.6 其餘API

sys_getsockname()/sys_getpeername()調用相應proto_ops->getname()。

sys_send()/sys_sendto()/sys_sendmsg()/sys_sendmmsg()最終都是經過___sys_sendmsg()實現。

sys_recv()/sys_recvfrom()/sys_recvmsg()/sys_recvmmsg()最終都是經過___sys_recvmsg實現。

sys_setsockopt()/sys_getsockopt()分別調用proto_ops->setsockopt()和proto_ops->getsockopt()。

sys_socketpair()調用proto_ops->socketpair(),sys_shutdown()調用proto_ops->shutdown()。

4. 簡單基於Socket的Server/Client通訊

相關文章
相關標籤/搜索