Java 網路編程之TCP通訊和簡單的檔案上傳功能
TCP通訊需要明確的幾點:
- tcp通訊是連線導向的,需要先啟動服務端,再啟動用戶端。
- 用戶端和服務端都要建立通訊端對象,用戶端需要指定服務端通訊端(ip+port),而服務端必須指定服務連接埠。
Socket client_socket = new Socket("192.168.100.17",8888); //用戶端通訊端(Socket類的通訊端為已串連通訊端)ServerSocket listen_socket = new ServerSocket(8888); //服務端通訊端,此時為監聽通訊端(已經bind()地址和連接埠了)
服務端需要使用accept()方法將監聽通訊端轉變為已串連通訊端。這個監聽通訊端可以產生多個已串連通訊端,這樣串連後還能監聽其他用戶端的請求。因此,這裡應該使用多線程實現並發訪問。獲得了已串連通訊端,就可以擷取很多用戶端的資訊,例如用戶端的ip地址,發送請求的連接埠等。
Socket server_scoket = socket.accept();Socket server_scoket2 = socket.accept();Socket server_scoket3 = socket.accept();
服務端要實現並發串連,大致使用如下代碼:其中ThreadTask是線程任務對象。
public static void main(String[] args) throws IOException { ServerSocket listen_sock = new ServerSocket(8888); //監聽通訊端只需建立一個,因此在任務之外 while (true) { //每建立一個串連,就開啟一個線程 Socket conn_sock = listen_sock.accept(); //沒有新串連進來時,main主線程阻塞在此 new Thread(new ThreadTask(conn_sock)).start(); }}
- 用戶端需要根據已串連通訊端擷取輸出資料流,服務端需要根據通訊端擷取輸入資料流。當然,既然有了已串連通訊端,那麼擷取無論哪一端都可以擷取到輸入資料流、輸出資料流。
OutputStream send_stream = client_socket.getOutputStream(); //用戶端擷取輸出資料流InputStream recv_stream = server_socket.getInputStream();
- 服務端應主動關閉已串連通訊端,至於監聽通訊端則在合適的地方關閉。
- 服務端應該迴圈不斷地負責接收。
簡單的Client端:
import java.io.IOException;import java.io.OutputStream;import java.net.Socket;public class TCPClient { public static void main(String[] args) { // 1.建立用戶端通訊端 Socket c_sock = null; OutputStream client_outstream = null; try { c_sock = new Socket("192.168.0.124",8888); // 2.擷取輸出資料流 client_outstream = c_sock.getOutputStream(); // 3.輸出資料 client_outstream.write("Hello,i'm coming".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if(c_sock != null){ try{ c_sock.close(); } catch(IOException e) { e.printStackTrace(); } } } }}
簡單的Server端:
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;public class TCPServer { public static void main(String[] args) { // 1.建立監聽通訊端 ServerSocket listen_sock = null; try { listen_sock = new ServerSocket(8888); } catch(IOException i) { i.printStackTrace(); } Socket server_sock = null; InputStream in_sock = null; while (true) { try { // 2.和用戶端建立串連,產生已串連通訊端,並擷取用戶端ip地址 server_sock = listen_sock.accept(); String client_ip = server_sock.getInetAddress().getHostAddress(); System.out.println("Client: " + client_ip + " connected"); // 3.根據已串連通訊端,擷取輸入資料流,讀取用戶端發送的資料 in_sock = server_sock.getInputStream(); BufferedReader bufr = new BufferedReader(new InputStreamReader(in_sock)); String line = null; while ((line = bufr.readLine()) != null) { System.out.println(line); } // 4.關閉已串連通訊端 server_sock.close(); } catch (IOException e) { e.printStackTrace(); } } }}
以下是tcp實現檔案上傳功能:
- 用戶端除了通訊端的輸出資料流,還有讀取本地檔案的輸入資料流,還有通訊端的輸入資料流來讀取來自服務端的反饋資訊。
- 服務端也同樣有三流:通訊端的輸入、輸出資料流,寫入上傳目標檔案的輸出資料流。
- 用戶端讀取本地檔案的所有資料後,需要使用通訊端的
shutdownOutput()
來通知服務端通訊端的輸出資料流已到末尾。
- 服務端為了能為多人提供上傳功能,需要使用多線程實現並發串連。
Client端:
import java.io.BufferedReader;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.net.Socket;public class UploadClient { public static void main(String[] args) { // TODO Auto-generated method stub String server_addr = "192.168.0.124"; int server_port = 8888; Socket send_sock = null; FileInputStream local_read = null; try { // 1.用戶端通訊端 send_sock = new Socket(server_addr, server_port); // 2.擷取串連管道的輸出資料流 OutputStream send_stream = send_sock.getOutputStream(); // 3.位元組輸入資料流讀取本地檔案資料,並使用通訊端的輸出資料流發送出去 local_read = new FileInputStream("d:/myjava/net/SQL.docx"); byte[] buf = new byte[1024]; int len = 0; while ((len = local_read.read(buf)) != -1) { send_stream.write(buf, 0, len); } // 4.標記輸出資料流到結尾 send_sock.shutdownOutput(); // 5.接收服務端的反饋資料,如上傳成功,上傳失敗等 InputStream recv_stream = send_sock.getInputStream(); BufferedReader ack_recv = new BufferedReader(new InputStreamReader(recv_stream)); String line = null; while ((line = ack_recv.readLine()) != null) { System.out.println(line); } } catch (IOException i) { i.printStackTrace(); } finally { if (send_sock != null) { try { send_sock.close(); local_read.close(); } catch (IOException i1) { i1.printStackTrace(); } } if (local_read != null) { try { local_read.close(); } catch (IOException i2) { i2.printStackTrace(); } } } }}
Server端:
import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;public class UploadServer { public static void main(String[] args) throws IOException { ServerSocket listen_sock = new ServerSocket(8888); //監聽通訊端只需建立一個,因此在任務之外 while (true) { //每建立一個串連,就開啟一個線程 Socket conn_sock = listen_sock.accept(); //沒有新串連進來時,main主線程阻塞在此 new Thread(new Uploader(conn_sock)).start(); } }}class Uploader implements Runnable { private File dest_dir = new File("d:/temp"); // 上傳目錄 private Socket conn_sock = null; // 串連通訊端 InputStream recv_stream = null; FileOutputStream dest_stream = null; Uploader(Socket conn_sock) throws IOException { this.conn_sock = conn_sock; } public void run() { try { if (!dest_dir.exists()) { dest_dir.mkdirs(); } // 1.擷取串連管道的輸入資料流 recv_stream = conn_sock.getInputStream(); // 用戶端ip String client_ip = conn_sock.getInetAddress().getHostAddress(); System.out.println(client_ip + ".....connected"); // 2.檔案的上傳位置,即輸出目標,以ip命名。如果檔案已存在,則使用括弧加數字建立檔案,如"192.168.100.23(1).txt" File dest_file = new File(dest_dir, client_ip + ".docx"); int count = 1; while (dest_file.exists()) { dest_file = new File(dest_dir, client_ip + "(" + count + ")" + ".docx"); count++; } // 3.讀取資料並寫入目標檔案 dest_stream = new FileOutputStream(dest_file); byte[] buf = new byte[1024]; int len = 0; while ((len = recv_stream.read(buf)) != -1) { dest_stream.write(buf, 0, len); } // 4. 向用戶端反饋資訊 OutputStream ack_send = conn_sock.getOutputStream(); byte[] text = "upload successful!".getBytes(); ack_send.write(text); } catch (IOException e1) { e1.printStackTrace(); } finally { if (dest_stream != null) { try { dest_stream.close(); } catch (IOException i) { i.printStackTrace(); } } if (conn_sock != null) { try { conn_sock.close(); } catch (IOException i) { i.printStackTrace(); } } } }}