網路程式設計之TCP
(1)建立連線,形成傳輸的資料通道 (2)在連線中進行大資料量傳輸 (3)通過三次握手完成連線,是可靠的協議 (4)必須連線,效率稍低 |
2、TCP分客戶端和服務端
(1)客戶端對應的物件:Socket (2)服務端對應的物件:ServerSocket |
2.1、客戶端
通過查閱Socket物件,發現在該物件建立時,就可以去連線指定主機,因為TCP時面向連線的,所以在建立Socket服務時,就要有服務端存在,並連線成功,形成通路後,在該通道進行資料傳輸。
思路:
(1)建立Socket服務,並制定要連線的主機和埠號 一旦Socket物件建立,就形成Socket流,包含輸入流和輸出流, InputStream getInputStream():返回次套接字的輸入流 OutputStream getOutputStream():返回套接字的輸出流 (2)為了傳送資料,應獲取Socket流中俄輸出流 (3)關閉資源 |
例:
package com.heima.net; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; publicclass TCPDemo1 { publicstaticvoid main(String[] args)throws //建立Socket服務 Socket s=new Socket(InetAddress.getByName("localhost"),10001); //傳送資料,應獲取Socket流中輸出流 OutputStream out=s.getOutputStream(); //寫資料 out.write("hello heima".getBytes()); //關閉資源 s.close(); } } |
2.2、服務端ServerSocket
思路
(1)建立服務端Socket服務,ServerSocket,並監聽一個埠 (2)獲取連線過來的客戶端物件,通過 (3)客戶端如果發過來資料,那麼服務端要使用對應的客戶端物件,並獲取到該客戶端物件的讀取流來讀取發過來的資料 (4)關閉流 |
程式碼如下:
package com.heima.net; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; publicclass TCPServer { publicstaticvoid main(String[] args)throws Exception { //建立ServerSocket服務 ServerSocket ss=new ServerSocket(10001); //通過accept方法獲取連線過來的客戶端物件 Socket s=ss.accept(); //獲取ip String ip=s.getInetAddress().getHostAddress(); //獲取客戶端發過來的資料 InputStream in=s.getInputStream(); //讀取資料到控制檯 byte[]buf=newbyte[1024]; int len=0; while((len=in.read(buf))!=-1){ System.out.println(new String(buf,0,len)); } //關閉資源 s.close(); } } |
應用一:建立一個文字轉換伺服器
客戶端給伺服器傳送文字,服務端會將文字轉換成大寫在返回該客戶端,而且客戶端能夠不斷的進行文字轉換。
package com.heima.net; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; publicclass TCPDemo1 { publicstaticvoid main(String[] args)throws Exception { } } //客戶端 class Send{ publicstaticvoid main(String[] args)throws Exception { //建立一個Socket服務 Socket s=new Socket(InetAddress.getByName("localhost"),10009); //建立一個鍵盤輸入流 BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in)); //建立一個Socket輸出流 BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //建立一個Socket輸入流 BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream())); //建立一個字串 String line=null; while((line=bufr.readLine())!=null){ //把從鍵盤讀取的資料寫到Socket輸出流中 bufOut.write(line); //換行 bufOut.newLine(); //重新整理 bufOut.flush(); //同時需要接受服務端反饋回來的資訊 String str=bufIn.readLine(); //打印出來 System.out.println("server說:"+str); } //關閉資源 bufr.close(); s.close(); } } //服務端 class Receive{ publicstaticvoid main(String[] args)throws Exception { //建立一個ServerSocket服務 ServerSocket ss=new ServerSocket(10009); //獲取客戶端的Socket服務 Socket s=ss.accept(); //建立一個Socket接受流 BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream())); //建立一個Socket輸出流 BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //定義一個字串 String line=null; while((line=bufIn.readLine())!=null){ //把讀取的資料轉換成大些寫出去 bufOut.write(line.toUpperCase()); //換行 bufOut.newLine(); //重新整理 bufOut.flush(); } //關閉資源 s.close(); } } |
注:如果不進行換行和重新整理,客戶端會停留等待,因為readLine(),只有回車符時才結束,二newLine表示一行的結束。
應用二:複製檔案
package com.heima.net; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; publicclass TCPDemo2 { } //客戶端 class Send1{ publicstaticvoid main(String[] args)throws Exception { //建立Socket服務 Socket s=new Socket(InetAddress.getByName("localhost"),10007); //建立輸入流 BufferedReader bufr=new BufferedReader( new FileReader(new File("d:"+File.separator+"clint.txt"))); //建立Socket輸出流 PrintWriter out=new PrintWriter(s.getOutputStream(),true); //建立一個字串 String line=null; while((line=bufr.readLine())!=null){ //把從檔案中讀取的資料寫到Socket輸出流中 out.println(line); } //關閉客戶端的輸出流,相當於給流種加入一個結束標記 s.shutdownOutput(); //讀取服務端反饋回來的資料 BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream())); //讀取服務端反饋回來的資料 String str=in.readLine(); System.out.println("伺服器說:"+str); //關閉資源 bufr.close(); s.close(); } } //服務端 class Receive1{ publicstaticvoid main(String[] args)throws Exception { //建立ServerSocket服務 ServerSocket ss=new ServerSocket(10007); //建立一個客戶端的Socket服務 Socket s=ss.accept(); //讀取Socket中資料 BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream())); //把讀取的資料寫到檔案中 PrintWriter pw=new PrintWriter(new FileWriter(new File("d:"+File.separator+"server.txt")),true); //定義一個字串 String line=null; while((line=bufIn.readLine())!=null){ //把資料讀取到指定的檔案中 pw.println(line); } //向客戶端反饋一些資訊 PrintWriter out=new PrintWriter(s.getOutputStream(),true); out.println("上傳成功"); //關閉資源 s.close(); pw.close(); } } |
應用三:TCP客戶端併發上傳圖片
問題:怎樣讓多個客戶端同時併發訪問服務端?
最好就是將每個客戶端封裝到一個單獨的執行緒中,這樣就可以同時處理多個客戶端請求。
如何定義執行緒呢?
只要明確了每個客戶端要在服務端執行的程式碼即可,將該程式碼存入run方法中。
定義的執行緒部分
//定義一個執行緒 class PicThread implements Runnable{ //此執行緒需要接受一個客戶端 private Socket s; public PicThread(Socket s){ this.s=s; } publicvoid run() { //獲取IP String ip=s.getInetAddress().getHostAddress(); FileOutputStream fis=null; try{ System.out.println(ip+"連線成功"); //從Socket服務中讀取客戶端傳過來的資料,因為時圖片,所有用位元組流 InputStream in=s.getInputStream(); //建立一個檔案物件 File file=new File(System.currentTimeMillis()+".jpg"); //把讀取到得圖片資料寫到指定的地方 fis=new FileOutputStream(file); //定義一個位元組陣列 byte[]buf=newbyte[1024]; //定義一個整型 int len=0; while((len=in.read(buf))!=-1){ //把讀取的資料寫到指定的檔案中 fis.write(buf,0,len); } //當上傳成功後給客戶端反饋一些資訊 OutputStream out=s.getOutputStream(); //寫一些資料 out.write("上傳成功".getBytes()); }catch(Exception e){ thrownew RuntimeException(ip+"上傳失敗"); }finally{ try{ if(fis!=null) fis.close(); if(s!=null) s.close(); }catch(Exception e){ e.printStackTrace(); } } } } |
客戶端:
//定義一個客戶端 class Client{ publicstaticvoid main(String[] args)throws Exception { //接收一個引數 if(args.length!=1){ System.out.println("請選擇一個jpg格式的圖片"); return; } //建立一個檔案 File file=new File(args[0]); //判斷是此檔案是否存在且為檔案 if(!(file.exists()&&file.isFile())){ System.out.println("此檔案不存在或者不是一個檔案"); return; } //判斷是否為jpg格式的 if(!file.getName().endsWith("jpg")){ System.out.println("只能上傳格式為jpg的檔案"); return; } //判斷檔案的大小 if(file.length()>1024*1024*3){ System.out.println("檔案超出了最大的上傳大小"); return; } //如果上面的步驟都通過了,可以上傳檔案了,首先建立Socket服務 Socket s=new Socket(InetAddress.getByName("localhost"),10009); //建立一個讀取流 FileInputStream fis=new FileInputStream(file); //建立一個Socket輸出流,把讀取到得資料輸出到服務端 OutputStream out=s.getOutputStream(); //定義一個位元組陣列 byte[]buf=newbyte[1024]; int len=0; while((len=fis.read(buf))!=-1){ //把讀取到的資料寫入到流中 out.write(buf, 0, len); } //資料寫完後,顯示一個標記 s.shutdownOutput(); //接受服務端反饋的資訊 InputStream in=s.getInputStream(); //建立一個位元組陣列,把接受的資料先存放到陣列中 byte[]bufIn=newbyte[1024]; int num=in.read(bufIn); System.out.println(new String(bufIn,0,num)); //關閉資源 fis.close(); s.close(); } } |
服務端:
//定義一個服務端 class Server{ publicstaticvoid main(String[] args)throws Exception { //建立一個ServerSocket服務 ServerSocket ss=new ServerSocket(10009); //不斷的接受客戶端 while(true){ //接受Socket客戶端 Socket s=ss.accept(); //建立一個執行緒 new Thread(new PicThread(s)).start(); } } } |
應用四:客戶端的併發登入
因為同時可能有很多人登入,所以用多執行緒
//定義一個執行緒 class UserThread implements Runnable{ //接收一個Socket服務 private Socket s=null; public UserThread(Socket s){ this.s=s; } @Override publicvoid run() { //獲取ip地址 String ip=s.getInetAddress().getHostAddress(); try { for(int i=0;i<3;i++){ //讀取從客戶端讀取的資料 BufferedReader bufrIn=new BufferedReader( new InputStreamReader(s.getInputStream())); //建立一個字串用於儲存讀取的資料 String name=bufrIn.readLine(); //從檔案中獲取資料 BufferedReader bufr=new BufferedReader(new FileReader( new File("d:"+File.separator+"user.txt"))); //定義一個字串 String line=null; //建立一個輸出流,向客戶端反饋資訊 PrintWriter out=new PrintWriter(s.getOutputStream(),true); //建立一個標記 boolean flag=false; while((line=bufr.readLine())!=null){ //判斷從檔案中讀取的資料是否包含客戶端傳過來的 if(line.equals(name)){//包含則把標記改為true flag=true; break; } } if(flag){ System.out.println(name+"登陸成功"); //向客戶端反饋資訊 out.println(name+"歡迎登入"); break; }else{ System.out.println(name+"嘗試登入"); out.println(name+"登入失敗"); } } s.close(); } catch (Exception e) { e.printStackTrace(); thrownew RuntimeException(ip+"校驗失敗"); } } } |
客戶端:登入三次,當三次失敗是退出程式
//定義一個客戶端 class LoginClient{ publicstaticvoid main(String[] args)throws Exception { //建立一個Socket服務 Socket s=new Socket(InetAddress.getByName("localhost"),10008); //從鍵盤輸入資料 BufferedReader bufr=new BufferedReader( new InputStreamReader(System.in)); //把從鍵盤接收的資料輸出到服務端 PrintWriter out=new PrintWriter(s.getOutputStream(),true); //接收服務端反饋回來的資訊 BufferedReader in=new BufferedReader( new InputStreamReader(s.getInputStream())); //定義一個字串 for(int i=0;i<3;i++){ String line=bufr.readLine(); if(line==null) break; out.println(line); String str=in.readLine(); //列印接收的服務端反饋 System.out.println(str); if(str.contains("歡迎")) break; } //關閉資源 bufr.close(); s.close(); } } |
服務端:
//定義一個服務端 class LoginServer{ publicstaticvoid main(String[] args)throws Exception { //建立ServerSocket服務 ServerSocket ss=new ServerSocket(10008); //迴圈接收客戶端 while(true){ //接收一個客戶端Socket服務 Socket s=ss.accept(); //開啟一個執行緒 new Thread(new UserThread(s)).start(); } } |