日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

UNIX再学习 -- TCP/UDP 客户机/服务器

發(fā)布時間:2025/3/15 编程问答 11 豆豆
生活随笔 收集整理的這篇文章主要介紹了 UNIX再学习 -- TCP/UDP 客户机/服务器 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、TCP 客戶機/服務器

1、TCP 協(xié)議的基本特征

TCP 提供客戶機與服務器的連接

一個完整 TCP 通信過程需要依次經(jīng)歷三個階段

首先,客戶機必須建立與服務器的連接,所謂虛電路。 然后,憑借已建立好的連接,通信雙方相互交換數(shù)據(jù)。 最后,客戶機與服務器雙雙終止連接,結(jié)束通信過程。

TCP 保證數(shù)據(jù)傳輸?shù)目煽啃?/span>

TCP 的協(xié)議棧底層在向另一端發(fā)送數(shù)據(jù)時,會要求對方在一個給定的時間窗口內(nèi)返回確認。如果超過了這個時間窗口仍沒有收到確認,則 TCP 會重傳數(shù)據(jù)并等待更長的時間。只有在數(shù)次重傳均告失敗以后,TCP 才會最終放棄。TCP 含有用于動態(tài)估算數(shù)據(jù)往返時間(Round-Trip Time, RTT)的算法,因此它知道等待一個確認需要多長時間。

TCP 保證數(shù)據(jù)傳輸?shù)挠行蛐?/span>

TCP 的協(xié)議棧底層在向另一端發(fā)送數(shù)據(jù)時,會為所發(fā)送數(shù)據(jù)的每個字節(jié)指定一個序列號。即使這些數(shù)據(jù)字節(jié)沒有能夠按照發(fā)送時的順序到達接收方,接收方的 TCP 也可以根據(jù)它們的序列號重新排序,再把最后的結(jié)果交給應用程序。 如果 TCP 收到重復的數(shù)據(jù)(比如發(fā)送方認為數(shù)據(jù)已丟失并重傳,但它可能并沒有真的丟失,而只是由于網(wǎng)絡擁塞而被延誤),它也可以根據(jù)序列號做出判斷,丟棄重復的數(shù)據(jù)。

TCP 提供流量控制

TCP 的協(xié)議棧底層在從另一端接收數(shù)據(jù)時,會不斷告知對方它能夠接收多少字節(jié)的數(shù)據(jù),即所謂通告窗口。任何時候,這個窗口都反映了接收緩沖區(qū)可用空間的大小,從而確保不會因為發(fā)送方發(fā)送數(shù)據(jù)過快而導致接收緩沖區(qū)溢出。

TCP 是流式傳輸協(xié)議

TCP 是一個字節(jié)流協(xié)議,無記錄邊界
應用程序如果需要確定記錄邊界,必須自己實現(xiàn)

TCP 是全雙工的

在給定的連接上,應用程序在任何時候都既可以發(fā)送數(shù)據(jù)也可以接受數(shù)據(jù)。因此,TCP 必須跟蹤每個方向上數(shù)據(jù)流的狀態(tài)信息,如序列號和通告窗口的大小。

2、TCP 連接的生命周期

(1)建立連接

被動打開

服務器必須首先做好準備隨時接受來自客戶機的連接請求。

三路握手

客戶機的 TCP 協(xié)議棧服務器發(fā)送一個 SYN 分節(jié),告知對方自己將在連接中發(fā)送數(shù)據(jù)的初始序列號,謂之主動打開。 服務器的 TCP 協(xié)議棧向客戶機發(fā)送一個單個分節(jié),其中不僅包括對客戶機 SYN 分節(jié)的 ACK 應答,還包含服務器自己的 SYN 分節(jié),以告知對方自己再同一連接中發(fā)送數(shù)據(jù)的初始序列號。 客戶機的 TCP 協(xié)議棧向服務器返回 ACK 應答,以表示對服務器所發(fā) SYN 的確認。

(2)交換數(shù)據(jù)

