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;
- else
- err_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;
- else
- err_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");
- }
- else
- cout << "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端調用connect
- if (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端調用connect
- if (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));
- }
- }