Socket網絡編程核心API深入分析(一):bind函數

2020-12-10 酷扯兒

本文轉載自【微信公眾號:小碼逆襲,ID:gh_7c5a039380a0】經微信公眾號授權轉載,如需轉載與原文作者聯繫

本篇文章你能學到:

1、實現簡單的c++版本的伺服器和客戶端

2、深入理解bind()函數,bind的系統調用過程,bind是如何處理埠衝突的,面試的時候你就這樣講。

注意:本片文章涉及到的內核源碼來自linux內核版本3.6

簡單的伺服器與客戶端實現

本篇文章的重點在於從底層深入分析bind()函數,相信已經能夠自己實現一個簡單的伺服器和客戶端並進行交互,下面是一個簡單的demo,幫助大家複習一下socket編程api的調用過程。

伺服器server.cpp

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <netinet/in.h>

int main(){

//創建套接字

int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

//將套接字和IP、埠綁定

struct sockaddr_in serv_addr;

memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充

serv_addr.sin_family = AF_INET; //使用IPv4地址

serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP位址

serv_addr.sin_port = htons(1234); //埠

bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

//進入監聽狀態,等待用戶發起請求

listen(serv_sock, 20);

//接收客戶端請求

struct sockaddr_in clnt_addr;

socklen_t clnt_addr_size = sizeof(clnt_addr);

int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

//向客戶端發送數據

char str[] = "http://c.biancheng.net/socket/";

write(clnt_sock, str, sizeof(str));

//關閉套接字

close(clnt_sock);

close(serv_sock);

return 0;

}

客戶端client.cpp

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

int main(){

//創建套接字

int sock = socket(AF_INET, SOCK_STREAM, 0);

//向伺服器(特定的IP和埠)發起請求

struct sockaddr_in serv_addr;

memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充

serv_addr.sin_family = AF_INET; //使用IPv4地址

serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP位址

serv_addr.sin_port = htons(1234); //埠

connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

//讀取伺服器傳回的數據

char buffer[40];

read(sock, buffer, sizeof(buffer)-1);

printf("Message form server: %s\n", buffer);

//關閉套接字

close(sock);

return 0;

}

socket編程TCP協議的調用流程如下:

深入分析bind函數

我從應用層出發,沿著網絡協議棧,從bind()的系統調用、到Socket層實現,最終到它的TCP層實現。

應用層

int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

給socket描述符綁定IP和埠,一般伺服器才需要。

也可交給系統來選擇:

my_addr.sin_port = 0; 系統隨機選擇一個未被使用的埠

my_addr.sin_addr.s_addr = INADDR_ANY; 自動填入本機的IP位址

#define INADDR_ANY ((unsigned long int) 0x00000000)

埠號的範圍為0 ~ 65535。調用bind()時,一般不要把埠號置為小於1024的值,因為1到1023是保留埠號。

系統調用

bind()是由glibc提供的,聲明位於include/sys/socket.h中,實現位於sysdeps/mach/hurd/bind.c中,主要是用來從用戶空間進入名為sys_socketcall的系統調用,並傳遞參數。sys_scoketcall()實際上是所有socket函數進入內核空間的共同入口。

在sys_socketcall()中會調用sys_bind()。

SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)

{

...

switch(call) {

...

case SYS_BIND:

err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);

break;

...

}

return err;

}

經過了socket層的總入口sys_socketcall(),現在進入sys_bind()。

/*

* Bind a name to a socket. Nothing much to do here since it's the protocol's responsibility

* to handle the local address.

* We move the socket address to kernel space before we call the protocol layer (having also

* checked the address is ok).

*/

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)

{

struct socket *sock;

struct sockaddr_storage address;

int err, fput_needed;

/* 通過文件描述符fd,找到對應的socket。

* 以fd為索引從當前進程的文件描述符表files_struct中找到對應的file實例,

* 然後從file實例的private_data成員中獲取socket實例。

*/

sock = sockfd_lookup_light(fd, &err, &fput_needed);

if (sock) {

/* 把用戶空間的地址複製到內核空間,成功返回0 */

err = move_addr_to_kernel(umyaddr, addrlen, &address);

if (err >= 0) {

/* SELInux相關 */

err = security_socket_bind(sock, (struct sockaddr *)&address, addrlen);

if (!err)

/* socket層的操作函數集。如果是SOCK_STREAM的話,proto_ops是inet_stream_ops,

* 接下來調用的是inet_bind()。

*/

err = sock->ops->bind(sock, (struct sockaddr *)&address, addrlen);

}

fput_light(sock->file, fput_needed);

}

return err;

}

