Reputation: 364
What I'm trying to do is simply send an mp3 file over http/tcp with my own http headers. On the client side I have a webpage with the following line:
<audio src="http://192.168.0.21:14441" controls autoplay loop>
The Java server side has a ServerSocket
and accepts basically any connection. It will then send a hardcoded http header followed by the binary data of the mp3-file.
My server class:
public class Server {
private int port = 14441;
private String localIPAddress;
private BufferedReader in;
private BufferedOutputStream out;
private ServerSocket serverSocket;
private Socket clientSocket;
public Server() {
}
public void start() {
new Thread(new Runnable() {
@Override
public void run() {
startAcceptingConnections();
}
}).start();
}
private void startAcceptingConnections() {
tryToOpenPort();//try to open external port with upnp
clientSocket = null;
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
System.err.println("Could not listen on port:" + port);
System.exit(1);
}
System.out.println("Waiting for connection.....");
try {
clientSocket = serverSocket.accept();
System.out.println("Connection successful");
System.out.println("Waiting for input.....");
out = new BufferedOutputStream(clientSocket.getOutputStream());
in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
String inputLine;
boolean isSendingMp3 = false;
while ((inputLine = in.readLine()) != null) {
//just output any line the client sends me
System.out.println("Received: " + inputLine);
//Whenever the client sends an empty line this means
//it's ready to receive
if (inputLine.equals("") && !isSendingMp3) {
isSendingMp3 = true;
//Making sure to keep listening to the InputStream
//so I send from a different thread
new Thread(new Runnable() {
@Override
public void run() {
sendMp3File();
}
}).start();
}
}//end of listening to client loop
out.close();
in.close();
clientSocket.close();
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendMp3File() {
try {
//I've tried all sorts of headers
String response = "HTTP/1.x 200 OK\r\n"
+ "Content-Type: audio/mpeg\r\n"
+ "Content-Size: 2911084\r\n"
+ "Range: bytes 0-2911083/2911084\r\n"
+ "X-Content-Duration: 300.1\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Duration: 300.1\r\n"
+ "\r\n";
out.write(response.getBytes(Charset.forName("UTF-8")));
byte[] bytesRaw = new byte[1024 * 10];
InputStream is = new FileInputStream("C:/sample.mp3");
int byteCount = 0;
while ((byteCount = is.read(bytesRaw)) != -1) {
System.out.println("sending bytes:" + byteCount);
out.write(bytesRaw, 0, byteCount);
}
out.flush();
is.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
//Use Cling to open the port via upnp
private void tryToOpenPort() {
try {
localIPAddress = Inet4Address.getLocalHost().getHostAddress();
PortMapping desiredMapping
= new PortMapping(
port,
localIPAddress,
PortMapping.Protocol.TCP,
"Test server"
);
UpnpService upnpService = new UpnpServiceImpl(new PortMappingListener(desiredMapping));
upnpService.getControlPoint().search();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
This always works on a PC browser (Firefox, Chrome, IE) and gives no problems machine to machine (no firewall interference).
However, as soon as I run the webpage from a mobile device (both iOS and Android) the connection is suddenly closed after sending what seems to be a random amount of data. This is somewhere between 0 and 2 seconds after the connection has been established.
The Java application throws the exception: java.net.SocketException: Connection reset by peer: socket write error
When I profile with Wireshark it shows me everything goes well and then suddenly the client starts sending a bunch of RST
messages. I've tried multiple types of headers, even copied a number of headers from existing webservers, but nothing seems to work.
Even simple headers like
"HTTP/1.1 200 OK\r\n"
+ "Content-Type: audio/mpeg \r\n"
+ "\r\n";
Work when I open them from a computer browser, but reset the connection on mobile. Am I forgetting something important?
UPDATE
On mobile browsers it closes the connection after a bit of data has been send. It connects and disconnects again at what seems like random intervals, sometimes with a range in the header and sometimes not. Even when it has received the entire file it will continue to open connections.
I'm guessing some sort of high protocol 'protection' when sending big requests, maybe specifically for when on unstable mobile networks.
Is there some way to bypass this? Whatever it is, it seems a bit unduly.
What happens is that the html5 audio element asks for the first 2 bytes with a range header. When I then (according to rfc2616 validly) ignore this range and send the whole file, the audio player starts behaving as if it's an audio stream (or at-least becomes very confused). This still only happens on mobile browsers somehow.
The solution might be to start accepting range request so that the player doesn't get "confused". I'll post the results as soon as I get the time to try this.
Upvotes: 0
Views: 292
Reputation: 123521
I think the problem is in the code you have not shown. My guess is that you accept the connection and then simple send the response without reading the request. But if the connections gets closed with the request not read this will cause a connection reset. How this reset affects the client depends on the timing, i.e. it might be that the client processed the response before it got the reset or that it found the reset before it had time to process the response.
To fix it you need to read the HTTP request before you sent the response.
Upvotes: 2