一旦連接建立,客戶機即可構(gòu)造請求并發(fā)往服務器。 服務器接收并處理來自客戶機的請求包,構(gòu)造響應包。 服務器向客戶機發(fā)送響應包,同時捎帶對客戶機請求包的 ACK 應答。到哪如果服務器處理請求和構(gòu)造響應的時間長于 200 毫秒,則應答也可能先于響應發(fā)出。 客戶機接收來自服務器的響應包,同時向?qū)Ψ桨l(fā)送 ACK 應答。

(3)終止連接

客戶機或者服務器主動關(guān)閉連接,TCP 協(xié)議棧向?qū)Ψ桨l(fā)送 FIN 分節(jié),表示數(shù)據(jù)通信結(jié)束。如果此時尚有數(shù)據(jù)滯留于發(fā)送緩沖區(qū)中,則 FIN 分節(jié)跟在所有未發(fā)送數(shù)據(jù)之后。 接收到 FIN 分節(jié)的另一端執(zhí)行被動關(guān)閉,一方面通過 TCP 協(xié)議棧向?qū)Ψ桨l(fā)送 ACK 應答,另一方面向應用程序傳遞文件結(jié)束符。如果此時接收緩沖區(qū)不空,則將所接收到的 FIN 分節(jié)追加到接收緩沖區(qū)的末尾。 一段時間以后,方才接收到 FIN 分節(jié)的進程關(guān)閉自己的連接,同時通過 TCP 協(xié)議棧向?qū)Ψ桨l(fā)送 FIN 分節(jié)。 對方在收到 FIN 分節(jié)后發(fā)送 ACK 應答。

3、常用函數(shù)

(1)函數(shù) listen:啟動偵聽

在指定套接字上啟動對連接請求的偵聽 #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog); 返回值:成功返回 0,失敗返回 -1

《1》參數(shù)解析

sockfd:套接字描述符 backlog:未決鏈接請求的最大值

《2》函數(shù)解析

socket 函數(shù)所創(chuàng)建的套接字一律被初始化為主動套接字,即可以通過后續(xù) connect 函數(shù)調(diào)用向服務器發(fā)起連接請求的客戶機套接字。listen 函數(shù)可以將一個這樣的主動套接字轉(zhuǎn)換為被動套接字,既可以等待并接受來自客戶機的連接請求的服務器套接字。 被 listen 函數(shù)啟動偵聽的套接字將由 CLOSED 狀態(tài)轉(zhuǎn)入 LISETN 狀態(tài)。 客戶機調(diào)用 connect 函數(shù)即開啟了 TCP 連接建立的第一路握手:通過協(xié)議棧向服務器發(fā)送 SYN 分節(jié)。服務器的 LISTEN 套接字一旦收到該分節(jié),即創(chuàng)建一個新的處于 SYN_RCVD 裝填的套接字,并將其排入未完成連接隊列。 服務器的 TCP 協(xié)議棧不斷監(jiān)視未完成連接隊列的狀態(tài),并在適當?shù)臅r機依次處理其中等待連接的套接字。一旦某個 套接字上的第二、三路握手完成,由 SYN_RCVD 狀態(tài)轉(zhuǎn)入 ESTABLISTEN 狀態(tài),即被移送到已完成連接隊列。 兩個隊列中的套接字個數(shù)之和不能超過 backlog 參數(shù)值。
若未完成連接隊列和已完成連接隊列中的套接字個數(shù)之和已經(jīng)達到 backlog,此時又有客戶機通過 connect 函數(shù)發(fā)起連接請求,則該請求所產(chǎn)生的 SYN 分節(jié)將被服務器的 TCP 協(xié)議棧直接忽略。客戶機的 TCP 協(xié)議棧會因第一路握手應答超時而重發(fā) SYN 分節(jié),期望不久能在未決隊列中找到空閑位置。若多次重發(fā)均告失敗,則客戶機放棄,connect 函數(shù)返回失敗。 客戶機對 connect 函數(shù)的調(diào)用在第二路握手完成時即返回,而此時服務器連接套接字可能還子啊未完成連接隊列(第三路握手尚未完成)或已完成連接隊列(套接字尚未返回給用戶進程)中。這種情況下客戶機發(fā)送的數(shù)據(jù),會被服務器的 TCP 協(xié)議棧排隊緩存,直到接收緩沖區(qū)滿為止。