通過文件描述符,找到對應的file結構。

static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)

{

struct file *file;

struct socket *sock;

*err = -EBADF; /* Bad file number */

/* 從當前進程的files_struct中找到網絡文件系統中的file指針,並增加它的引用計數 */

file = fget_light(fd, fput_needed);

if (file) {

sock = sock_from_file(file, err); /* 通過file找到對應的socket */

if (sock)

return sock;

fput_light(file, *fput_needed); /* 失敗的話減少file的引用計數 */

}

return NULL;

}

通過file結構,找到對應的socket結構。

struct socket *sock_from_file(struct file *file, int *err)

{

if (file->f_op == &socket_file_ops) /* 說明此file對應一個socket */

return file->private_data; /* set in sock_map_fd */

*err = -ENOTSOCK;

return NULL;

}

把用戶空間的socket地址複製到內核空間,同時檢查是否合法,成功返回0。

int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr)

{

if (ulen < 0 || ulen > sizeof(struct sockaddr_storage)) /* socket地址長度是否合法 */

return -EINVAL;

if (ulen == 0)

return 0;

if (copy_from_user(kaddr, uaddr, ulen))

return -EFAULT; /* socket地址是否合法 */

return audit_sockaddr(ulen, kaddr);

}

Socket層

SOCK_STREAM套接口的socket層操作函數集實例為inet_stream_ops,其中綁定函數為inet_bind()。

const struct proto_ops inet_stream_ops = {

.family = PF_INET,

.owner = THIS_MODULE,

...

.bind = inet_bind, /* socket層的bind實現 */

...

}

socket層做的主要事情為合法性檢查、綁定IP位址,而真正的埠綁定是在TCP層進行的。

int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)

{

struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;

struct sock *sk = sock->sk; /* 傳輸層實例 */

struct inet_sock *inet = inet_sk(sk); /* INET實例 */

unsigned short snum; /* 要綁定的埠 */

int chk_addr_ret; /* IP位址類型 */

int err;

/* If the socket has its own bind function then use it. (RAW)

* 用於原始套接字,TCP協議實例tcp_prot不含此函數指針。

*/

if (sk->sk_prot->bind) {

err = sk->sk_prot->bind(sk, uaddr, addr_len);

goto out;

}

err = -EINVAL;

if (addr_len < sizeof(struct sockaddr_in)) /* socket地址長度錯誤 */

goto out;

if (addr->sin_family != AF_INET) { /* 非INET協議族 */

/* Compatibility games: accept AF_UNSPEC (mapped to AF_INET)

* only if s_addr is INADDR_ANY.

*/

err = -EAFNOSUPPORT;

if (addr->sin_family != AF_UNSPEC || addr->sin_addr.s_addr != htonl(INADDR_ANY))

goto out;

}

/* 在路由中檢查IP位址類型,單播、多播還是廣播 */

chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);

/* Not specified by any standard per-se, however it breaks too many applications

* when removed. It is unfortunate since allowing applications to make a non-local

* bind solves several problems with systems using dynamic addressing.

* (ie. your servers still start up even if your ISDN link is temporarily down)

*/

/* sysctl_ip_nonlocal_bind表示是否允許綁定非本地的IP位址。

* inet->freebind表示是否允許綁定非主機地址。

* 這裡需要允許綁定非本地地址,除非是發送給自己、多播或廣播。

*/

err = -EADDRNOTAVAIL; /* Cannot assign requested address */

if (! sysctl_ip_nonlocal_bind && ! (inet->freebind || inet->transparent) &&

addr->sin_addr.s_addr != htonl(INADDR_ANY) &&

chk_addr_ret != RTN_LOCAL && chk_addr_ret != RTN_MULTICAST &&

chk_addr_ret != RTN_BROADCAST)

goto out;

snum = ntohs(addr->sin_port); /* 要綁定的埠 */

err = -EACCES; /* Permission denied */

/* snum為0表示讓系統隨機選擇一個未使用的埠,因此是合法的。

* 如要需要綁定的埠為1 ~ 1023,則需要對應的特權。

*/

if (snum && snum < PORT_SOCK && ! capable(CAP_NET_BIND_SERVICE))

goto out;

lock_sock(sk);

/* Check these errors (active socket, double bind).

* 如果套接字不在初始狀態TCP_CLOSE,或者已經綁定埠了,則出錯。

* 一個socket最多可以綁定一個埠,而一個埠則可能被多個socket共用。

*/

