AddThis Social Bookmark Button

Print

C# I/O and Networking
Pages: 1, 2

Filesystem I/O

Performing disk operations in Java is pretty simple -- it mostly involves manipulating the java.io.File object and using either a java.io.FileInputStream or a java.io.FileOutputStream. As we have seen many times before, C# is like Java but slightly different.



Like Java, C# File objects do not have a concrete tie to the underlying filesystem; it is possible to create File objects for non-existent files, and it is also possible create a File for an existing file and move that file out from underneath the CLR without the C# program noticing until it attempts to open the file. Unlike Java, the File object can play a much more pivotal role as it has static methods such as CreateText or AppendText that will return a stream to the filesystem. In Java, the constructor for the FileInputStream must be used to get the same functionality.

To create a new file for writing to in Java, you just have to use the FileInputStream to

FileOutputStream fos new FileOutputStream( "brand-new-file.txt" =);
fos.write( ... )

but C# allows either a

Stream s File.Create( "brand-new-file.txt" );

or a

StreamWriter sw File.CreateText( "brand-new-file.txt" );

to get a Stream or a StreamWriter to the new file. (Appending in Java is done by setting the "append" boolean in one of the FileOutputStream's constructors.) Java allows for reading from files using the java.io.FileInputStream, while C# has static methods named Open Write and OpenText. Lastly, C# offers more fine-grained control in its Open method -- this method exposes the ability to set the file permissions and access contexts.

Table 3: Manipulating files for reading and writing
The methods to use to either read or write from files in both Java and C#

Function

Java

C#

Create a new file for writing

Use the java.io.FileOutputStream

Either use the static File.Create method, static CreateText, or the instance method CreateText

Write to an existing file

Use the java.io.FileOutputStream

Use either the static or instance OpenWrite method

Append text to a file

Use the java.io.FileOutputStream, but use the constructor that takes the append parameter

Use either the static or instance AppendText method

Read text from a file

Use the java.io.FileInputStream

Make use of the static or instance OpenRead or OpenText methods

Another improvement that C# has made that is worth mentioning for curiosity's sake is the inclusion of a File.Copy method. A problem that most Java programmers who have worked with filesystem I/O notice is the inability to properly move files. java.io.File contains a renameTo method that can rename a file; however, that does not work over filesystem boundaries (disks, networks, etc.). Most of the time, programmers are forced to implement their own move command, which copies the file using both a java.io.FileInputStream and a java.io.FileOutputStream, then deletes the original file. C#'s inclusion of a Copy method makes moving files trivial, though the File.Move command also does not work across volumes and filesystem boundaries.

The C# file-system implementation does not have to deal with the cross-platform issues that the Java model has to cope with. There are no equivalent variables to the java.io.File.pathSeparator or the java.io.File.separator. Unfortunately, this also means that the favorite of the java.io.File constructors

public File( File parent, String child )

does not exist -- instead, C# programmers are left with constructing a new System.IO.File object with

File parent  ...
File child  new File( parent.FullName + "\\" + childName );

Understanding Networking

Both programming languages provide a few layers of abstraction around a base level socket implementation -- granted, Java's java.net.Socket class is far more abstract than C#'s System.Net.Sockets.Socket class.

Table 4: Network architecture tiers in Java and C#
Both Java and C# have different abstraction layers for networking that allow for leveraging different aspects of the interface

Tier

Java

C#

Response/Request

java.net.URL and java.net.URLConnection

System.Net.WebRequest

Protocol

java.net.Socket and java.net.ServerSocket for TCP/IP; java.net.DatagramSocket and java.net.MulticastSocket for UDP

System.Net.Sockets.TCPListener and System.Net.Sockets.TCPClient for TCP/IP; System.Net.Sockets.UDPClient

Raw Socket

N/A

System.Net.Sockets.Socket

The Response/Request tier can be used for HTTP type requests, where one end initiates a connection, sends bytes down the stream, and then blocks while it waits for a set of bytes as a response. For more fluid stream-like operations, the protocol tier can be very useful (we will cover TCP/IP operations below). Most Java programmers, unless highly optimizing network operations, do not require fine socket control -- C# still does provide the ability to control raw Berkeley sockets if it is needed.

Response/Request Tier

This tier heavily abstracts away all the networking and provides a stream-like interface to move data back and forth. Java will take a HTTP URL and perform a GET simply by doing a

URL url  new URL( "http://to.post.to.com" );
URLConnection urlConnection  url.openConnection();
InputStream input  urlConnection.getInputStream();
... read stuff from input ...
input.close();

C# mimics this code with its System.Net.WebRequest object:

WebRequest request WebRequestFactory.Create( = "http://to.post.to.com" );
Stream input request.GetResponse().GetResponseStream();
... read stuff from input ...
input.Close();

Both of these implementations will hide the underlying socket creation and HTTP protocol requirements and will simply provide streams that the programmer can use to ship and then receive data. Just like the C# Stream class, the WebRequest class has methods to asynchronously get a request stream to write to or a WebResponse object to read from.

Protocol Tier

The System.Net.Sockets.TCPClient class should seem very familiar to those Java programmers familiar with java.net.Socket, as they are nearly identical; both share a very similar API and share similar functionality, as the programmer does not have to deal with the socket implementation but instead only the return streams to be used.

A simplistic telnet client implementation can be concocted in Java by simply using:

Socket telnet  new Socket( "telnet.host.com", 23 );
OutputStream output  telnet.getOutputStream();
InputStream input  telnet.getInputStream();

and both streams can be used in conjunction to telnet to telnet.host.com. The same program can be written in C# in almost the same fashion:

TCPClient telnet  new TCPClient( "telnet.host.com", 23 );
Stream telnetStream  telnet.GetStream();
StreamWriter output  new StreamWriter( telnetStream );
StreamReader input  new StreamReader( telnetStream );

Also, receiving a TCP/IP connection is nearly identical in both languages, as an incoming socket in Java is set up and then received using:

ServerSocket server  new ServerSocket( 23 );
Socket accept  server.accept();

while C# allows for:

TCPListener server  new TCPListener( 23 );
server.Start();
Socket accept  server.Accept();

In both languages, each socket that is accepted needs to be dealt with separately. In Java, the preferred way (until Java 1.4) is to spawn a thread for each individual socket that is received. The same can be done for the C# sockets; however, the Socket class provides the ability to use an event-driven interface with the "select" method. (Programming sockets in an event-driven model is outside the scope of this article.)

Raw Socket Tier

Here, we probably venture into unfamiliar territory for most Java programmers. Java-only programmers rarely need to know anything about the Berkeley socket implementation, as it is being abstracted away by the java.net.Socket and java.net.DatagramSocket classes. By manipulating this Berkeley socket class properly, the familiar Java functionality of streams can be achieved.

Now we have a C# repertoire that includes the most powerful abstractions from Java -- the ability to perform I/O and networking. Check back for the next article in this series, which will cover multi-threading to allow for parallel operations.

Raffi Krikorian makes a career of hacking everything and anything. Professionally, he is the founding partner at Synthesis Studios: a technological design and consulting firm that orchestrates his disjointed train of thought.


Return to ONJava.com.