(2)函數(shù) accept:等待連接

在指定套接字上等待并接受連接請求 #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 返回值:成功返回連接套接字描述符,失敗返回 -1

《1》參數(shù)解析

sockfd:偵聽套接字描述符 addr:輸出連接請求發(fā)起者地址結(jié)構(gòu) addrlen:輸入/輸出,連接請求發(fā)起者地址結(jié)構(gòu)長度(以字節(jié)為單位)

《2》函數(shù)解析

accept 函數(shù)由 TCP 服務器調(diào)用,返回排在已完成連接隊列首部的連接套接字對象的描述符,若隊列為空則阻塞。 若 accept 函數(shù)執(zhí)行成功,則通過 addr 和 addrlen 向調(diào)用者輸出發(fā)起連接請求的客戶機的協(xié)議地址及其字節(jié)長度。 注意 addrlen 既是輸入?yún)?shù)也是輸出參數(shù)。調(diào)用 accept 函數(shù)時,指針 addrlen 所指向的變量被初始化 addr 結(jié)構(gòu)體的字節(jié)大小;等到該函數(shù)返回時,該指針的目標則被更新為系統(tǒng)內(nèi)核保存在 addr 結(jié)構(gòu)體內(nèi)的實際字節(jié)數(shù)。 accept 函數(shù)成功返回的是一個有別于其參數(shù)套接字,由系統(tǒng)內(nèi)核自動生成的全新套接字描述符。它代表與客戶機的 TCP 連接,因此被稱為連接套接字,而該函數(shù)的第一個參數(shù)則被稱為偵聽套接字。通常一個服務器只有一個偵聽套接字,且一直存在直到服務器關(guān)閉,而連接套接字則是一個客戶機一個,專門負責與該客戶機的通信。

(3)函數(shù) recv:接收數(shù)據(jù)

通過指定套接字接收數(shù)據(jù) #include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); 返回值:成功返回實際接收到的字節(jié)數(shù),失敗返回 -1

《1》參數(shù)解析

sockfd:套接字描述符 buf:應用程序接收緩沖區(qū) len:期望接收的字節(jié)數(shù) flags:接收標志,一般取 0,還可取以下值:
? ? MSG_DONTWAIT ? ?以非阻塞方式接受數(shù)據(jù)
? ? MSG_OOB ? ? ? ? ? ? ? 接收帶外數(shù)據(jù) ? ? MSG_PEEK ? ? ? ? ? ? ? 只查看可接收的數(shù)據(jù),函數(shù)返回后數(shù)據(jù)依然留在接收緩沖區(qū)中
如前所述,客戶機或者服務器主動關(guān)閉連接,TCP 協(xié)議棧向?qū)Ψ桨l(fā)送 FIN 分節(jié),表示數(shù)據(jù)通信結(jié)束,接收到 FIN 分節(jié)的另一端執(zhí)行被動關(guān)閉,一方面通過 TCP 協(xié)議棧向?qū)Ψ桨l(fā)送 ACK 應答,另一方面向應用程序傳遞文件結(jié)束符,此時 recv 函數(shù)返回 0.

《2》阻塞于非阻塞