err = -EINVAL;

if (sk->sk_state != TCP_CLOSE || inet->inet_num)

goto out_release_sock;

/* We keep a pair of addresses. rcv_saddr is the one used by hash lookups,

* and saddr is used for transmit.

* In the BSD API these are the same except where it would be illegal to use them

* (multicast/broadcast) in which case the sending device address is used.

*/

inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; /* 綁定地址 */

if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)

inet->inet_saddr = 0; /* Use device */

/* Make sure we are allowed to bind here.

* 如果使用的是TCP,則sk_prot為tcp_prot,get_port為inet_csk_get_port()

* 埠可用的話返回0。

*/

if (sk->sk_prot->get_port(sk, snum)) {

inet->inet_saddr = inet->inet_rcv_saddr = 0;

err = -EADDRINUSE;

goto out_release_sock;

}

/* inet_rcv_saddr表示綁定的地址,接收數據時用於查找socket */

if (inet->inet_rcv_saddr)

sk->sk_userlocks |= SOCK_BINDADDR_LOCK; /* 表示綁定了本地地址 */

if (snum)

sk->sk_userlocks |= SOCK_BINDPORT_LOCK; /* 表示綁定了本地埠 */

inet->inet_sport = htons(inet->inet_num); /* 綁定埠 */

inet->inet_daddr = 0;

inet->inet_dport = 0;

sk_dst_reset(sk);

err = 0;

out_release_sock:

release_sock(sk);

out:

return err;

}

/* Sockets 0 - 1023 can't be bound to unless you are superuser */

#define PORT_SOCK 1024

/* Allows binding to TCP/UDP sockets below 1024 */

#define CAP_NET_BIND_SERVICE 10

TCP層

SOCK_STREAM套接口的TCP層操作函數集實例為tcp_prot,其中埠綁定函數為inet_csk_get_port()。

struct proto tcp_prot = {

.name = "TCP",

.owner = THIS_MODULE,

...

.get_port = inet_csk_get_port, /* TCP層bind()相關操作 */

...

};

和較早的內核版本不同,現在系統自動選擇埠時,也可以復用埠了。

/* Obtain a reference to a local port for the given sock,

* if snum is zero it means select any available local port.

*/

int inet_csk_get_port(struct sock *sk, unsigned short snum)

{

struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; /* 指向tcp_hashinfo */

struct inet_bind_hashbucket *head;

struct hlist_node *node;

struct inet_bind_bucket *tb;

int ret, attempts = 5;

struct net *net = sock_net(sk);

int smallest_size = -1, smallest_rover;

local_bh_disable(); /* 禁止下半部,防止和進程衝突 */

/* 如果snum為0,系統自動為sock選擇一個埠號 */

if (! snum) {

int remaining, rover, low, high;

again:

inet_get_local_port_range(&low, &high); /* 獲取埠號的取值範圍 */

remaining = (high - low) + 1; /* 取值範圍內埠號的個數 */

smallest_rover = rover = net_random() % remaining + low; /* 隨機選取範圍內的一個埠 */

smallest_size = -1;

do {

if (inet_is_reserved_local_port(rover)) /* 查看埠是否屬於保留的 */

goto next_nolock; /* rover加1,繼續 */

/* 根據埠號,確定所在的哈希桶 */

head = &hashinfo->bhash[inet_bhashfn(net, rover, hashinfo->bhash_size)];

spin_lock( &head->lock); /* 鎖住哈希桶 */

inet_bind_bucket_for_each(tb, node, &head->chain) /* 從頭遍歷哈希桶 */

/* 如果埠被使用了 */

if (eq(ib_net(tb), net) && tb->port == rover) {

if (tb->fastreuse > 0 && sk->sk_reuse && sk->sk_state != TCP_LISTEN &&

tb->num_owners < smallest_size || smallest_size == -1)) {

smallest_size = tb->num_owners; /* 記下這個埠使用者的個數 */

smallest_rover = rover; /* 記下這個埠 */

/* 如果系統綁定的埠已經很多了,那麼就判斷埠是否有綁定衝突*/

if (atomic_read(&hashinfo->bsockets) > (high - low) + 1 &&

! inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) {

snum = smallest_rover; /* 沒有衝突,使用此埠 */

goto tb_found;

}

}

/* 檢查是否有埠綁定衝突,該埠是否能重用 */

if (! inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) {

snum = rover;

goto tb_found;

}

goto next; /* 此埠不可重用,看下一個 */

}

break; /* 找到了沒被用的埠,退出 */

next:

spin_unlock(&head->lock);

next_nolock:

if (++rover > high)

rover = low;

} while(--remaining > 0);

/* Exhausted local port range during search? It is not possible for us to be holding

* one of the bind hash locks if this test triggers, because if 'remaining' drops to zero,

* we broke out of the do/while loop at the top level, not from the 'break' statement.

*/

ret = 1;

if (remaining <= 0) { /* 完全遍歷 */

if (smallest_size != -1) {

snum = smallest_rover;

goto have_snum;

}

goto fail;

}

/* OK, here is the one we will use.

* HEAD is non-NULL and we hold it's mutex.

*/

snum = rover; /* 自動選擇的可用埠 */

} else { /* 如果有指定要綁定的埠 */

have_snum:

head = &hashinfo->bhash[inet_bhashfn(net, snum, hashinfo->bhash_size)];

spin_lock(&head->lock);

inet_bind_bucket_for_each(tb, node, &head->chain)

if (net_eq(ib_net(tb), net) && tb->port == snum)

goto tb_found; /* 發現埠在用 */

}

