Socket编程实践(12) --UDP编程基础
UDP特點
? ?無連接,面向數據報(基于消息,不會粘包)的數據傳輸服務;
? ?不可靠(可能會丟包,?亂序,?重復),?但因此一般情況下UDP更加高效;
UDP客戶/服務器模型
?
?
UDP-API使用
#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); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);/**實踐: 實現一個基于UDP的echo回聲server/client**/ //server端代碼 void echoServer(int sockfd); int main() {int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == -1)err_exit("socket error");struct sockaddr_in servAddr;servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = htonl(INADDR_ANY);servAddr.sin_port = htons(8001);if (bind(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("bind error");echoServer(sockfd); } void echoServer(int sockfd) {char buf[BUFSIZ];ssize_t recvBytes = 0;struct sockaddr_in clientAddr;socklen_t addrLen;while (true){memset(buf, 0, sizeof(buf));addrLen = sizeof(clientAddr);memset(&clientAddr, 0, addrLen);recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0,(struct sockaddr *)&clientAddr, &addrLen);//如果recvBytes=0, 并不代表對端連接關閉, 因為UDP是無連接的if (recvBytes < 0){if (errno == EINTR)continue;elseerr_exit("recvfrom error");}cout << buf ;if (sendto(sockfd, buf, recvBytes, 0,(const struct sockaddr *)&clientAddr, addrLen) == -1)err_exit("sendto error");} } /**client端代碼**/ void echoClient(int sockfd); int main() {int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == -1)err_exit("socket error");echoClient(sockfd);cout << "Client exiting..." << endl; } void echoClient(int sockfd) {struct sockaddr_in servAddr;servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");servAddr.sin_port = htons(8001);char buf[BUFSIZ] = {0};while (fgets(buf, sizeof(buf), stdin) != NULL){if (sendto(sockfd, buf, strlen(buf), 0,(const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("sendto error");memset(buf, 0, sizeof(buf));int recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);if (recvBytes == -1){if (errno == EINTR)continue;elseerr_exit("recvfrom error");}cout << buf ;memset(buf, 0, sizeof(buf));} }
實踐解析:編譯運行server,在兩個終端里各開一個client與server交互,可以看到server具有并發服務的能力。用<Ctrl+C>關閉server,然后再運行server,此時client還能和server聯系上。和前面TCP程序的運行結果相比較,我們可以體會無連接的含義。udp?協議來說,server與client?的界限更模糊了,只要知道對等方地址(ip和port)?都可以主動發數據。
?
UDP編程注意事項
? ?1.UDP報文可能會丟失(超時重傳)、重復、亂序(維護一個序號)
? ?2.UDP缺乏流量控制:當緩沖區寫滿以后,由于UDP沒有流量控制機制,因此會覆蓋緩沖區。
? ?3.UDP協議數據報文截斷:如果對端發送的UDP數據報大于本地接收緩沖區,報文可能被截斷,后面的部分會丟失(而不是像我們想象的下一次能夠接收到)。
? ?4.recvfrom可以返回0,并不代表連接關閉,因為UDP是無連接的,?代表發送端沒有發送任何數據[sendto可以發送數據0包(只含有UDP+IP首部40B)]。
? ?5.ICMP異步錯誤
? ?? ?觀察現象:使用上例,關閉UDP服務端,啟動客戶端,從鍵盤接受數據后,再發送數據。如果recvfrom中flags標志為0,?且client端沒有調用connect的情況下,?UDP客戶端阻塞在recvfrom位置(見測試代碼3);
? ?? ?說明:
? ?? ?? ?1)UDP發送報文的時,只把數據copy到發送緩沖區。在服務器沒有起來的情況下,可以發送成功。
? ?? ?? ?2)所謂ICMP異步錯誤是指:發送的報文的時候,沒有錯誤,接受報文recvfrom的時候,回收到ICMP應答.
? ?? ?? ?3)異步的錯誤,無法返回未連接的套接字,?因此如果上例我們調用了connect,?是可以收到該異步ICMP報文的;
? ?6.UDP調用connect
? ?? ?1)UDP調用connet,并沒有三次握手,只是維護了一個(和對等方的)狀態信息,?因此我們可以看到即使server沒有開啟,?client端的connect依然還可以正確返回的!(測試代碼如測試代碼2)
? ?? ?2)一但調用connect,?發送可以使用send/write,?接收可以使用recv/read函數(見測試代碼3)
? ?7.UDP外出接口的確定:
? ?? ?假設客戶端有多個IP地址,由connect?/sendto?函數提供的遠程地址的參數,系統會選擇一個合適的出口,比如Server的IP是192.168.2.10,?而客戶端現在的IP有?192.168.1.32?和?192.168.2.75?那么會自動選擇192.168.2.75?這個IP出去。
/**測試1: 測試注意點3, UDP報文截斷, recvfrom返回-1, errno值為EAGAIN**/ int main() {int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == -1)err_exit("socket error");struct sockaddr_in servAddr;servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = htonl(INADDR_ANY);servAddr.sin_port = htons(8001);if (bind(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("bind error");//給自己發送數據if (sendto(sockfd, "ABCDE", 5, 0,(const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("sendto error");for (int i = 0; i < 5; ++i){char ch;int recvBytes = recvfrom(sockfd, &ch, 1, MSG_DONTWAIT, NULL, NULL);if (recvBytes == -1){if (errno == EINTR)continue;else if (errno == EAGAIN)err_exit("recvfrom error");}elsecout << "char = " << ch << ", recvBytes = " << recvBytes << endl;} } /**測試2:將client端echoClient函數的代碼改造如下, 注意是在server端尚未開啟時執行該程序**/ void echoClient(int sockfd) {struct sockaddr_in servAddr;servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");servAddr.sin_port = htons(8001);// UDP client端調用connectif (connect(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("connect error");char buf[BUFSIZ] = {0};while (fgets(buf, sizeof(buf), stdin) != NULL){if (sendto(sockfd, buf, strlen(buf), 0,(const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("sendto error");memset(buf, 0, sizeof(buf));int recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);if (recvBytes == -1)err_exit("recvfrom error");cout << buf ;memset(buf, 0, sizeof(buf));} } /**測試3: client端在調用connect之后調用send, 而不是send**/ void echoClient(int sockfd) {struct sockaddr_in servAddr;servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");servAddr.sin_port = htons(8001);// UDP client端調用connectif (connect(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("connect error");char buf[BUFSIZ] = {0};while (fgets(buf, sizeof(buf), stdin) != NULL){if (send(sockfd, buf, strlen(buf), 0) == -1)err_exit("send error");memset(buf, 0, sizeof(buf));int recvBytes = recv(sockfd, buf, sizeof(buf), 0);if (recvBytes == -1)err_exit("recv error");cout << buf ;memset(buf, 0, sizeof(buf));} }總結
以上是生活随笔為你收集整理的Socket编程实践(12) --UDP编程基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小故事分享:千里马与苍蝇的故事
- 下一篇: 90后,一个即将成为程序员的我