套接字 I/O 的缺省方式都是阻塞的。對于 TCP 而言,如果接收緩沖區(qū)中沒有數(shù)據(jù),recv 函數(shù)將會阻塞,直到有數(shù)據(jù)到來并被復制到 buf 緩沖區(qū)時才會返回。此時所接收都的數(shù)據(jù)可能比 len 參數(shù)期望接收的字節(jié)數(shù)少。除非調(diào)用 recv 函數(shù)時使用 MSG_WAITALL 標志,不接收到 len 字節(jié)的數(shù)據(jù),函數(shù)就不返回。但即便使用了 MSG_WAITALL 標志,實際接收到的字節(jié)數(shù)在以下三種情況下仍然可能比期望的少。 ? ? 函數(shù)被信號中斷 ? ? 連接被對方終止 ? ? 發(fā)生套接字錯誤 MAG_DONTWAIT 標志令接收過程以非阻塞方式進行,即便收不到數(shù)據(jù),recv 函數(shù)也會立即返回,返回值 -1,errno 為 EAGAIN 或 EWOULDBLOCK

(4)函數(shù) send:發(fā)送數(shù)據(jù)

通過指定套接字發(fā)送數(shù)據(jù) #include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); 返回值:成功返回實際被發(fā)送的字節(jié)數(shù),失敗返回 -1

《1》參數(shù)解析

sockfd:套接字描述符 buf:應用程序發(fā)送緩沖區(qū) len:期望發(fā)送的字節(jié)數(shù) flags:發(fā)送標志,一般取 0,還可以取以下值: ? ? MSG_DONTWAIT ? ?以非阻塞方式發(fā)送數(shù)據(jù) ? ? MSG_OOB ? ? ? ? ? ? ? 發(fā)送帶外數(shù)據(jù) ? ? MSG_DONTROUTE ?不查路由器,直接在本地網(wǎng)絡中尋找目的主機

《2》阻塞于非阻塞

套接字 I/O的缺省方式都是阻塞的。對于 TCP 而言,如果發(fā)送緩沖區(qū)中沒有足夠的空閑空間,send 函數(shù)將會阻塞,直到其空閑空間足以容納 len 字節(jié)的待發(fā)送數(shù)據(jù),并在將全部待發(fā)送數(shù)據(jù)復制到發(fā)送緩沖區(qū)后才會返回。 MSG_DONTWAIT 標志令發(fā)送過程以非阻塞方式進行,即便發(fā)送緩沖區(qū)中一個字節(jié)的空閑空間都沒有,send 函數(shù)也會立即返回,返回值為 -1,errno 為 EAGAIN 或 EWOULDBLOCK。 在非阻塞方式下,如果發(fā)送緩沖區(qū)中尚有少量空閑空間,則會將部分待發(fā)送數(shù)據(jù)復制到發(fā)送緩沖區(qū),同時返回復制到發(fā)送緩沖區(qū)中的字節(jié)數(shù)。

4、編程模型

基于 TCP 協(xié)議實現(xiàn)網(wǎng)絡通信的編程模型

5、服務模型

迭代服務

服務器在單線程中以循環(huán)迭代的方式依次處理每個客戶機的業(yè)務需求。迭代模型的前提是針對每個客戶機的處理時間必須足夠短暫,否則會延誤對其客戶機的響應。

并發(fā)服務

主進程阻塞在 accept 函數(shù)上。每當一個客戶機與服務器建立連接,accept 函數(shù)返回,即通過 fork 函數(shù)創(chuàng)建子進程,主進程繼續(xù)等待新的連接,子進程處理客戶機業(yè)務。

首先服務器主進程阻塞于針對偵聽套接字的 accept 調(diào)用,客戶機進程通過 connect 函數(shù)向服務器發(fā)起連接請求
客戶機的連接請求被系統(tǒng)內(nèi)核接受,服務器主進程從 accept 函數(shù)中返回,同時得到可用于通信的連接套接字
服務器主進程調(diào)用 fork 函數(shù)創(chuàng)建子進程,子進程復制父進程的文件描述符,因此子進程也有偵聽和連接兩個套接字描述符

服務器主進程關(guān)閉連接套接字;服務器子進程關(guān)閉偵聽套接字。主進程通過循環(huán)繼續(xù)阻塞于針對偵聽套接字的 accept 調(diào)用,而子進程則通過連接套接字與客戶機通信