tb = NULL;

goto tb_not_found;

tb_found:

/* 埠上有綁定sock時 */

if (! hlist_empty(&tb->owners)) {

/* 這是強制的綁定啊,不管埠是否會綁定衝突!*/

if (sk->sk_reuse == SK_FORCE_REUSE)

goto success;

if (tb->fastreuse > 0 && sk->sk_reuse && sk->sk_state != TCP_LISTEN &&

smallest_size == -1) { /* 指定埠的情況 */

goto success;

} else {

ret = 1;

if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, true)) { /* 埠綁定衝突 */

/* 自動分配的埠綁定衝突了,再次嘗試,最多重試5次。

* 我覺得以下if不必要,因為自動選擇時goto tb_found之前都有檢測過了。

*/

if (sk->sk_reuse && sk->sk_state != TCP_LISTEN && smallest_size != -1

&& --attempts >= 0) {

spin_unlock(&head->lock);

goto again;

}

goto fail_unlock; /* 失敗 */

}

}

}

tb_not_found:

ret = 1;

/* 申請和初始化一個inet_bind_bucket結構 */

if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep, net, head, snum)) == NULL)

goto fail_unlock;

if (hlist_empty(&tb->owners)) {

if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)

tb->fastreuse = 1;

else

tb->fastreuse = 0;

} else if (tb->fastreuse && (! sk->sk_reuse || sk->sk_state == TCP_LISTEN))

tb->fastreuse = 0;

success:

/* 賦值icsk中的inet_bind_bucket */

if (! inet_csk(sk)->icsk_bind_hash)

inet_bind_hash(sk, tb, snum);

WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);

ret = 0;

fail_unlock:

spin_unlock(&head->lock);

fail:

local_bh_enable();

return ret;

}

埠區間

我們可以指定系統自動分配埠號時,埠的區間:

/proc/sys/net/ipv4/ip_local_port_range,默認為:32768 61000

也可以指定要保留的埠區間:

/proc/sys/net/ipv4/ip_local_reserved_ports,默認為空。

/* This struct holds the first and last local port number.

* 用於系統自動分配的埠區間。

*/

struct local_ports sysctl_local_ports __read_mostly = {

.lock = __SEQLOCK_UNLOCKED(sysctl_local_ports.lock),

.range = {32768, 61000},

};

埠綁定衝突

看下面這段代碼的實現:

int inet_csk_bind_conflict(const struct sock *sk, const struct inet_bind_bucket *tb, bool relax)

