[英]UDP broadcasting implementation in C++
我有一個python代碼,用於廣播消息,並使用UDP(SOCK_DGRAM)接收廣播的消息。 python源代碼在這篇文章中: https : //stackoverflow.com/a/17055865/260127
我需要將此python代碼轉換為C ++ / C。 我用谷歌搜索手動翻譯功能,以獲取此代碼。
#include <iostream>
#include <memory>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <thread>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
// https://stackoverflow.com/questions/13898207/recvfrom-bad-address-sendto-address-family-not-supported-by-protocol
// http://linux.die.net/man/3/setsockopt
void pinger(string msg)
{
cout << "pinger spawned: " << msg;
int bytes_sent;
char data_sent[256] = "This is a test";
struct sockaddr_in to;
int addrlen;
int s = socket(AF_INET, SOCK_DGRAM, 0);
memset(&to, 0, sizeof(to));
to.sin_family = AF_INET;
to.sin_addr.s_addr = inet_addr("192.168.65.255");
to.sin_port = htons(4499);
int optval = 1;
socklen_t optlen;
getsockopt(s, SOL_SOCKET, SO_BROADCAST, &optval, &optlen);
getsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, &optlen);
if (optval != 0) {
cout << "SO_BROADCAST enabled on s!\n";
}
sleep(0.1);
bytes_sent = sendto(s, data_sent, sizeof(data_sent), 0,
(struct sockaddr*)&to, sizeof(to));
}
int main(int argc, char *argv[])
{
thread pingerThread(pinger, "Message");
pingerThread.join();
// get the message
int bytes_received;
char data_received[256];
struct sockaddr_in from;
memset(&from, 0, sizeof(from));
from.sin_family = AF_INET;
from.sin_addr.s_addr = inet_addr("192.168.65.255");
from.sin_port = htons(4499);
int s = socket(AF_INET, SOCK_DGRAM, 0);
if(s == -1)
perror("socket");
if (bind(s, (struct sockaddr*)&from, sizeof(from)) == -1)
{
perror("Bind error");
}
socklen_t len = sizeof from;
if(recvfrom(s, data_received, 256, 0, (struct sockaddr*)&from, &len)==-1)
perror("recvfrom");
if(close(s) == -1)
perror("close");
}
編譯沒有錯誤,但是當我執行代碼時,它似乎會永遠等待。 我聽不到pinger的提示消息。
此代碼有什么問題?
查看Python代碼找出您要執行的操作,這是您的錯誤:
thread pingerThread(pinger, "Message");
pingerThread.join();
區別非常明顯:Python代碼不會在任何地方調用a.join()
(這意味着線程在主腳本的末尾隱式連接),但是在C ++端口中,您插入了立即pingerThread.join()
由於某種原因。
那么,為什么會有區別呢? 因為它保證了死鎖。 在收到消息之前,ping線程無法完成。 期望從主線程接收該消息。 但是主線程卡在該join
調用中,等待pinger線程完成。
您不能僅通過刪除join
來解決此問題,因為在C ++中,必須在每個std::thread
超出范圍之前將其加入。 (在Python中使連接顯式是一個很好的主意,但是在C ++中,這不僅是個好主意,而且是法律,如果破壞了它,則程序將被終止。)因此,只需將其移至main
功能。
您的代碼中還有其他一些嚴重的問題。
Python time.sleep
需要一個帶有小數秒的浮點數; 您從C ++調用的POSIX sleep
采用一個無符號的int,並且不能用於小數秒的睡眠。 這意味着您的sleep(0.1)
隱式將0.1
強制轉換為0
。
您的編譯器應對此發出警告,如下所示:
pinger.cpp:40:11: warning: implicit conversion from 'double' to 'unsigned int'
changes value from 0.1 to 0 [-Wliteral-conversion]
sleep(0.1);
~~~~~ ^~~
如果您的平台具有POSIX sleep
,則它可能也具有POSIX nanosleep
(除非它很舊,在這種情況下,它至少可能具有BSD usleep
),所以請改用它。
但是,盡管該Python代碼的作者說了什么,但是sleep(0.1)
並沒有真正解決問題3(“有時候,線程產生得如此之快,以至於偵聽器只會錯過廣播數據”)。
線程化的第一個規則是,您無法使用sleep
調用解決競爭條件。 您所能做的就是將錯誤的再現性調整到經常發生的程度,以使您的程序在實踐中無法使用,但又不足以調試為什么其無法使用。
等待100毫秒可以保證主線程達到其recvfrom
並沒有什么神奇的。 線程總是被調度100ms,尤其是在繁忙的系統上。
唯一的解決方案是正確地排序事物。 這是否意味着更改操作順序,使用同步原語,使用套接字本身進行排序,更改邏輯(例如,對該問題的公認答案可以通過重復發送數據來解決該問題)。
Python代碼調用setsockopt
,以允許程序重用地址並打開廣播模式。 但是您的C ++端口調用getsockopt
, getsockopt
讀取兩個選項的值,而不更改任何內容。 因此,例如,如果您連續兩次運行同一程序,則第二次可能無法bind
該地址。
另外,您不會將optlen
的值初始化為任何值。 您必須將其設置為sizeof(optval)
否則可能會遍歷整個堆棧-或僅讀取可選值的前0個字節,而不是全部4個字節,這意味着您根本不會檢查任何內容。
另外,您必須在使用返回的值之前檢查getsockopt
的返回值。 並且沒有充分的理由連續兩次調用getsockopt
並覆蓋第一個optval
而無需檢查它。
同時,Python代碼已經在做錯了:您需要在調用bind
那一側而不是發送給它的那一側設置SO_REUSEADDR
。
同樣,雖然Python的socket.sendto
接收一個字符串並發送與該字符串中一樣多的字節,但是C的sendto
接收一個字符串和一個長度,並發送length
字節,即使該字符串在此之前終止。
因此,您發送的是256個字節而不是14個字節。
另外,您永遠不會關閉發送套接字,僅關閉偵聽套接字。
在您的Python代碼中,這已經是一個問題,但是在C ++代碼中,這是一個更糟糕的問題。 在Python中,如果您忘記close
某些內容,則最終將收集垃圾,這有時就足夠了。 在C ++中,除了被設計為自我管理的類 (包括標准庫中的大多數C ++類,但不包括文件句柄之類的C級事物)之外,您還必須自己進行顯式清理。
對於將要立即退出的玩具程序,這可能並不重要。 但是在現實生活中,代碼確實可以。
根據答案,我修改了代碼以使其正常運行。
這是修改后的代碼:
#include <iostream>
#include <memory>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <thread>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cassert>
using namespace std;
// http://stackoverflow.com/questions/13898207/recvfrom-bad-address-sendto-address-family-not-supported-by-protocol
// http://linux.die.net/man/3/setsockopt
void pinger(string msg)
{
sockaddr_in si_me, si_other;
int s;
assert((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))!=-1);
int port=4499;
int broadcast=1;
setsockopt(s, SOL_SOCKET, SO_BROADCAST,
&broadcast, sizeof broadcast);
memset(&si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(port);
si_me.sin_addr.s_addr = inet_addr("192.168.65.255");
unsigned char buffer[10] = "hello";
int bytes_sent = sendto(s, buffer, sizeof(buffer), 0,
(struct sockaddr*)&si_me, sizeof(si_me));
cout << bytes_sent;
}
int main(int argc, char *argv[])
{
sockaddr_in si_me;
unsigned char buffer[20];
int s;
assert((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))!=-1);
int port=4499;
memset(&si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(port);
si_me.sin_addr.s_addr = inet_addr("192.168.65.255");
if (bind(s, (struct sockaddr*)&si_me, sizeof(si_me)) == -1)
{
perror("Bind error");
}
// Send the message after the bind
pinger("hello");
socklen_t len = sizeof si_me;
if(recvfrom(s, buffer, 20, 0, (struct sockaddr*)&si_me, &len)==-1)
perror("recvfrom");
cout << "\nRECEIVE" << buffer;
if(close(s) == -1)
perror("close");
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.