套接字描述符與普通的我那件描述符一樣,是帶有引用計數(shù)的。在一個套接字描述符上調(diào)用 close 函數(shù),并不一定真的關(guān)閉該套接字,而只是將其引用計數(shù)減一。只有當套接字描述符的引用計數(shù)被減到零時,才真的會釋放該套接字對象所占用的資源,并向?qū)Ψ桨l(fā)送 FIN 分節(jié)。因此服務器主進程關(guān)閉連接套接字,并不會影響子進程通過該套接字與客戶機通信。同理,服務器子進程關(guān)閉偵聽套接字也不會影響主進程通過套接字繼續(xù)等待連接。

如果服務器主進程在創(chuàng)建子進程后不關(guān)閉連接套接字,一方面將耗盡其可用文件描述符;令一方面在子進程結(jié)束通信關(guān)閉鏈接套接字時,其描述符上的引用計數(shù)只會由 2 變成 1,而不會變成 0,TCP 協(xié)議棧將永遠保持此連接。

6、示例說明

基于 TCP 協(xié)議的客戶機與服務器

//服務器 tcpA.c #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> int main() {int listenfd = socket (AF_INET, SOCK_STREAM, 0);if (listenfd == -1){perror ("socket");exit (EXIT_FAILURE);}struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons (8888);addr.sin_addr.s_addr = INADDR_ANY;if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1){perror ("bind");exit (EXIT_FAILURE);}if (listen (listenfd, 1024) == -1){perror ("listen");exit (EXIT_FAILURE);}struct sockaddr_in addrcli = {};socklen_t addrlen = sizeof (addrcli);int connfd = accept (listenfd, (struct sockaddr*)&addrcli, &addrlen);if (connfd == -1){perror ("accept");exit (EXIT_FAILURE);}printf ("服務器已接受來自%s:%hu客戶機的連接請求\n", inet_ntoa (addrcli.sin_addr),ntohs (addrcli.sin_port));char buf[1024];ssize_t rcvd = recv (connfd, buf, sizeof (buf), 0);if (rcvd == -1){perror ("recv");exit (EXIT_FAILURE);}if (rcvd == 0){printf ("客戶機已關(guān)閉連接\n");exit (EXIT_FAILURE);}buf[rcvd] = '\0';printf ("客戶端說:%s\n", buf);printf ("服務器說:");gets (buf);ssize_t sent = send (connfd, buf, strlen (buf) * sizeof (buf[0]), 0);if (sent == -1){perror ("send");exit (EXIT_FAILURE);}if (close (listenfd) == -1){perror ("close");exit (EXIT_FAILURE);}if (close (connfd) == -1){perror ("close");exit (EXIT_FAILURE);}return 0; } //客戶端 tcpB.c #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> int main() {int listenfd = socket (AF_INET, SOCK_STREAM, 0);if (listenfd == -1){perror ("socket");exit (EXIT_FAILURE);}struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons (8888);addr.sin_addr.s_addr = inet_addr("127.0.0.1");if (connect (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1){perror ("connect");exit (EXIT_FAILURE);}char buf[1024] = "你好,服務器";printf ("客戶端說:%s\n", buf);ssize_t sent = send (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0);if (sent == -1){perror ("send");exit (EXIT_FAILURE);}ssize_t rcvd = recv (listenfd, buf, sizeof (buf), 0);if (rcvd == -1){perror ("recv");exit (EXIT_FAILURE);}buf[rcvd] = '\0';printf ("服務器說:%s\n", buf);if (close (listenfd) == -1){perror ("close");exit (EXIT_FAILURE);}return 0; } 輸出結(jié)果: 在一個終端執(zhí)行: # ./tcpA 服務器已接受來自127.0.0.1:41428客戶機的連接請求 客戶端說:你好,服務器 服務器說:hello另一個終端執(zhí)行: # ./tcpB 客戶端說:你好,服務器 服務器說:hello

二、UDP 客戶機/服務器