{

struct sock *sk2;

struct hlist_node *node;

int reuse = sk->sk_reuse; /* SO_REUSEADDR,表示處於TIME_WAIT狀態的埠允許重用 */

/* Unlike other sk lookup places we do not check for sk_net here, since all the socks

* listed in tb->owners list belong to the same net - the one this bucket belongs to.

* 遍歷此埠上的sock。

*/

sk_for_each_bound(sk2, node, &tb->owners) {

/* 衝突的條件1:不是同一socket、綁定在相同的設備上 */

if (sk != sk2 && ! inet_v6_ipv6only(sk2) && (! sk->sk_bound_dev_if || ! sk2->sk_bound_dev_if

|| sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) {

/* 衝突的條件2:綁定在相同的IP上

* 衝突的條件3(符合一個即滿足):

* 3.1 本socket不允許重用

* 3.2 鍊表中的socket不允許重用

* 3.3 鍊表中的socket處於監聽狀態

*/

if (! reuse || ! sk2->sk_reuse || sk2->sk_state == TCP_LISTEN) {

const __u32 sk2_rcv_saddr = sk_rcv_saddr(sk2); /* sk2的綁定IP */

if (! sk2_rcv_saddr || ! sk_rcv_saddr(sk) || sk2_rcv_saddr == sk_rcv_saddr(sk))

break; /* 衝突了 */

}

/* 覺得這段代碼有好多冗餘,可以精簡下:)

* 3.4 relax為false

*/

if (! relax && reuse && sk2->sk_reuse && sk2->sk_state != TCP_LISTEN) {

const __be32 sk2_rcv_saddr = sk_rcv_saddr(sk2);

if (! sk2_rcv_saddr || ! sk_rcv_saddr(sk) || sk2_rcv_saddr == sk_rcv_saddr(sk))

break;

}

}

}

return node != NULL;

}

什麼條件下會出現衝突呢?

同時符合以下條件才會衝突:

綁定的設備相同(不允許自動選擇設備)綁定的IP位址相同(不允許自動選擇IP)以下條件任意一個:要綁定的socket不允許重用已綁定的socket不允許重用已綁定的socket處於監聽狀態 relax參數為false

能夠重用埠號的情況

綁定的設備不同綁定的IP位址不同要綁定的socket允許重用,且已綁定的socket允許重用,且已綁定的socket不處於監聽狀態,relex參數為true。自動選擇埠的思路

相關焦點

  • 輕鬆玩轉函數式編程
    本文將通過以下 3 個部分來深入函數式編程。在編程的世界中,同一個問題,可以站在多個角度去分析解決,這些不同的解決方案就對應了不同的編程風格。 如果使用 webpack-chain 去修改配置,一個函數 api 就搞定了,而使用命令式的編程,則需要去逐步遍歷整個 webpack 配置文件,找出需要修改的點,才能進行修改,這無疑就大大減少了我們的工作量。
  • 深入作業系統,從內核理解網絡包的接收過程(Linux篇)
    我們現在想知道的是:當網絡包達到網卡,直到我們的recvfrom收到數據,這中間,究竟都發生過什麼?通過本文,你將從作業系統內部這一層深入理解網絡是如何實現的,以及各個部分之間是如何交互的。(上篇)》《不為人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)》《不為人知的網絡編程(三):關閉TCP連接時為什麼會TIME_WAIT、CLOSE_WAIT》《不為人知的網絡編程(四):深入研究分析TCP的異常關閉》《不為人知的網絡編程(五):UDP的連接性和負載均衡》
  • C風格的面向對象編程
    面向對象編程(OOP),最早是C++、java等面向對象語言的一個核心特點,之後發展成了一種編程思想。面向對象編程的主要特點是,把屬性(數據)與方法(函數)按照類型綁定,並按照類型之間的關係分層分模塊設計,通過基類指針和虛函數實現多態。
  • Linux進程間通信的socketpair()函數
    Linux的socketpair()函數,是創建一對互相連接著的socket描述符。類似TCP連接,兩個文件描述符都可以讀寫,sv[0]寫入的數據在sv[1]讀出,sv[1]寫入的數據在sv[0]讀出。
  • JAVA高並發網絡編程之BIO堵塞網絡編程
    上次說了網絡編程都是有作業系統統一的API的,每個語言有對它的實現,這次來一起說說通過java原生的socket編程完成BIO的網絡編程。
  • Java Socket網絡編程, 五個常見的異常發生的原因以及對應的解決方案
    空調WiFi西瓜,葛優同款沙發夕陽西下,我打開電腦學 java ……在Java網絡編程中,我們經常性的會碰到一些異常,有些異常是我們反覆碰見的,下面整理幾條常見的異常,供大家參考交流。1.java.net.
  • java8的函數式編程解析
    其實在java8就已經有java的函數式編程寫法,只是難度較大,大家都習慣了對象式用法,但在其它語言中都有函數式的用法,如js,scala,函數式其實是抽象到極致的思想。什麼是函數式編程 函數式編程並不是Java新提出的概念,其與指令編程相比,強調函數的計算比指令的計算更重要;與過程化編程相比,其中函數的計算可以隨時調用。
  • Linux伺服器編程簡介
    Linux伺服器編程的特點是異步高並發,代碼不能阻塞、不能休眠,以提高伺服器的並發效率。給nginx寫自定義的模塊,就是典型的Linux伺服器編程。nginx-rtmp-module就是一個開源的nginx模塊,它為nginx添加了rtmp協議的支持。
  • 函數式編程那些事兒
    函數式編程是一種編程範式,在其中它試圖將每個函數都綁定到純數學函數中。這是一種聲明式的編程風格,著重於解決什麼而不是如何解決。Clojure,Common Lisp,Erlang,Haskell和Scala是遵循函數式編程方法的一些著名程式語言。
  • 全網獨一份首發阿里大牛編寫UNIX網絡編程(附習題)
    (有多少讀者能看出這是20世紀80年代很流行的UUCP撥號網絡的地址? )現在UUCP網絡已經很罕見了,而無線網絡等新技術則變得無處不在!在這種背景下,新的網絡協議和編程範型業已開發出來,但程式設計師卻苦於找不到一本好的參考書來學習這些複雜的新技術。這本書填補了這一空白。
  • 高級前端進階,為什麼要使用call、apply、bind
    前言:call、apply、bind這3個方法的用處都是更改this指向,在學習call、apply、bind之前,需要先了解this,所以本文會先對this進行講解。第一個參數為要更改的對象,arg1, arg2, ...為傳入的參數與call、apply的不同在於,call、apply調用後會立即執行,返回值依賴於調用他們的函數。而bind,調用後會返回原函數,擁有指定的this以及初始參數。
  • 大數據入門:Scala函數式編程
    提到Scala,首先會提到的一個概念,就是函數式編程,這也是Scala語言區別與其他程式語言的典型特徵。Scala是一門多範式(multi-paradigm)的程式語言,設計初衷是要集成面向對象編程和函數式編程的各種特性。
  • 從Python到Haskell:程式設計師為何與函數式編程「墜入愛河」?
    儘管像Google這樣的大公司依賴於函數式編程的關鍵概念,但是普通程式設計師對此幾乎一無所知。這種情況即將改變了。不僅是Java或Python這樣的語言越來越多地採用了函數式編程的概念,類似Haskell這樣的新語言也正在完全實現函數式編程。簡單來說,函數式編程就是為不可變變量構建函數。
  • 寫Python 代碼不可不知的函數式編程技術
    近來,越來越多人使用函數式編程(functional programming)。因此,很多傳統的命令式語言(如 Java 和 Python)開始支持函數式編程技術。本文對 Python 中的函數式編程技術進行了簡單的入門介紹。本文適合對函數式編程有基本了解的讀者。
  • 知識分享之函數式編程的簡單介紹
    各位小夥伴們大家好,好久不見,這次小編要分享的一個知識是有關於函數式編程的。函數式編程是近年來比較熱門的一個話題,很多人都在談FunctionalProgramming,函數式編程有如下特點:函數即為數據,第一等公民。
  • NIO&AIO編程模型
    上圖是NIO的線程模型, 基於select實現, 這種線程模型的特點: 多條channel通過一個選擇器和單挑線程綁定, 並且在這種編程模型中, Channel中相關業務邏輯不允許存在耗時的任務 , 如果一定會有耗時的邏輯, 請將它們放置到線程池中去運行, 因為這種模型雖然做到了非阻塞, 但是他並不是真正的異步編程, 任何channel上的任何耗時的操作, 都會拖垮這個選擇器
  • Mars-java 3.0.0 發布,面向聲明式 API 編程(DAP)的框架
    本次更新帶來了兩個變動 面向聲明式API編程(DAP) 單元測試改動單元測試 取消了抽象方法before,而採用了註解的方式,在單測類上加上MarsTest註解即可面向聲明式API編程(DAP)
  • 函數式編程很難懂?其實真心很簡單
    話不多說,開始今天的學習:現在直播一直都很火,今天我們就用Java代碼簡單地模擬一個直播案例,以此來一步步說明什麼叫函數式編程。不要看這個名字好像挺難懂的樣子,其實很簡單,兩分鐘時間即可看完。一、直播間案例現在有一個直播平臺,它如何管理那些想直播的人?很簡單,制定規則就好了,在Java裡面接口的作用就是制定規則。①有一個接口叫LiveRoom,就是直播間。你想要直播?很簡單,實現直播間接口就好了,實現了你就能開個直播間直播了。
  • 2018年10月全國自學考試(網絡作業系統)真題
    2018年10月全國自學考試(網絡作業系統)真題一、單項選擇題(本大題共20小題,每小題2分,共40分)在每小題列出的四個備選項中只有一個是符合題目要求的,請將其代碼填寫在題後的括號內。錯選、多選或未選均無分。