I'm currently trying to implement udp hole punching on Android for my udp server. Things should work like this:
The client (behind a nat; maybe 3G,..) sends a DatagramPacket to the server (the server has a public ip; port is also known to be 45555). The client repeats to send a Datagram with a given delay
Once the server received a Datagram, it sends Datagrams ("signals") back every 500ms.
If hole punching worked, the client should receive those signals
Here is my current Client implementation (Android):
//in onCreate()
DatagramSocket socket = new DatagramSocket(46222);
socket.setSoTimeout(2000);
final Thread t = new Thread(new Runnable(){
#Override
public void run() {
int delay = Integer.parseInt(e2.getText().toString());//e1 and e2 are EditTexts
String ip = e1.getText().toString();
try {
DatagramPacket packet = new DatagramPacket(new byte[1],1, InetAddress.getByName(ip), 45555);
while(!cleanUp){//cleanUp is set to true in onPause()
lock.lock(); //Lock lock = new ReentrantLock();
socket.send(packet);
lock.unlock();
Thread.sleep(delay);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(socket!=null)
socket.close();
}
}
});
final Thread t2 = new Thread(new Runnable(){
#Override
public void run() {
try {
Thread.sleep(1000);
DatagramPacket packet = new DatagramPacket(new byte[1],1);
while(!cleanUp){
lock.lock();
try{
socket.receive(packet);
}catch(SocketTimeoutException e){
lock.unlock();
Thread.sleep(15);
continue;
}
lock.unlock();
final String s = tv.getText().toString()+"signal\n";
MainActivity.this.runOnUiThread(new Runnable(){
#Override
public void run() {
tv.setText(s);//tv is a TextView
}
});
Thread.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
}
finally{
if(socket!=null)
socket.close();
}
}
});
//start both threads
Here is the server-side implementation (Java):
//int static void main(String[] args):
final Thread t = new Thread(new Runnable(){
#Override
public void run() {
try {
DatagramPacket packet = new DatagramPacket(new byte[1],1, addr, port);
DatagramSocket socket = new DatagramSocket();
System.out.println("send");
while(true){
socket.send(packet);
Thread.sleep(500);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
final Thread t2 = new Thread(new Runnable(){
#Override
public void run() {
try {
DatagramPacket packet = new DatagramPacket(new byte[1],1);
DatagramSocket socket = new DatagramSocket(45555);
socket.receive(packet);
addr = packet.getAddress(); //private static field InetAddress addr
port = packet.getPort();
System.out.println(addr+":"+ packet.getPort()); //field int port
t.start();
while(true){
socket.receive(packet);
System.out.println("idle");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
t2.start();
Everything works when client and server are in the same private network. To imitate the public server I run the server-side code on my computer and set up a port on my router (which has a public ip)*. The clients will send its packets to the public ip of the router. But in both cases ( my smartphone is connected to internet via my wlan network / 3G or E) no signals are received (the server receives the datagrams of the client)
So why does the hole punching process not work?
regards
*: The router will forward any udp packets sent to its port 45555 to my computer
Different sockets binds to different private port and use different public port of your NAT. You are receiving and sending through different sockets. Send with the same socket through which you received the data. Else Your sending socket is using different public port of your router to send data to your clients NAT. This packet your clients NAT discards because it came from same IP but unknown port.
Related
Intro
There are a number of tutorials on creating a multicast publisher and receiver.
I used the one found here with a few modifications.
A few others:
here
here
here
note that these tutorials are all quite similar.
Details:
The server runs on port 7777 and sends datagrams to 224.0.0.0 (I tested a couple of other ip's in the multicast range: 224.0.0.0 to 239.255.255.255, but these didn't work)
Client then joins the multicast group 224.0.0.0 and waits for a packet response (run as a thread)
extra: I send a message like: 123.23.13.12[host-name]:1234 as the datagram data.
Problem:
Multicast packets from server (on localhost) not reaching client (on localhost).
Clients include a java console application (code found below) and Android application on Android Emulator. Both clients do not receive multicast packets.
I know that the multicast packets are being sent as this is shown in Wireshark
Below you will find a basic example of that which I have.
TL;DR: Server sends multicast packets (confirmed via Wireshark) but client doesn't receive them.
Suggestions are very welcome!
UPDATE
Based on Just another Java programmer's comment, I check my firewall. Lo and behold, my firewall was dropping on the input and forward chains. I set this to acceptall incoming (temporarily)
Based on Ron Maupin's comments.
I have changed the message sent to exclude the hostname, thus the message sent is 123.12.13.23:1234
I have changed the multicast send address to 239.254.0.0 which is within the specified range (see Ron's comment)
the multicast port is set to 7777
the outgoing interface is set with s.setInterface(InetAddress.getLocalHost()) in the broadcastServer() try catch block
With these changes applied, the client(s) still do not receive any packets.
Code:
Server Side (Console App):
String multicastAddress = "239.254.0.0", multicastPort = 7777;
private void broadcastServer() {
String message = null;
MulticastSocket s = null;
InetAddress local = null, group = null;
InetAddress[] allByName;
try {
local = InetAddress.getLocalHost();
s = new MulticastSocket(multicastPort);
s.setReuseAddress(true);
s.setInterface(local)
s.joinGroup(InetAddress.getByName(multicastAddress));
group = InetAddress.getByName(multicastAddress);
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
MulticastSocket socket = s;
// getNetworkIP() gets lan network ip
// serverport = 1025
message = local.getHostAddress() + ":" + String.valueOf(serverPort);
byte[] buf = message.getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, group, multicastPort);
thdBroadcaster = new Thread(() -> {
while (bRunServer) {
try {
Thread.sleep(1000);
printf("[Broadcast] Broadcasting...");
socket.send(packet);
printf("OK\n");
printf("");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
socket.close();
});
thdBroadcaster.start();
}
Client Side (Console app):
String multicastAddress = "239.254.0.0", multicastPort = 7777;
private void startServerListenerThread() {
Thread thdServerListener = new Thread(new Runnable() {
#Override
public void run() {
MulticastSocket socket = null;
InetAddress group = null;
try {
socket = new MulticastSocket(multicastPort);
socket.setReuseAddress(true);
group = InetAddress.getByName(multicastAddress);
socket.joinGroup(group);
handleServerBroadcasts(socket);
socket.leaveGroup(group);
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleServerBroadcasts(final MulticastSocket socket) {
while (true){
try {
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String received = new String(packet.getData());
String address = received.substring(0, received.indexOf(":"));
String port = received.substring(received.indexOf(":") + 1);
System.out.println();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception x){
x.printStackTrace();
}
}
}
});
thdServerListener.start();
}
You are calling setReuseAddress() after binding the socket. It has no effect. You need to create an unbound MulticastSocket, call setReuseAddress(), and then bind it.
The solution ended up being a trivial change.
Resolving the issue was as simple as changing:
s = new MulticastSocket(multicastPort);
to
s = new MulticastSocket();
on the server side ONLY
Note: The firewall is a requirement, check if multicast packets are allowed through. The interface I used is localhost, not a requirement though. However this can be set by getting the specified interface you need.
UPDATE
EJP's comment:
I changed the port used to 8888. This port was used to send server datagrams and for the client to connect on with their MulticastSocket
i'am writing a small app to remote control a (Behringer x32) Mixing console. And i got a problem with the communication.
I'am sending data from the pc (app) to the console (port 10023 UDP Protocol), then the console answers to the port from the pc has send data, (random port).
So i have 2 Threads one for sending data, and one for listening for data from the console..... so every time i send data to the console, i need to change the listening port... so i have to kill the listening thread and start it new.
But after some time a have and the app has about x1000 threads open.
How can i restart the Thread or update the listening port without create a new thread?
here's the code for this section, the whole files are # gihub
the listening thread class:
public class Receiver implements Runnable {
private List<IReceiverListener> listeners;
private final static int PACKETSIZE = 48;
private int port;
public Receiver() {
listeners = new ArrayList();
}
public void addReceiverListener(IReceiverListener listener) {
listeners.add(listener);
}
private void update(String data, String adress) {
for (IReceiverListener listener : listeners) {
listener.receiveConsoleData(data, adress);
if (data.indexOf("active") > -1) {
listener.incrementWatchDog();
}
}
}
#Override
public void run() {
try {
// Convert the argument to ensure that is it valid
// Construct the socket
while (true) {
//System.out.println("Listen on Port:" + this.port);
DatagramSocket socket = new DatagramSocket(this.port);
// Create a packet
DatagramPacket packet = new DatagramPacket(new byte[PACKETSIZE], PACKETSIZE);
// Receive a packet (blocking)
socket.receive(packet);
// Print the packet
update(new String(packet.getData()), packet.getAddress().toString());
//logger.addLogData(new String(packet.getData())+" "+packet.getAddress().toString());
// Return the packet to the sender
socket.close();
}
} catch (IOException e) {
}
}
public void setPort(int port) {
this.port = port;
}
public int getPort() {
return port;
}
}
and here my port updateFunction
#Override
public void updatePort(int port) {
receiverThread.interrupt();
receiverThread = null;
receiver.setPort(port);
receiverThread = new Thread(receiver);
receiverThread.start();
}
and the sending thread does this, when it sends data:
listener.updatePort(dsocket.getLocalPort());
This is actually not a threading problem. The problem is, that the receiver thread is stuck in the receive method, so it cannot react to the changed port. However, calling the method DatagramSocket#close from another thread releases the blocking receiver thread with a SocketException.
Thus, you can solve this by closing the currently receiving socket when the receiving port was changed. The receiving thread can now catch the SocketException and create a new DatagramSocket that listens on the new port.
There is no need to kill and recreate threads.
First you put the socket into a field. This allows you to access it from another thread, so you can call the socket.close() method. Second, you put another try-catch block into the while(true) loop, which only catches SocketException.
Something like this might work fine:
public class Receiver implements Runnable {
private static final int PACKETSIZE = 48;
private final ConcurrentLinkedQueue<IReceiverListener> listeners = new ConcurrentLinkedQueue<>();
private volatile DatagramSocket socket;
private volatile int port;
public Receiver(int port) {
this.port = port;
}
public void addReceiverListener(IReceiverListener listener) {
listeners.add(listener);
}
public void updatePort(int port) {
this.port = port;
DatagramSocket socket = this.socket;
if (socket != null) {
socket.close();
}
}
#Override
public void run() {
try {
while (true) {
receiveLoop(new DatagramSocket(port));
}
} catch (IOException e) {
// handle error
}
}
private void receiveLoop(DatagramSocket newSocket) throws IOException {
try (DatagramSocket socket = newSocket) {
this.socket = newSocket;
while (true) {
DatagramPacket packet = new DatagramPacket(new byte[PACKETSIZE], PACKETSIZE);
socket.receive(packet);
process(packet);
}
} catch (SocketException e) {
// port was changed -> return and restart with a new socket
} finally {
this.socket = null;
}
}
private void process(DatagramPacket packet) {
update(new String(packet.getData()), packet.getAddress().toString());
}
private void update(String data, String adress) {
for (IReceiverListener listener : listeners) {
listener.receiveConsoleData(data, adress);
if (data.indexOf("active") > -1) {
listener.incrementWatchDog();
}
}
}
}
Please note, that this might still contains some bugs. It is only supposed to give you a rough idea of how to solve this.
As you are using DatagramSocket, you can change the used port by Binding the socket to a new port rather than the used one:
socket.bind(new InetSocketAddress(new_port));
But remember that bind() method won't work unless the socket is already opened and a port assigned to it, so at the first time you have to create the socket regularly, then when you try to change the port, just bind it.
And the following is a complete visualization of the process:
public void video_udp_server(int port) throws Exception
{
byte[] receiveData = new byte[Integer.MAX_VALUE/100];
for(int i = 0; i < receiveData.length; i++){
receiveData[i] = ' ';
}
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
DatagramSocket socket = null;
try{
socket = new DatagramSocket(port);
}catch(Exception ex){
socket.bind(new InetSocketAddress(port));
}
socket.setReuseAddress(true);
socket.receive(receivePacket);
System.out.println(new String(receivePacket.getData()));
}
I'm writing a chat program in java and I have been stuck for hours with this problem. This is my class that waits for clients to connect to the server.
Every time a new client connects I create a new ChatClient(String name, DatagramSocket serverSocket,InetAddress IPAddress, int port) object.
My idea was that every ChatClient object listens on the socket and when a package is sent from the same IP as the ChatClient, it will handle it, otherwise do nothing.
As it is now, when I only have one client connected; the client gets every 2 packages, then run() in WaitForConnection() gets the rest.
So my question, is it possible to have multiple threads listening on the same DatagramSocket without loss (everyone gets everything send). If there is a solution, how?
private ArrayList<ChatClient> clients;
private DatagramSocket serverSocket;
private boolean running;
public WaitForConnection() {
running = true;
clients = new ArrayList<ChatClient>();
try {
serverSocket = new DatagramSocket(ChatServer.port);
} catch (SocketException e) {
System.out
.println("Couldn't open socket. Port might alreadybe in use");
e.printStackTrace();
}
try {
serverSocket.setReuseAddress(true);
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public void run() {
while (running) {
for (ChatClient ch : clients) {
System.out.println(ch.toString());
}
byte[] handShake = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(handShake,
handShake.length);
try {
serverSocket.receive(receivePacket);
} catch (IOException e) {
System.out.println("Waiting for connections error");
e.printStackTrace();
}
String connect = new String(receivePacket.getData());
System.out.println(connect);
InetAddress IPAddress = receivePacket.getAddress();
// if connect == "OPEN_CONNECTION" -> new client want to connect.
if (connect.contains("openconnection")) {
int port = receivePacket.getPort();
try {
ChatClient chatClient = new ChatClient(
IPAddress.getHostName(), serverSocket, IPAddress,
port);
// Don't want double clients.
for (int i = 0; i < clients.size(); i++) {
if (clients.get(i).equals(chatClient)) {
clients.remove(i);
}
}
clients.add(chatClient);
} catch (IOException e) {
System.out.println("Couldn't connect to client");
e.printStackTrace();
}
}
}
}
}
Code for ChatClient if you need to look at it.
public class ChatClient extends Thread {
private InetAddress IPAddress;
private DatagramSocket serverSocket;
private int port;
private String name;
public ChatClient(String name, DatagramSocket serverSocket,
InetAddress IPAddress, int port) throws IOException {
super(name);
this.name = name;
this.IPAddress = IPAddress;
this.serverSocket = serverSocket;
this.port = port;
byte[] confirmConnection = new byte[1024];
String connected = "Connection to server established";
confirmConnection = connected.getBytes();
serverSocket.send(new DatagramPacket(confirmConnection,
confirmConnection.length, IPAddress, port));
start();
}
public void run() {
while (true) {
byte[] message = new byte[1024];
DatagramPacket receivedPacket = new DatagramPacket(message,
message.length);
try {
serverSocket.receive(receivedPacket);
} catch (IOException e) {
System.out
.println("Something went wrong receiving data in ChatClient");
}
if (receivedPacket.getAddress().equals(IPAddress)) {
String connect = new String(receivedPacket.getData());
connect = connect.toUpperCase();
System.out.println(connect + "client side");
message = connect.getBytes();
try {
serverSocket.send(new DatagramPacket(message,
message.length, IPAddress, port));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
It is possible for multiple threads to receive from the same DatagramSocket, but only one of them will get each datagram.
I don't see why you think you need this.
It is technically not possible, because the network hardware receives the packet only once. But then you can always duplicate it in memory once it is read. In your code basically just do Arrays.copyOf(receivePacket)
For a more sophisticated version, you could use the NIO package and work with a Selector. This would allow you to have all network connections run through a single thread, that reads and distributes the data to processing threads. That saves you the extra threading-overhead if you have multiple connections from many clients.
I want to broadcast the string over LAN, but when I change the server IP in client code to 255.255.255.255 it doesn't broadcast. What shall I do in order to broadcast the string over LAN? What shall I do in client code so that all the listening ports at different IP's can receive the string at same time.
My client or code for sending the string is:
public class MainActivity extends Activity {
private Socket socket;
private static final int SERVERPORT = 6000;
private static final String SERVER_IP = "192.168.1.10";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new ClientThread()).start();
}
public void onClick(View view) {
try {
EditText et = (EditText) findViewById(R.id.EditText01);
String str = et.getText().toString();
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
out.println(str);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
class ClientThread implements Runnable {
#Override
public void run() {
try {
InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
socket = new Socket(serverAddr, SERVERPORT);
} catch (UnknownHostException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
My server or code for receiving string is:
public class MainActivity extends Activity {
private ServerSocket serverSocket;
Handler updateConversationHandler;
Thread serverThread = null;
private TextView text;
public static final int SERVERPORT = 6000;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text2);
updateConversationHandler = new Handler();
this.serverThread = new Thread(new ServerThread());
this.serverThread.start();
}
#Override
protected void onStop() {
super.onStop();
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
class ServerThread implements Runnable {
public void run() {
Socket socket = null;
try {
serverSocket = new ServerSocket(SERVERPORT);
} catch (IOException e) {
e.printStackTrace();
}
while (!Thread.currentThread().isInterrupted()) {
try {
socket = serverSocket.accept();
CommunicationThread commThread = new CommunicationThread(socket);
new Thread(commThread).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class CommunicationThread implements Runnable {
private Socket clientSocket;
private BufferedReader input;
public CommunicationThread(Socket clientSocket) {
this.clientSocket = clientSocket;
try {
this.input = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
String read = input.readLine();
updateConversationHandler.post(new updateUIThread(read));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class updateUIThread implements Runnable {
private String msg;
public updateUIThread(String str) {
this.msg = str;
}
#Override
public void run() {
text.setText(text.getText().toString()+"Client Says: "+ msg + "\n");
}
}
}
Socket is a TCP socket. Broadcasting is not possible with TCP. If you want to use TCP, you cannot broadcast, you must have a connection open to each client and send the data over each connection separately.
DatagramSocket is a UDP socket. Broadcasting is possible with UDP. However, the caveat is that UDP does not guarantee that your message will actually arrive. To guarantee that your message arrives you must implement some kind of acknowledgment/retry protocol, but if you do that, you might as well use TCP, since that's what it does.
Edit: Another question and my responses from the comments below. OP wrote:
then how i'll get the IP's of listening devices in LAN in order to make connection separately?
The subject here is device or service discovery, a not uncommon challenge. There are many options. Here are some, in no particular order:
Specify the server IP address in the client device's configurations and have them connect to you.
Specify a list of the client IP addresses in the server device's configuration and have it connect to all of them.
Implement some kind of UDP discovery protocol where you broadcast a discovery request over UDP and devices respond with information about their IP address, etc. Same caveat as above.
Have your server broadcast UDP messages announcing its presence and its IP address, have your clients listen for these and establish TCP connections to server. Same caveat as above.
Check out an existing service discovery protocol, e.g. jmdns.sourceforge.net (compatible with Bonjour/zeroconf). This is actually quite a common issue and many protocols exist to solve it.
Have your server scan all IPs in its subnet and attempt to establish a TCP connection to each. Very time consuming, but may be appropriate.
Options 1-2 are the simplest to implement but require manual configuration by the user.
Options 3-5 have a common theme: Avoid manual configuration requirements by using UDP and its broadcast capabilities to automatically exchange configuration information. Use that information to establish TCP connections, and then use TCP for reliable data transfer. Keep in mind that UDP broadcasts are limited in scope to the subnet, so you could not use broadcast-based discovery to discover machines on other LANs -- for that you'd have to do some kind of central service registry with TCP based registration and a well-known registration server.
Option 6 avoids manual configuration at the expense of extremely poor discovery performance and potentially high usage of system resources. Options 3-5 seek to optimize the discovery process.
I am trying to send packets from one host to another peer host using java UDP protocol.
The one host sends data, while the other reads it. The corresponding read however keeps blocking, thus receiving no data. I can see the sender packets going to the right destination using wireshark, but the receiver just wont pick it up. The read operation keeps blocking indefinitely.
Please help.
Code for cient:
//CLIENT CLASS
//Sections ommited....
DatagramSocket so = new DatagramSocket(port);
protected void send(byte[] buffer,int packetsize){
DatagramPacket p;
try {
myClient.notifyInform("Sending data to "+inetaddress+" on"+port+"\n");
p=new DatagramPacket(buffer,buffer.length,InetAddress.getByName(inetaddress),port);
writeLock.lock();
try{
so.send(p);
}finally{
writeLock.unlock();
}
} catch (UnknownHostException e) {
myClient.perror("Could not connect to peer:"+e.getMessage()+"\n");
e.printStackTrace();
} catch (IOException e) {
myClient.perror("IOException while sending to peer.\n");
e.printStackTrace();
}
}
protected DatagramPacket read(){
byte[] buf=new byte[bufsize];
DatagramPacket p=new DatagramPacket(buf,buf.length);//TODO check these values, what should buffer be? just made it psize*10 for now
readLock.lock();
try{
myClient.notifyInform("receiving data\n");
so.receive(p);
this.myclient.notifyInform(String.valueOf(p.getData().length)+"\n");
} catch (IOException e) {
myClient.perror("IOException while reading from peer.\n");
e.printStackTrace();
}finally{
readLock.unlock();
}
return p;
}
protected void beginRead() {
while(active) {
System.out.println("########################");
byte[] data=this.read().getData();
myClient.notifyInform("Receiving data\n");
}
}
protected void beginSend(){
forkWork(new Runnable(){
#Override
public void run() {
byte[] sendBuffer=new byte[bufsize];
int cnt;
while(callActive){
try{
sourceLock.lock();
cnt=dataSource.read(sendBuffer, 0, bufsize);
}finally{
sourceLock.unlock();
}
if (cnt >0) {
send(sendBuffer, packetsize);
}
}
}
});
}
UPDATE:I made a mistake that I finally tracked down. After binding the port, and fixing that error, it now works.
You need to specify the port that the datagram socket is listening on like this:
this.so = new DatagramSocket(SOME_NUMBER_HERE);
and make sure you send it to the same port number in the send() method
Is your receiving DatagramSocket listening at the IP:port the sender is sending to?