1、UDP 協(xié)議的基本特點

UDP 不提供客戶機與服務器的連接

UDP 的客戶機與服務器不必存在長期關(guān)系。一個 UDP 的客戶機在通過一個套接字向一個 UDP 服務器發(fā)送了一個數(shù)據(jù)報之后,馬上可以通過同一個套接字向另一個 UDP 服務器發(fā)送另一個數(shù)據(jù)報。同樣,一個 UDP 服務器也可以通過同一個套接字接收來自不同客戶機的數(shù)據(jù)報。

UDP 不保證數(shù)據(jù)傳輸?shù)目煽啃院陀行蛐?/span>

UDP 的協(xié)議棧底層不提供諸如確認、超時重傳、RTT估算以及序列號等機制。因此 UDP 數(shù)據(jù)報在網(wǎng)絡傳輸?shù)倪^程中,可能丟失,也可能重復,甚至重新排序。應用程序必須自己處理這些情況。

UDP 不提供流量控制

UDP 協(xié)議棧底層只是一味地按照發(fā)送方的速率發(fā)送數(shù)據(jù),全然不顧接收方的緩沖區(qū)是否裝得下。

UDP 是記錄式傳輸協(xié)議

每個 UDP 數(shù)據(jù)報都有一定長度,一個數(shù)據(jù)報就是一條記錄。如果數(shù)據(jù)報正確地到達了目的地,那么數(shù)據(jù)報的長度將被傳遞接收方的應用進程。

UDP 是全雙工的

在一個 UDP 套接字上,應用程序在任何時候都既可以發(fā)送數(shù)據(jù)也可以接受數(shù)據(jù)。

2、常用函數(shù)

(1)函數(shù) recvfrom:接收數(shù)據(jù)

從指定的地址結(jié)構(gòu)接收數(shù)據(jù) #include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen); 返回值:成功返回實際接收到的字節(jié)數(shù),失敗返回 -1

《1》參數(shù)解析

sockfd:套接字描述符 buf:應用程序接收緩沖區(qū) len:期望接收的字節(jié)數(shù) flag:接收標志,一般取 0,還可取以下值: ? ? MSG_DONTWAIT ? ?以非阻塞方式接收數(shù)據(jù) ? ? MSG_OOB ? ? ? ? ? ? ? 接收帶外數(shù)據(jù) ? ? MSG_PEEK ? ? ? ? ? ? ? 只查看可接收的數(shù)據(jù),函數(shù)返回后數(shù)據(jù)依然留在接收緩沖區(qū)中 ? ? MSG_WAITALL ? ? ? ?等待所有數(shù)據(jù),即不接收到 len 字節(jié)的數(shù)據(jù),函數(shù)就不返回 src_addr:輸出數(shù)據(jù)報發(fā)送者的地址結(jié)構(gòu),可置為 NULL addrlen:輸出 src_addr 參數(shù)所指向內(nèi)存塊的字節(jié)數(shù),輸出數(shù)據(jù)發(fā)送者地址結(jié)構(gòu)的字節(jié)數(shù),可置為 NULL

《2》函數(shù)解析

recvfrom 函數(shù)返回 0,表示接收到一個空數(shù)據(jù)報(只有 IP 和 UDP 包頭而無數(shù)據(jù)內(nèi)容),與對方是否關(guān)閉套接字無關(guān)。

(2)函數(shù) sendto:發(fā)送數(shù)據(jù)

向指定的地址結(jié)構(gòu)發(fā)送數(shù)據(jù) #include <sys/types.h> #include <sys/socket.h> ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); 返回值:成功返回實際被發(fā)送的字節(jié)數(shù),失敗返回 -1

《1》參數(shù)解析

