7.6.3 Socket Communication Based on TCP Protocol (2)
Category Android Basic Tutorial
Introduction to This Section:
>
In the previous section, we introduced some basic concepts of Socket and how to use it, and then we wrote a simple chat room demo for a pig. I believe everyone has a preliminary grasp of Socket. In this section, we will learn how to use Socket to implement the resumption of large file transfers from the breakpoint! What is explained here is an example of someone else's well-written Socket to upload large files, and it is not required for us to be able to write it ourselves. It's just good to use it when needed!
1. Running Effect Picture:
First, run our well-written Socket server:
Put an audio file in the root directory of the SD card:
Run our client:
After the upload is successful, you can see a folder named file generated in the project of our server, where we can find the uploaded file: The .log file is our log file
2. Implementation Flowchart:
3. Code Example:
First, write a stream parsing class that both the server and client will use:
StreamTool.java :
public class StreamTool {
public static void save(File file, byte[] data) throws Exception {
FileOutputStream outStream = new FileOutputStream(file);
outStream.write(data);
outStream.close();
}
public static String readLine(PushbackInputStream in) throws IOException {
char buf[] = new char[128];
int room = buf.length;
int offset = 0;
int c;
loop: while (true) {
switch (c = in.read()) {
case -1:
case '\n':
break loop;
case '\r':
int c2 = in.read();
if ((c2 != '\n') && (c2 != -1)) in.unread(c2);
break loop;
default:
if (--room < 0) {
char[] lineBuffer = buf;
buf = new char[offset + 128];
room = buf.length - offset - 1;
System.arraycopy(lineBuffer, 0, buf, 0, offset);
}
buf[offset++] = (char) c;
break;
}
}
if ((c == -1) && (offset == 0)) return null;
return String.copyValueOf(buf, 0, offset);
}
/**
* Read the stream
* @param inStream
* @return byte array
* @throws Exception
*/
public static byte[] readStream(InputStream inStream) throws Exception{
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while( (len=inStream.read(buffer)) != -1){
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
return outSteam.toByteArray();
}
}
1) Implementation of the server:
Socket management and multi-thread management class:
FileServer.java :
public class FileServer {
private ExecutorService executorService; //Thread pool
private int port; //Listening port
private boolean quit = false; //Exit
private ServerSocket server;
private Map<Long, FileLog> datas = new HashMap<Long, FileLog>(); //Store breakpoint data
public FileServer(int port){
this.port = port;
//Create a thread pool, with (number of CPUs * 50) threads in the pool
executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50);
}
/**
* Exit
*/
public void quit(){
this.quit = true;
try {
server.close();
} catch (IOException e) {
}
}
/**
* Start the service
* @throws Exception
*/
public void start() throws Exception{
server = new ServerSocket(port);
while(!quit){
try {
Socket socket = server.accept();
//To support concurrent access by multiple users, a thread pool is used to manage each user's connection request
executorService.execute(new SocketTask(socket));
} catch (Exception e) {
// e.printStackTrace();
}
}
}
private final class SocketTask implements Runnable{
private Socket socket = null;
public SocketTask(Socket socket) {
this.socket = socket;
}
If the directory does not exist, create it;
file = new File(dir, filename);
If the file already exists (in case of a file name conflict during upload, then rename it)
filename = filename.substring(0, filename.indexOf(".") - 1) + dir.listFiles().length + filename.substring(filename.indexOf("."));
file = new File(dir, filename);
save(id, file);
} else { // If there is an upload record, read the length of the already uploaded data
file = new File(log.getPath()); // Get the file path from the upload record
if (file.exists()) {
File logFile = new File(file.getParentFile(), file.getName() + ".log");
if (logFile.exists()) {
Properties properties = new Properties();
properties.load(new FileInputStream(logFile));
position = Integer.valueOf(properties.getProperty("length")); // Read the length of the already uploaded data
}
}
}
OutputStream outStream = socket.getOutputStream();
String response = "sourceid=" + id + ";position=" + position + "\r\n";
// After the server receives the client's request information, it returns a response to the client: sourceid=1274773833264;position=0
// The sourceid is generated by the server side and uniquely identifies the uploaded file, and position indicates the position in the file where the client starts uploading
outStream.write(response.getBytes());
RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd");
if (position == 0) fileOutStream.setLength(Integer.valueOf(filelength)); // Set file length
fileOutStream.seek(position); // Specify the position in the file to start writing data
byte[] buffer = new byte[1024];
int len = -1;
int length = position;
while ((len = inStream.read(buffer)) != -1) { // Read data from the input stream and write it to the file
fileOutStream.write(buffer, 0, len);
length += len;
Properties properties = new Properties();
properties.put("length", String.valueOf(length));
FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName() + ".log"));
properties.store(logFile, null); // Record the received file length in real time
logFile.close();
}
if (length == fileOutStream.length()) delete(id);
fileOutStream.close();
inStream.close();
outStream.close();
file = null;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (socket != null && !socket.isClosed())
socket.close();
} catch (IOException e) {
}
}
}
public FileLog find(Long sourceid) {
return datas.get(sourceid);
}
// Save upload record
public void save(Long id, File saveFile) {
// Can be changed to store through the database in the future
datas.put(id, new FileLog(id, saveFile.getAbsolutePath()));
}
// When the file upload is complete, delete the record
public void delete(long sourceid) {
if (datas.containsKey(sourceid))
datas.remove(sourceid);
}
private class FileLog {
private Long id;
private String path;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public FileLog(Long id, String path) {
this.id = id;
this.path = path;
}
}
}
Server interface class: **ServerWindow.java**:
```java
public class ServerWindow extends Frame {
private FileServer s = new FileServer(12345);
private Label label;
public ServerWindow(String title) {
super(title);
label = new Label();
add(label, BorderLayout.PAGE_START);
label.setText("Server has started");
this.addWindowListener(new WindowListener() {
public void windowOpened(WindowEvent e) {
new Thread(new Runnable() {
public void run() {
try {
s.start();
} catch (Exception e) {
// e.printStackTrace();
}
}
}).start();
}
public void windowIconified(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowDeactivated(WindowEvent e) {
}
public void windowClosing(WindowEvent e) {
s.quit();
System.exit(0);
}
});
}
}
}
public void windowClosed(WindowEvent e) { }
public void windowActivated(WindowEvent e) { }; });
/**
- @param args */ public static void main(String[] args) throws IOException { InetAddress address = InetAddress.getLocalHost(); ServerWindow window = new ServerWindow("File Upload Server: " + address.getHostAddress()); window.setSize(400, 300); window.setVisible(true);
}
}
2) Client (Android side)
Firstly, the layout file: activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="File Name"
android:textSize="18sp" />
<EditText
android:id="@+id/edit_fname"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Nikki Jamal - Priceless.mp3" />
<Button
android:id="@+id/btn_upload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Upload" />
<Button
android:id="@+id/btn_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop" />
<ProgressBar
android:id="@+id/pgbar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="fill_parent"
android:layout_height="40px" />
<TextView
android:id="@+id/txt_result"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center" />
</LinearLayout>
For resumable uploads, we need to save the upload progress, which requires a database. Here we define a database management class: DBOpenHelper.java :
/**
* Created by Jay on 2015/9/17 0017.
*/
public class DBOpenHelper extends SQLiteOpenHelper {
public DBOpenHelper(Context context) {
super(context, "jay.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS uploadlog (_id integer primary key autoincrement, path varchar(20), sourceid varchar(20))");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Then there is the database operation class: UploadHelper.java :
/**
* Created by Jay on 2015/9/17 0017.
*/
public class UploadHelper {
private DBOpenHelper dbOpenHelper;
public UploadHelper(Context context) {
dbOpenHelper = new DBOpenHelper(context);
}
public String getBindId(File file) {
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select sourceid from uploadlog where path=?", new String[]{file.getAbsolutePath()});
if (cursor.moveToFirst()) {
return cursor.getString(0);
}
return null;
}
public void save(String sourceid, File file) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
db.execSQL("insert into uploadlog(path,sourceid) values(?,?)",
new Object[]{file.getAbsolutePath(), sourceid});
}
public void delete(File file) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
db.execSQL("delete from uploadlog where path=?", new Object[]{file.getAbsolutePath()});
}
}
Also, don't forget to include the stream parsing class on the client side. Finally, here is our MainActivity.java :
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText edit_fname;
private Button btn_upload;
private Button btn_stop;
private ProgressBar pgbar;
private TextView txt_result;
private UploadHelper upHelper;
private boolean flag = true;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
pgbar.setProgress(msg.getData().getInt("length"));
float num = (float) pgbar.getProgress() / (float) pgbar.getMax();
int result = (int) (num * 100);
txt_result.setText(result + "%");
if (pgbar.getProgress() == pgbar.getMax()) {
Toast.makeText(MainActivity.this, "Upload successful", Toast.LENGTH_SHORT).show();
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
case R.id.btn_upload:
String filename = edit_fname.getText().toString();
flag = true;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File file = new File(Environment.getExternalStorageDirectory(), filename);
if (file.exists()) {
pgbar.setMax((int) file.length());
uploadFile(file);
} else {
Toast.makeText(MainActivity.this, "The file does not exist~", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(MainActivity.this, "The SD card does not exist or is not available", Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_stop:
flag = false;
break;
}
}
private void uploadFile(final File file) {
new Thread(new Runnable() {
public void run() {
try {
String sourceid = upHelper.getBindId(file);
Socket socket = new Socket("172.16.2.54", 12345);
OutputStream outStream = socket.getOutputStream();
String head = "Content-Length=" + file.length() + ";filename=" + file.getName()
+ ";sourceid=" + (sourceid != null ? sourceid : "") + "\r\n";
outStream.write(head.getBytes());
PushbackInputStream inStream = new PushbackInputStream(socket.getInputStream());
String response = StreamTool.readLine(inStream);
String[] items = response.split(";");
String responseSourceid = items[0].substring(items[0].indexOf("=") + 1);
String position = items[1].substring(items[1].indexOf("=") + 1);
if (sourceid == null) { //If it is the first time to upload the file, there is no resource id bound to this file in the database
upHelper.save(responseSourceid, file);
}
RandomAccessFile fileOutStream = new RandomAccessFile(file, "r");
fileOutStream.seek(Integer.valueOf(position));
byte[] buffer = new byte[1024];
int len = -1;
int length = Integer.valueOf(position);
while (flag && (len = fileOutStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
length += len; //Accumulate the length of the data that has been uploaded
Message msg = new Message();
msg.getData().putInt("length", length);
handler.sendMessage(msg);
}
if (length == file.length()) upHelper.delete(file);
fileOutStream.close();
outStream.close();
inStream.close();
socket.close();
} catch (Exception e) {
Toast.makeText(MainActivity.this, "Upload exception~", Toast.LENGTH_SHORT).show();
}
}
}).start();
}
}
Finally, remember to write these permissions into AndroidManifest.xml!
<!-- Permission to create and delete files on the SD card -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- Permission to write data to the SD card -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Permission to access the internet -->
<uses-permission android:name="android.permission.INTERNET"/>
4. Code Download:
5. Summary of This Section:
>
This section introduced another example of Socket based on the TCP protocol: using Socket to complete the resumption of large file uploads. I believe everyone's understanding of Socket has further improved. Well, let's write another example in the next section, an example of two mobile phones under the same Wifi transferring data to each other! That's all for now, thank you~
-1.0.1 Latest Android Basic Tutorial Catalog for 2015
-1.1 Background and System Architecture Analysis
-1.2 Development Environment Setup
-1.2.1 Developing Android APP with Eclipse + ADT + SDK
-1.2.2 Developing Android APP with Android Studio
-1.3 Solving SDK Update Issues
-1.4 Genymotion Emulator Installation
-1.5.1 Git Tutorial on Basic Operations of Local Repositories
-1.5.2 Git: Setting Up a Remote Repository with GitHub
-
4.4.2 Further Exploration of ContentProvider - Document Provider
[5.
7.6.3 Socket Communication Based on TCP Protocol (2)
8.3.4 Paint API — Xfermode and PorterDuff Detailed Explanation (I)
8.3.5 Paint API — Xfermode and PorterDuff Detailed Explanation (II)
8.3.6 Paint API — Xfermode and PorterDuff Detailed Explanation (III)
8.3.7 Paint API — Xfermode and PorterDuff Detailed Explanation (IV)
8.3.8 Paint API — Xfermode and PorterDuff Detailed Explanation (V)
8.3.14 Paint Several Enumeration/Constant Values and ShadowLayer Shadow Effects
8.3.17 Detailed Explanation of Canvas API (Part 2) - Collection of Clipping Methods
8.3.18 Detailed Explanation of Canvas API (Part 3) - Matrix and drawBitmapMesh
8.4.3 Android Animation Collection - Property Animation - First Encounter
8.4.4 Android Animation Collection - Property Animation - Re-encounter
-