I'm making a client server application in Java. In short, the Server has some files. The Client can send a file to the Server and the Client can request to download all the files from the Server. I'm using RMI for the Server and Client to communicate and I'm using the RMI IO library to send files between Client and Server.
Some example code:
Server:
class Server implements ServerService {
// private Map<String, File> files;
private ConcurrentHashMap<String, File> files // Solution
// adding a file to the server
public synchronized void addFile(RemoteInputStream inFile, String filename)
throws RemoteException, IOException {
// From RMI IO library
InputStream istream = RemoteInputStreamClient.wrap(inFile);
File f = new File(dir, filename);
FileOutputStream ostream = new FileOutputStream(f);
while (istream.available() > 0) {
ostream.write(istream.read());
}
istream.close();
ostream.close();
files.put(filename, f);
}
// requesting all files
public requestFiles(ClientService stub)
throws RemoteException, IOException {
for(File f: files.values()) {
//Open a stream to this file and give it to the Client
RemoteInputStreamServer istream = null;
istream = new SimpleRemoteInputStream(new BufferedInputStream(
new FileInputStream(f)));
stub.receiveFile(istream.export());
}
}
Please note that this is just some example code to demonstrate.
My questions concerns concurrent access to the files on the Server. As you can see, I've made the addFile method synchronized because it modifies the resources on my Server. My requestFiles method is not synchronized.
I am wondering if this can cause some trouble. When Client A is adding a File and Client B is at the same time requesting all files, or vice versa, will this cause trouble? Or will the addFile method wait (or make the other method wait) because it is synchronized?
Thanks in advance!
Yes this could cause trouble. Other threads could access requestFiles(), whilst a single thread is performing the addFile() method.
It is not possible for two invocations of synchronized methods
on the same object to interleave. When one thread is executing a
synchronized method for an object, all other threads that invoke
synchronized methods for the same object block (suspend execution)
until the first thread is done with the object.
[Source] http://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
So methods that are declared syncronised lock the instance to all syncronised methods in that instance (In your case the instance of Server). If you had the requestFiles() method syncronised as well, you would essentially be syncronising access to the Server instance completely. So you wouldn't have this problem.
You could also use syncronised blocks on the files map. See this stackoverflow question:
Java synchronized block vs. Collections.synchronizedMap
That being said, a model that essentially locks the entire Server object whenever a file is being written or read is hampering a concurrent design.
Depending on the rest of your design and assuming each file you write with the 'addFile()' method has a different name, and you are not overwriting files. I would explore something like the following:
Remove the map completely, and have each method interact with the file system separately.
I would use a temporary (.tmp) extension for files being written by 'addFile()', and then (once the file has been written) perform an atomic file rename to convert the extension to a '.txt' file.
Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);
Then restrict the entire 'requestFiles()' method to just '.txt' files. This way file writes and file reads could happen in parallel.
Obviously use whatever extensions you require.
Related
In Java code, I have a region of file mapped using MappedByteBuffer and I need to send this to the client (write to Outputstream). I need to make sure that while sending/writing to socket, it does not create any copy due to memory constraints. How can I achieve this? Will Bytebuffer.array() serve this purpose?
Sharing the code. Note: FileChannel is read-only and I need to send ByteBuffer data as it is.
private void writeData(Socket clientSocket, MappedByteBuffer byteBuffer){
Path path = Paths.get(myfile);
MappedByteBuffer memoryMappedBuffer = null;
try (FileChannel fChannel = FileChannel.open(path, StandardOpenOption.READ)) {
memoryMappedBuffer = fChannel.map(FileChannel.MapMode.READ_ONLY, location, size);
}catch(){
//How can i write memoryMappedBuffer to socket outputStream without copying data...? like
clientSocket.getOutputStream().write(memoryMappedBuffer .array());
}
If you are creating the socket yourself, you can use SocketChannel.open() instead, and use write(ByteBuffer). It manages the socket internally.
InetSocketAddress address = ...
SocketChannel channel = SocketChannel.open(address);
channel.write(memoryMappedBuffer);
// ...
channel.close(); // Closes the connection
If you have a pre-existing socket, you can create a Channel from the socket's output stream. However this allocates a (reused) temporary buffer.
WritableByteChannel channel = Channels.newChannel(clientSocket.getOutputStream());
channel.write(memoryMappedBuffer);
// ...
channel.close();
Based on your description you want to write to a socket the content of a file, or a slice of the file, slice that it is mapped to the virtual address space of your java process.
The memory-mapped files is used in general to share memory between multiple processes or to minimize the I/O operations on disk when your process is writing and reading data to/from the same files (a concrete example is Kafka which uses this procedure).
In your case, when you write the data to a socket, that socket has a buffer (regardless if it's blocking or non-blocking). When the buffer is full you will not be able to write, until the receiver acknowledge the data, and the buffer is cleared. Now, if the receiver is slow, you will remain with that portion of the file loaded to your main memory for a long time which can affect the performance of your server (I suppose you will not have a single consumer/client for your server).
One good and efficient solution is to use pipe streaming which sends data from your server to a consumer (in this case a socket) in a producer/consumer way. By default the PipeInputStream uses a buffer of 1024 bytes (you can increase it), meaning that only 1024 bytes will be kept in the memory at one moment in time for a specific execution thread. If your process has 500 clients, then you will consume only 500*1024 bytes = 512Kb. If one reader is slow, then that producer will be slow also without putting pressure on your process memory.
If all you have to do is to write the content of various files to sockets, I don't know how using memory-mapped files can helps you.
As part of my web service, I have a picture repository which retrieves an image from Amazon S3 (a datastore) then returns it. This is how the method that does this looks:
File getPicture(String path) throws IOException {
File file = File.createTempFile(path, ".png");
S3Object object = s3Client.getObject(new GetObjectRequest(bucketName, path));
IOUtils.copy(object.getObjectContent(), new FileOutputStream(file));
return file;
}
The problem is that it takes way too long to get a response from the service - (a 3MB image took 7.5 seconds to download). I notice that if I comment out the IOUtils.copy() line, the response time is significantly faster so it must be that particular method that's causing this delay.
I've seen this method used in almost all modern examples of converting an S3Object to a file but I seem to be a unique case. Am I missing a trick here?
Appreciate any help!
From the AWS documentation:
public S3Object getObject(GetObjectRequest getObjectRequest)
the returned Amazon S3 object contains a direct stream of data from the HTTP connection. The underlying HTTP connection cannot be reused until the user finishes reading the data and closes the stream.
public S3ObjectInputStream getObjectContent()
Note: The method is a simple getter and does not actually create a stream. If you retrieve an S3Object, you should close this input stream as soon as possible, because the object contents aren't buffered in memory and stream directly from Amazon S3.
If you remove the IOUtils.copy line, then method exits quickly because you don't actually process the stream. If the file is large it will take time to download. You can't do much about that unless you can get a better connection to the AWS services.
I have a Client-Server architecture. The server can have * clients so for each of them two threads (input & output) are created.
I have one master class that coordinates all actions on server side. it has (among others) such a method:
public static synchronized void sendMessageToUser(Message message, String username){
clientOutputThreadPool.submit(new ObjectStreamOutputCallable(userObjectOutputStreams.get(username), message));
}
the objectOutputStreamCallable gets the objectOutputStream for the specific user passed (i keep them in a hashmap to reuse them). The callable is executed in a threadpool
the callable looks like this:
#Override
public Object call() throws Exception {
writeObjectToStream();
return null;
}
private synchronized void writeObjectToStream() throws IOException {
oos.reset();
oos.writeObject(message);
oos.flush();
}
Now I !Sometimes! get the above mentioned errors (on the client side). The fact that this only happens about 30-40% of the time, gets me to believe, that it has something to do with concurrency. Could it be e.g. that the message object that is being serialized is at the same time manipulated somewhere else in the code and that then creates the error? I have read many times that one may not use more than one objectOutputStream or objectInputStream . But I cannot find any place in my code where I use different objectOutputStreams for the same client. Each one has one oos that is created at socket creation time and then kept in a hashmap for later use. also i reset the socket before each message but that still has no effect...
Im currently building an web app, using Java Servlets in Tomcat 7.
This webapp uses Jupload as a client side applet to provide a more comfortable way of uploading multiple files to server.
However, currently this applet sends the files one per post request. My implemented Servlet reads the data from input stream and stores it local. Thats fine and this works.
But additional i have to store filename and paths and such things in DB. Thats why I wanted to store such informations in an object and keep them in a list, and collecting this infos during the incoming requests from the applet.
The list is currently realized as class variable.
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private ArrayList<ImageUploadInformation> uploadInfos; //I know, thats bad.
public UploadServlet() {
super();
uploadInfos = new ArrayList<>();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// not relevant stuff...
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//accessing data stream directly
//jUpload sends file information and file binary data in one stream, so we have to deal with mixed data in streams
InputStream inputStream = request.getInputStream();
DataInputStream dataInputStream = new DataInputStream(inputStream);
//some other stuff, not relevant
byte[] b = IOUtils.toByteArray(inputStream);
File file = null;
if (finalFilename != null) {
file = new File(finalFilename);
}
if (file != null) {
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write(b);
bos.close();
fos.close();
}
else
throw new IOException("File Creation on Server failed!");
//adding Meta informations about file in list
uploadInfos.add(new ImageUploadInformation(filename, relativeDir,"1"));
}
}
But i read on some threads here, that it is really a bad thing to do, in case of threadsafety. Im not very experinced in writing web applications, so maybe the following approach is completely wrong.
I tried to bind the list as session attribute of the request.
request.getSession().setAttribute("uploadInfos", uploadInfos);
However, I cannot use this, because it is a entirely new post request which comes from the applet, and that why I dont have access to this list, in another request.
I read something about binding objects in ServletContext, but I think this is also a bad practice, but i couldnt find any proof for that. How can I achieve, that I can store this list over multiple independent requests.
Would it be better, if all files will be sent to servlet in only one post request, where i can create the list inside of the doPost() Method ?
Think this is configurable within Jupload, but actually the files could be very large.
Is it common practice to send large amount of files in one request ?
Thanks for any help and links to additional literature on that kind of stuff.
#edit: additional stuff
tried also this..
if (request.getSession().getAttribute("uploadInfos") != null) {
uploadInfos = (ArrayList<ImageUploadInformation>)request.getSession().getAttribute("uploadInfos");
uploadInfos.add(new ImageUploadInformation(filename, relativeDir,"1"));
System.out.println("UploadInfos found in Session, current size: " +uploadInfos.size());
request.getSession().setAttribute("uploadInfos", uploadInfos);
}
else {
System.out.println("No UploadInfos found, creating new List...");
uploadInfos = new ArrayList<>();
uploadInfos.add(new ImageUploadInformation(filename, relativeDir,"1"));
request.getSession().setAttribute("uploadInfos", uploadInfos);
}
Here's the output of the test:
Incoming post request
No UploadInfos found, creating new List...
Incoming post request
No UploadInfos found, creating new List...
Incoming post request
No UploadInfos found, creating new List...
You're almost there. The session is where to store state, on the server, that you want to keep across requests.
Servlets may be called from multiple threads and from different clients; the container may also create or destroy instances as it pleases, so the servlets should not hold state themselves. The field "uploadInfos" needs to be removed. That list should be a thread-safe collection, e.g. CopyOnWriteArrayList, and stored in the session instead. First get the attribute from the session, if it's null, create a new list and store it in the session. Then add your entry to the list as before.
It's also worth mentioning that storing state between requests on the server is sometimes undesirable as it can make systems harder to scale out. An alternative would be to store the state on the client, using JavaScript. In your case, though, I wouldn't bother with that, just store it in a session. It's easier.
When creating a standalone server in Java (not using a container like tomcat/jetty), what are the various techniques to keep the service running and not ending?
I have seen where people use a ServerSocket (since you will be communicating with the service presumably), and they use ServerSocket.accept() which blocks until it receives a message. And this is usually done in a while loop:
while(...) {
serverSocket.accept();
}
(http://docs.oracle.com/javase/6/docs/api/java/net/ServerSocket.html#accept())
Is this the only way? If not, what other ways are there and any pros/cons with it?
Are there any libraries that help with building your own service, or its pretty much roll your own.
There are various libraries that help you roll your own Windows/Unix service in Java:
Apache Commons Daemon
Akuma
Java Service Wrapper
How you keep the application running depends on the actual needs of your application. If your application is a server, you would normally want to listen for incoming connections which usually involves some sort of blocking/polling. How you do that again depends on the type of server you want to build. Of the generic solutions there's the ServerSocket class and its method accept() that you already mentioned. Another possibility is to use java.nio and implement a reactor which provides a single-threaded server that can handle multiple connections at once (see http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf for details), but may be a bit hard to grasp and debug.
What you probably want is a multi-threaded server.
Each time the server accepts a connection, the server creates a thread to handle sending/reciving to that client. If you do not use threads in your server, it will only be able to handle one connection at a time.
So, as you meantioned, the server loops infinitly and listens for incomming connections:
while(true){
serverSocket.accept();
ClientHandler c = new ClientHandler(serverSocket);
A instance of the class ClientHandler will be created each time a connection is accepted. This class implements Runnable, and loops for incomming messages using getInputStream and getOutputStream on that socket:
public class ClientHandler implements Runnable{
DataInputStream in;
DataOutputStream out;
//ClientHandler constructor
public ClientHandler(Socket s) throws IOException{
in= new DataInputStream(socket.getInputStream());
out=new DataOutputStream(socket.getOutputStream());
thread.start();
}
The run method:
public void run() {
while(true){
String temp="";
while ((temp = (String) in.readUTF()) != null){ // Read from the input stream each iteration. When temp is not null a message is recived
System.out.println(temp);
Please that the above code does not take into account different exceptions that might occur and is very basic. But it should give you a basic idea on how a server using Sockets can be implemented.
For a quick solution (in a testing environment only!) you can go for something often dubbed as "Enterprise Loop" (because it is too often found in production systems):
while (true)
try {
// do something
} catch (Throwable t) {
// maybe log
}
However, this is not good style in the production code.
(see [1] for a parody of that idiom)
To create a service, you want one of the libraries from this answer.
If you "just need multithreading", have a look into the Java concurrency framework. I stronly suggest reading Java Concurrency in Practice, as multi-threading is much more that just starting another thread and errors are hard to debug.
[1] http://blog.antiblau.de/2016/01/26/java-enterprise-loop/