sockfd:套接字描述符 buf:應用程序發(fā)送緩沖區(qū) len:期望發(fā)送的字節(jié)數(shù) flags:發(fā)送標志,一般取 0,還可取以下值: ? ? MSG_DONTWAIT ? ?以非阻塞方式發(fā)送數(shù)據(jù) ? ? MSG_OOB ? ? ? ? ? ? ? 發(fā)送帶外數(shù)據(jù) ? ? MSG_DONTROUTE 不查路由表,直接在本地網(wǎng)絡中尋找目的主機 dest_addr:數(shù)據(jù)報接收者的地址結(jié)構(gòu) addrlen:數(shù)據(jù)報接收者地址結(jié)構(gòu)的字節(jié)數(shù)

3、編程模型

基于 UDP 協(xié)議的無連接編程模型


UDP 服務器的阻塞焦點不在 accept 函數(shù)上,而在 recvfrom 函數(shù)上。任何一個 UDP 客戶機通過 sendto 函數(shù)發(fā)送的請求數(shù)據(jù)都可以被 recvfrom 函數(shù)返回給 UDP 服務器,其輸出的客戶機地址結(jié)構(gòu) src_addr 可直接被用于向客戶機返回響應時調(diào)用 sendto 函數(shù)的輸入 dest_addr

基于 UDP 協(xié)議的有連接編程模型


UDP 的 connect 函數(shù)與 TCP 的 connect 函數(shù)完全不同,既無三路握手,亦無虛擬電路,而僅僅是將傳遞給該函數(shù)的對方地址結(jié)構(gòu)緩存在套接字對象中。此后收發(fā)數(shù)據(jù)時,可不使用 recvfrom/sendto 函數(shù),而是使用 recv/send 或者 read/write 函數(shù),直接和所連接的對方主機通信。

3、服務模型

迭代服務

基于 UDP 協(xié)議建立通信的客戶機和服務器,不需要維持長期的連接。因此 UDP 服務器在一個單線程中,以循環(huán)迭代的方式即可處理來自不同客戶機的業(yè)務需求。

4、示例說明

基于 UDP 協(xié)議的客戶機和服務器

//服務器 udpA.c #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> int main() {int listenfd = socket (AF_INET, SOCK_DGRAM, 0);if (listenfd == -1){perror ("socket");exit (EXIT_FAILURE);}struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons (8888);addr.sin_addr.s_addr = INADDR_ANY;if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1){perror ("bind");exit (EXIT_FAILURE);}char buf[1024];struct sockaddr_in addrcli = {};socklen_t addrlen = sizeof (addrcli);ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrcli, &addrlen);if (rcvd == -1){perror ("recvfrom");exit (EXIT_FAILURE);}buf[rcvd] = '\0';printf ("客戶端說:%s\n", buf);printf ("服務器說:");gets (buf);ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addrcli, sizeof (addrcli));if (sent == -1){perror ("send");exit (EXIT_FAILURE);}if (close (listenfd) == -1){perror ("close");exit (EXIT_FAILURE);}return 0; } //客戶端 udpB.c #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> int main() {int listenfd = socket (AF_INET, SOCK_DGRAM, 0);if (listenfd == -1){perror ("socket");exit (EXIT_FAILURE);}struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons (8888);addr.sin_addr.s_addr = inet_addr("127.0.0.1");char buf[1024] = "你好,服務器";printf ("客戶端說:%s\n", buf);ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addr, sizeof (addr));if (sent == -1){perror ("send");exit (EXIT_FAILURE);}struct sockaddr_in addrser = {};socklen_t addrlen = sizeof (addrser);ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrser, &addrlen);if (rcvd == -1){perror ("recvfrom");exit (EXIT_FAILURE);}buf[rcvd] = '\0';printf ("服務器說:%s\n", buf);if (close (listenfd) == -1){perror ("close");exit (EXIT_FAILURE);}return 0; } 輸出結(jié)果: 在一個終端執(zhí)行: # ./udpA 客戶端說:你好,服務器 服務器說:hello另一個終端執(zhí)行: # ./udpB 客戶端說:你好,服務器 服務器說:hello



與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖

總結(jié)

以上是生活随笔為你收集整理的UNIX再学习 -- TCP/UDP 客户机/服务器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。