<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          TCP編程入門簡介

          共 1074字,需瀏覽 3分鐘

           ·

          2022-02-11 08:06

          在前幾篇文章中,我們先從宏觀角度(TCP 概述)大致介紹了 tcp 的概念,然后從微觀角度(滑動窗口、擁塞窗口等)詳細說明了從 client 端和 server 端,tcp 是如何進行網絡控制的。在本文中,我們將通過一個 tcp 例子,將整個過程聯(lián)通起來,講解 tcp 從連接、發(fā)送以及關閉,整個流程是怎樣運行的。

          示例代碼

          server.c

          #include?
          #include?
          #include?
          #include?
          #include?
          #include?

          int?main(int?argc,?char?**)?{
          ????int?server_fd,?new_socket,?valread;
          ????struct?sockaddr_in?address;
          ????int?opt?=?1;
          ????int?addrlen?=?sizeof(address);
          ????char?buffer[1024]?=?{0};
          ????char?*hello?=?"hello?from?server";
          ??? int port = 8080;

          ????//?創(chuàng)建socket文件描述符
          ????if?((server_fd?=?socket(AF_INET,?SOCK_STREAM,?0))?==?0)?{
          ????????return?1;
          ????}


          ?????//?端口?&?地址復用
          ????if?(setsockopt(server_fd,?SOL_SOCKET,?SO_REUSEADDR?|?SO_REUSEPORT,
          ??????????????????????????????????????????????????&opt,?sizeof(opt)))?{
          ????????return?1;
          ????}
          ????address.sin_family?=?AF_INET;
          ????address.sin_addr.s_addr?=?INADDR_ANY;
          ????address.sin_port?=?htons(?port?);

          ????//?綁定8080端口
          ????if?(bind(server_fd,?(struct?sockaddr?*)&address,
          ?????????????????????????????????sizeof(address))<0)?{
          ????????perror("bind?failed");
          ????????return?1;
          ????}
          ????if?(listen(server_fd,?3)?????????return?1;
          ????}
          ????if?((new_socket?=?accept(server_fd,?(struct?sockaddr?*)&address,
          ???????????????????????(socklen_t*)&addrlen))<0)?{
          ????????return?1;
          ????}
          ????valread?=?read(?new_socket?,?buffer,?1024);
          ????//?向client發(fā)送消息
          ????send(new_socket?,?hello?,?strlen(hello)?,?0?);
          ????close(new_socket);
          ????close(server_fd);
          ????return?0;
          }

          client.c

          #include?
          #include?
          #include?
          #include?
          #include?

          int?main(int?argc,?char?const?*argv[])?{
          ????int?sock?=?0,?valread;
          ????struct?sockaddr_in?serv_addr;
          ????char?*hello?=?"hello?from?client";
          ????char?buffer[1024]?=?{0};
          ????int?port?=?8080;
          ????if?((sock?=?socket(AF_INET,?SOCK_STREAM,?0))?return?-1;
          ????}

          ????serv_addr.sin_family?=?AF_INET;
          ????serv_addr.sin_port?=?htons(port);

          ????if(inet_pton(AF_INET,?"127.0.0.1",?&serv_addr.sin_addr)<=0)?{
          ????????return?1;
          ????}

          ????//?建立連接
          ????if?(connect(sock,?(struct?sockaddr?*)&serv_addr,?sizeof(serv_addr))?????????return?1;
          ????}
          ????send(sock?,?hello?,?strlen(hello)?,?0?);
          ????valread?=?read(?sock?,?buffer,?1024);
          ????close(sock);
          ????return?0;
          }

          上面是 tcp 的示例代碼,很簡單,算是網絡編程的入門級代碼。讀者可以使用下面命令進行編譯:

          gcc?-g?server.c?-o?server
          gcc?-g?client.c?-o?client

          過程分析

          下面,我們分析前面的示例代碼, 對于 server 端:

          1、創(chuàng)建 socket

          2、bind 端口

          3、listen

          4、accept 新的連接,獲取新連接的 socket

          5、通過 read 函數(shù),接收數(shù)據(jù)

          6、通過 send 函數(shù),發(fā)送數(shù)據(jù)

          7、調用 close 函數(shù)關閉 socket

          對于 client 端:

          1、創(chuàng)建 socket

          2、通過 connect 與 server 端建立連接

          3、通過 send 發(fā)送數(shù)據(jù)

          4、通過 recv 接收數(shù)據(jù)

          5、close socket

          對于 server 端和 client 端的每個步驟,其都是有關聯(lián)的,比如 client 端調用 connect 之后,server 端將從 accept 函數(shù)返回,其返回值是新連接的 socket 等等。下面,我們將以一個圖的方式來了解兩端的聯(lián)系。

          詳述

          socket()

          #include?

          int?socket?(int?domain,?int?type,?int?protocol);

          socket 系統(tǒng)調用通過分配一個新的描述符來創(chuàng)建一個新的套接字。它唯一標識一個 socket。這個 socket 描述字跟文件描述字一樣,后續(xù)的操作都有用到它,把它作為參數(shù),通過它來進行一些讀寫操作。成功時返回一個非負的文件描述符編號,錯誤時返回-1。

          • domain:即協(xié)議域,又稱為協(xié)議族(family)。常用的協(xié)議族有,AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或稱 AF_UNIX,Unix 域 socket)、AF_ROUTE 等等。
          • type:指定 socket 類型。常用的 socket 類型有,SOCK_STREAM(流式套接字)、SOCK_DGRAM(數(shù)據(jù)報式套接字)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET 等等
          • protocol:就是指定協(xié)議。常用的協(xié)議有,IPPROTO_TCP、PPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC 等,它們分別對應 TCP 傳輸協(xié)議、UDP 傳輸協(xié)議、STCP 傳輸協(xié)議、TIPC 傳輸協(xié)議。

          在此,需要注意的是,并不是上面的 type 和 protocol 可以隨意組合的,如 SOCK_STREAM 不可以跟 IPPROTO_UDP 組合。當 protocol 為 0 時,會自動選擇 type 類型對應的默認協(xié)議。由于數(shù)據(jù)流的 IP 協(xié)議就是 TCP,前兩個參數(shù)已經有效地指定了 TCP。因此,第三個參數(shù)可以保留為 0,讓操作系統(tǒng)分配一個默認協(xié)議(這將是 IPPROTO_TCP)。

          bind()

          ?#include???????????/*?See?NOTES?*/
          ?#include?

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

          bind 函數(shù)用來綁定 socket 的本地地址和端口號。

          • sockfd 是待綁定的 socket 對應的文件描述符。
          • addr 一個 const struct sockaddr *指針,指向要綁定給 sockfd 的協(xié)議地址。
          • addrlen 必須是結構體的大小 sockaddr_in(或正在使用的任何結構)。
          struct?in_addr?{
          u_int32_t?s_addr;
          };

          struct?sockaddr_in?{
          short?sin_family;
          u_short?sin_port;
          struct?in_addr?sin_addr;
          char?sin_zero[8];
          };

          sin_port 指定要使用的 16 位端口號。它在網絡中給出(大端)字節(jié)順序,因此必須使用 htons 將主機字節(jié)順序轉換為網絡字節(jié)順序。

          將 sin 端口值指定為 0 會告訴操作系統(tǒng)選擇端口號。操作系統(tǒng)將在 1024 和 5000 之間選擇一個未使用的端口號作為應用程序。注意只有超級用戶才能綁定 1024 以下的端口號。

          對于 bind 系統(tǒng)調用,此處需要注意的是,在 client 不需要強制調用,而在 server 端需要強制調用,這是因為,在 server 端,bind 之后,要進行端口監(jiān)聽。而在 client 端,如果代碼中沒有執(zhí)行此系統(tǒng)調用,則在內核中會隱式執(zhí)行此調用。

          connect()

          #include???????????/*?See?NOTES?*/
          #include?

          int?connect(int?sockfd,?const?struct?sockaddr?*addr,?socklen_t?addrlen);

          通過執(zhí)行 connect 函數(shù),與 server 端建立連接。

          • sockfd socket 對應的文件描述符。
          • addr 指定了對端的 ip 和端口。
          • addrlen 指定了上面結構體的大小

          經典的三次握手,就在 connect 階段,如下圖:

          listen()

          ?#include???????????/*?See?NOTES?*/
          ?#include?

          ?int?listen(int?sockfd,?int?backlog);

          listen() 函數(shù)的主要作用就是將套接字( sockfd )變成被動的連接監(jiān)聽套接字(被動等待客戶端的連接),至于參數(shù) backlog 的作用是設置內核中連接隊列的長度(該參數(shù)在現(xiàn)在大部分系統(tǒng)中已經不被使用),listen()的作用僅僅告訴內核一些信息。成功返回 0,否則返回-1.

          • sockfd 是綁定到要接受的端口的未連接套接字連接。
          • backlog 以前指定了操作系統(tǒng)在應用程序之前接受的連接數(shù)。現(xiàn)在這個值已經沒用了。

          這里需要注意的是,listen()函數(shù)不會阻塞,它主要做的事情為,將該套接字和套接字對應的連接隊列長度告訴 Linux 內核,然后,listen()函數(shù)就結束。

          accept()

          #include???????????/*?See?NOTES?*/
          #include?

          int?accept(int?sockfd,?struct?sockaddr?*addr,?socklen_t?*addrlen);

          accept()函數(shù)功能是,從處于 established 狀態(tài)的連接隊列頭部取出一個已經完成的連接,如果這個隊列沒有已經完成的連接,accept()函數(shù)就會阻塞,直到取出隊列中已完成的用戶連接為止。如果沒有客戶端連接,accept 將阻塞直到一個 做。錯誤返回 -1。

          • sockfd 指的是上面綁定以及 listen 的 socket 描述符
          • addr 在有連接過來的時候,里面的內容就是 client 端的信息
          • addrlen 上一個變量的長度

          需要注意的是,accept 的功能并不是建立連接,而是從當前連接的等待隊列中獲取一條連接。

          accept 的第一個參數(shù)為服務器的 socket 描述字,是服務器開始調用 socket()函數(shù)生成的,稱為監(jiān)聽 socket 描述字;而 accept 函數(shù)返回的是已連接的 socket 描述字。一個服務器通常通常僅僅只創(chuàng)建一個監(jiān)聽 socket 描述字,它在該服務器的生命周期內一直存在。內核為每個由服務器進程接受的客戶連接創(chuàng)建了一個已連接 socket 描述字,當服務器完成了對某個客戶的服務,相應的已連接 socket 描述字就被關閉。

          send()

          #include?
          #include?

          ssize_t?send(int?sockfd,?const?void?*buf,?size_t?len,?int?flags);

          向指定 socket 發(fā)送數(shù)據(jù)。

          • sockfd 指定 socket 描述符
          • buf 待發(fā)送的數(shù)據(jù) buf
          • len 數(shù)據(jù) buf 長度
          • flags 發(fā)送標記。其值為 MSG_CONFIRM MSG_DONTROUTE MSG_DONTWAIT MSG_EOR MSG_MORE MSG_NOSIGNAL MSG_OOB 中 1 個或者幾個的邏輯或或者邏輯與值

          對于 send()的詳細講解,請轉閱TCP之send&recv

          recv()

          #include?
          #include?

          ssize_t?recv(int?sockfd,?void?*buf,?size_t?len,?int?flags);

          從指定socket中讀取數(shù)據(jù)。

          • sockfd 指定 socket 描述符
          • buf 接收數(shù)據(jù) buf
          • len 接收數(shù)據(jù)buf 長度
          • flags 發(fā)送標記。其值為 MSG_CMSG_CLOEXEC MSG_DONTWAIT MSG_ERRQUEUE MSG_OOB MSG_PEEK MSG_TRUNC MSG_WAITALL 中 1 個或者幾個的邏輯或或者邏輯與值

          對于 recv()的詳細講解,請轉閱TCP之send&recv

          close()

          ?#include?

          ?int?close(int?fd);

          關閉socket。

          • fd為待關閉的文件描述符

          close 一個套接字的默認行為是把套接字標記為已關閉,然后立即返回到調用進程,該套接字描述符不能再由調用進程使用,也就是說它不能再作為send或recv的第一個參數(shù),然而TCP將嘗試發(fā)送已排隊等待發(fā)送到對端,發(fā)送完畢后發(fā)生的是正常的TCP連接終止序列。在多進程并發(fā)服務器中,父子進程共享著套接字,套接字描述符引用計數(shù)記錄著共享著的進程個數(shù),當父進程或某一子進程close掉套接字時,描述符引用計數(shù)會相應的減一,當引用計數(shù)仍大于零時,這個close調用就不會引發(fā)TCP的四路握手斷連過程。四次揮手就在這個步驟。


          END

          瀏覽 62
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产在线拍揄自揄拍无码视频 | 美日韩A片 | 中国一级黄色毛片 | 国产区激情| 少妇成人视频 |