關於網路,我們已經基本上看完了所有的前置動作(伺服器端3個、客戶端1個),本日的重點就是兩者如何搭上線。
accept
NAME
accept, accept4 - accept a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);
這個是伺服器端的接受用的系統呼叫。後面兩個傳指標呼叫是為了取得提出連接請求的客戶的位址資訊,前者則當然就是先前準備好的檔案描述子了。成功的話會回傳一個用來與之溝通的檔案描述子。
老樣子,在net/socket.c
中,
1499 SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
1500 int __user *, upeer_addrlen)
1501 {
1502 return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
1503 }
accept
呼叫直接在不使用第四個flags
參數的情況下引用accept4
呼叫。
1418 SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
1419 int __user *, upeer_addrlen, int, flags)
1420 {
1421 struct socket *sock, *newsock;
1422 struct file *newfile;
1423 int err, len, newfd, fput_needed;
1424 struct sockaddr_storage address;
1425
1426 if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
1427 return -EINVAL;
1428
1429 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
1430 flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
1431
1432 sock = sockfd_lookup_light(fd, &err, &fput_needed);
1433 if (!sock)
1434 goto out;
我們昨天就看過的sockfd_lookup_light
再度出現,這很正常,因為是從檔案描述子提取socket結構的重要方法。
1436 err = -ENFILE;
1437 newsock = sock_alloc();
1438 if (!newsock)
1439 goto out_put;
1440
1441 newsock->type = sock->type;
1442 newsock->ops = sock->ops;
1443
1444 /*
1445 * We don't need try_module_get here, as the listening socket (sock)
1446 * has the protocol module (sock->ops->owner) held.
1447 */
1448 __module_get(newsock->ops->owner);
創造一個新的socket作為聯絡之用。這裡不需要整組引用socket
呼叫的原因是,有些部份已經不用重來,而是可以直接挪用聆聽用得socket的設定,比方說他們必然是相同的協定之類,像是1441、1442兩行的作為。1448處理協定使用核心模組的情況,這也是前日在socket
呼叫的時候看過的。
1450 newfd = get_unused_fd_flags(flags);
1451 if (unlikely(newfd < 0)) {
1452 err = newfd;
1453 sock_release(newsock);
1454 goto out_put;
1455 }
1456 newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
1457 if (IS_ERR(newfile)) {
1458 err = PTR_ERR(newfile);
1459 put_unused_fd(newfd);
1460 sock_release(newsock);
1461 goto out_put;
1462 }
這也是socket
中看過的內容,也就是取得未利用的檔案描述子,然後配置檔案給他,然後將兩者連通。
1468 err = sock->ops->accept(sock, newsock, sock->file->f_flags);
1469 if (err < 0)
1470 goto out_fd;
1471
1472 if (upeer_sockaddr) {
1473 if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
1474 &len, 2) < 0) {
1475 err = -ECONNABORTED;
1476 goto out_fd;
1477 }
1478 err = move_addr_to_user(&address,
1479 len, upeer_sockaddr, upeer_addrlen);
1480 if (err < 0)
1481 goto out_fd;
1482 }
accept
介面看似一個動作,在這裡可以清楚的看見是->accept
的建立連結與->getname
的取得位址資訊兩個步驟。這兩者也分別在net/ipv4/af_inet.c
之中。先看inet_accept
:
671 int inet_accept(struct socket *sock, struct socket *newsock, int flags)
672 {
673 struct sock *sk1 = sock->sk;
674 int err = -EINVAL;
675 struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);
676
677 if (!sk2)
678 goto do_err;
679
680 lock_sock(sk2);
681
682 sock_rps_record_flow(sk2);
683 WARN_ON(!((1 << sk2->sk_state) &
684 (TCPF_ESTABLISHED | TCPF_SYN_RECV |
685 TCPF_CLOSE_WAIT | TCPF_CLOSE)));
686
687 sock_graft(sk2, newsock);
688
689 newsock->state = SS_CONNECTED;
690 err = 0;
691 release_sock(sk2);
692 do_err:
693 return err;
694 }
值得一提的是687行的sock_graft
指定了newsock
與sk2
之間的親子關係,因為sk2
是由sk
在網路層的處理得來,而newsock
卻是從原本的sock
得到創建的資訊。675行的accept
指向net/ipv4/inet_connection_sock.c
的inet_csk_accept
,其中會卡在inet_csk_wait_for_connect
一行等待著連接。
回到accept4
的尾聲,這時呼叫了inet_getname
而透過更下層的資料結構取得連接者的資訊。然後將相關資訊複製回使用者空間,之後:
1486 fd_install(newfd, newfile);
1487 err = newfd;
1488
1489 out_put:
1490 fput_light(sock->file, fput_needed);
1491 out:
1492 return err;
1493 out_fd:
1494 fput(newfile);
1495 put_unused_fd(newfd);
1496 goto out_put;
1497 }
將檔案描述子安裝到新的檔案上,大功告成。
connect
然後我們來看看客戶端的狀況。
NAME
connect - initiate a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
雖然和accept
很類似,但是用意不同;這裡的後兩個參數是要指定伺服器所在的位置用的,而非為了取得。
程式碼:
1517 SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
1518 int, addrlen)
1519 {
1520 struct socket *sock;
1521 struct sockaddr_storage address;
1522 int err, fput_needed;
1523
1524 sock = sockfd_lookup_light(fd, &err, &fput_needed);
1525 if (!sock)
1526 goto out;
1527 err = move_addr_to_kernel(uservaddr, addrlen, &address);
1528 if (err < 0)
1529 goto out_put;
1530
1531 err =
1532 security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
1533 if (err)
1534 goto out_put;
1535
1536 err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
1537 sock->file->f_flags);
1538 out_put:
1539 fput_light(sock->file, fput_needed);
1540 out:
1541 return err;
1542 }
1524行取得相對應的應用層socket物件,然後1527行將使用者空間的位址資訊複製到核心空間來。然後1536行呼叫inet_stream_connect
(這次不能靠亂猜了),
655 int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
656 int addr_len, int flags)
657 {
658 int err;
659
660 lock_sock(sock->sk);
661 err = __inet_stream_connect(sock, uaddr, addr_len, flags);
662 release_sock(sock->sk);
663 return err;
664 }
而__inet_stream_connect
就在上方不遠處。內容有一個狀態機:
584 switch (sock->state) {
585 default:
586 err = -EINVAL;
587 goto out;
588 case SS_CONNECTED:
589 err = -EISCONN;
590 goto out;
591 case SS_CONNECTING:
592 err = -EALREADY;
593 /* Fall out of switch with err, set for this state */
594 break;
595 case SS_UNCONNECTED:
596 err = -EISCONN;
597 if (sk->sk_state != TCP_CLOSE)
598 goto out;
599
600 err = sk->sk_prot->connect(sk, uaddr, addr_len);
601 if (err < 0)
602 goto out;
603
604 sock->state = SS_CONNECTING;
605
606 /* Just entered SS_CONNECTING state; the only
607 * difference is that return value in non-blocking
608 * case is EINPROGRESS, rather than EALREADY.
609 */
610 err = -EINPROGRESS;
611 break;
612 }
最一開始進入的當然是595行的未連接狀態,這裡有600行的sk->sk_prot->connect
呼叫,在範例程式的case指到net/ipv4/tcp_ipv4.c
的tcp_v4_connect
,裡面就是TCP的細節了。
614 timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
615
616 if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
617 int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
618 tcp_sk(sk)->fastopen_req &&
619 tcp_sk(sk)->fastopen_req->data ? 1 : 0;
620
621 /* Error code is set above */
622 if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))
623 goto out;
624
625 err = sock_intr_errno(timeo);
626 if (signal_pending(current))
627 goto out;
628 }
從狀態機離開之後會設定一個timeo
變數當作time out的期限。這裡因為給了O_NONBLOCK
所以都會是0,進而導致622行的inet_wait_for_connect
不會花時間等待。筆者猜想這是TCP連線的特質,假設伺服器存在,所以不需要等待。
633 if (sk->sk_state == TCP_CLOSE)
634 goto sock_error;
635
636 /* sk->sk_err may be not zero now, if RECVERR was ordered by user
637 * and error was received after socket entered established state.
638 * Hence, it is handled normally after connect() return successfully.
639 */
640
641 sock->state = SS_CONNECTED;
642 err = 0;
643 out:
644 return err;
645
646 sock_error:
647 err = sock_error(sk) ? : -ECONNABORTED;
648 sock->state = SS_UNCONNECTED;
...
最後的關鍵就是633行的判斷,經過上述手續之後,網路層的socket狀態是否是TCP_CLOSE
?如果是的話就只能錯誤收場,反之則可以進到641設定連接狀態。
本日我們大致看過了「伺服器—客戶」模型建立連線的背後機制。TCP/IP本身是個很龐大的架構,對於更深入的部份,不是筆者此時能夠探討的,只能瀏覽到核心如何管理相關的資料結構以及檔案安排,這樣未來要進一步學習的時候有個基礎知識。至此,雖然忽略的底層細節,但是已經可以看到兩者互相連通了。明日就將介紹連結建立之後的訊息傳送,也正式結束網路的部份。感謝各位讀者,我們明日再會!