【Socket网络编程】16.UDP 循环读取recvfrom() 与 循环发送 sendto()
生活随笔
收集整理的這篇文章主要介紹了
【Socket网络编程】16.UDP 循环读取recvfrom() 与 循环发送 sendto()
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
@zhz:
疑問:有時候會看到某些代碼,sendto()時用了while循環, 而recvfrom()時沒使用while循環?
答:他們都可以使用循環語句,可參考TCP數據粘包的處理。
什么時候需要使用循環,什么時候不使用循環,可以看下面的分析:
以下其實是我根據自己項目使用的udp協議中的recvfrom()和sendto()進行測試沒問題后分析的。但是對于TCP粘包的問題,卻并非如此,并非recv每次只取一次完整發送的數據(UDP的recvfrom()為什么可以取這么準?),我目前還沒測試。
1. recvfrom()要使用與不使用循環的情況:
我們通常指定的接收端一次接收長度都會 >= 發送端一次發送的數據長度。通常情況下,我們發送端一次發送的數據長度都不會是固定的,所以就需要接收端設置一個合適的固定的接收長度,這個固定長度需要大于等于發送端一次發送的最大數據長度。
當recvfrom()函數指定buf的長度后,并且一次recvfrom()函數讀取到的數據小于指定長度max_length(這個是可以保證的),那么:
- 如果能確定每次recvfrom()實際讀取到的數據是發送端一次發送的完整數據,那就不用循環recvfrom()。
- 如果每次recvfrom()實際讀取到的數據不是發送端一次發送的完整數據,就需要循環recvfrom()。
2.sendto()要使用與不使用循環的情況:
sendto()一般情況下需要使用循環,因為假如一個數據包太大,如長度為10MB,一次sendto()發送到輸出緩沖區可能發不完整,此時就需要對sendto()使用循環發送,直到把10MB的數據都拷貝到輸出緩沖區。
sendto()函數中參數指定的數據長度,就是本次發送(就是寫入輸出緩沖區)的數據長度,都會提前計算好之后再填入,每次發送的數據長短可能不一樣,所以他就不是固定長度的。
而recvfrom()函數中指定的長度是固定的。
3.recvfrom()和sendto()例子:
recvfrom()和sendto()的第三個參數len都是指定第二個參數buf的長度。
- 1.recvfrom()從輸入緩沖區中拷貝數據到應用程序緩沖區buf,在此需要指定buf的長度。他的長度一般在定義緩沖buf時就定下來了,如
- 2.sendto()從輸出緩沖區中拷貝數據到應用程序緩沖區buf,在此需要指定buf的長度。他的長度都會提前計算好之后再填入,每次發送的數據長短可能不一樣,所以他就不是固定長度的:
sendto()
size_t UdpStream::write(const uint8_t* data, size_t length, uint8_t flag) {size_t total_nsent = 0;// if (flag) {// peer_sockaddr_.sin_addr.s_addr = htonl(INADDR_BROADCAST);// }peer_sockaddr_.sin_addr.s_addr = peer_addr_;peer_sockaddr_.sin_port = peer_broad_port_;SDEBUG << "sendto addr: " << inet_ntoa(peer_sockaddr_.sin_addr)<< ", port: " << ntohs(peer_sockaddr_.sin_port);while (length > 0) {ssize_t nsent =::sendto(sockfd_, data, length, 0, (struct sockaddr*)&peer_sockaddr_,(socklen_t)sizeof(peer_sockaddr_));if (nsent < 0) { // errorif (errno == EINTR) {continue;} else {// errorif (errno == EPIPE || errno == ECONNRESET) {status_ = Stream::Status::DISCONNECTED;errno_ = errno;} else if (errno != EAGAIN) {status_ = Stream::Status::ERROR;errno_ = errno;}return total_nsent;}}total_nsent += nsent;length -= nsent;data += nsent;}return total_nsent; }recvfrom()
size_t UdpStream::read(uint8_t* buffer, size_t max_length, uint8_t flag) {ssize_t ret = 0;struct sockaddr_in addrfrom;addrfrom.sin_addr.s_addr = htonl(INADDR_ANY);if (flag) {peer_sockaddr_.sin_addr.s_addr = htonl(INADDR_ANY);} else {addrfrom.sin_addr.s_addr = peer_sockaddr_.sin_addr.s_addr;}while ((ret = ::recvfrom(sockfd_, buffer, max_length, 0,(struct sockaddr*)&peer_sockaddr_,reinterpret_cast<socklen_t*>(&socklenth_))) < 0) {if (errno == EINTR) {continue;} else {// errorif (errno != EAGAIN) {status_ = Stream::Status::ERROR;errno_ = errno;}}return 0;}// 接收來自本車obu的數據包:0x63,0x69,0x64,0x69分別表示cidi的ASCII碼:99,105,100,105// 如果不是"cidi",1.如果是單播,就把ip保持為上一次成功單播的ip;// 2.如果是廣播,就把ip設為0.0.0.0(即htonl(INADDR_ANY)),即本機任意網卡的ipif (0x63 != buffer[0] && 0x69 != buffer[1] &&0x64 != buffer[2] && 0x69 != buffer[3]) {peer_sockaddr_.sin_addr.s_addr = addrfrom.sin_addr.s_addr;}// // 0x60,0x61 分別對應'`'和'a'的ASCII碼 96(`),97(a)// if (buffer[0] != 0x60 && buffer[1] != 0x61) {// peer_sockaddr_.sin_addr.s_addr = addrfrom.sin_addr.s_addr;// }SDEBUG << "Receive addr: " << inet_ntoa(peer_sockaddr_.sin_addr)<< ", port: " << ntohs(peer_sockaddr_.sin_port);return ret; }總結
以上是生活随笔為你收集整理的【Socket网络编程】16.UDP 循环读取recvfrom() 与 循环发送 sendto()的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Socket网络编程】15. 发送端和
- 下一篇: 【Socket网络编程】17. recv