小小的java socket聊天程式

來源:互聯網
上載者:User
      本程式是基於java語言的Socket聊天程式,採用TCP傳輸協議,實現兩個人之間的資訊互動。在形成最終結果之前,我經曆了兩個過程程式,兩個過程均為半成品,他們反映了我整個課程設計中的思考過程,在一個較為系統思考過程後,socket思想一層一層加固,讓我映像很深刻,收穫很多。在此文檔中,我將示範我兩個過程程式的思考過程,然後對原始碼進行講解,但最終上傳的代碼為製成品。      在兩個過程程式中,TestServer1和TestClient1為第一個過程,TestServer2和TestClient2為第二個過程,MySingleThreadServer1和MySingleThreadClient1為最終程式。其中,TestServer2和TestClient2實現了多線程,一個線程負責接收,另一個線程負責發送,MySingleThreadServer1和MySingleThreadClient1實現了基於GUI的聊天。控制項並非像MFC那樣很容易就可以通過拖動組件實現,java是通過程式的編寫來實現,我採用awt組件編碼實現GUI,介面很簡單,但花了不少時間來布局。  核心代碼講解:      三個過程程式都牽涉了java socket編程最核心的思想,以下為核心的講解。1.伺服器端      Java Socket伺服器端需要引入兩個包,java.io包和java.net包,io包是解決輸入輸出資料流的問題,而net包包含了socket編程所需的API.      伺服器端首先要得到ServerSocket的對象,即ServerSocket ss = new ServerSocket(5555); 5555為伺服器端的連接埠號碼。Socket s = ss.accept();伺服器端Socket對象通過accept()方法開始監聽連結過來的用戶端資訊。如果有用戶端有資訊過來,則對象s調用輸入輸出資料流的方法,如s.getInputStream(),同時把得到的InputStream 封裝在DataInputStream當中,在用戶端與伺服器端通訊時,有可能兩端存在於不同的作業系統,封裝在DataInputStream可以很好的解決這個問題。 2.用戶端      其實用戶端代碼與伺服器端大多類似,有一點顯著不同,在用戶端沒有ServerSocket類,即用戶端不用監聽任何連結,他只需要發送連結即可。Socket s = new Socket(String IPAddr,int port),IPAddr為伺服器端的IP地址,port為伺服器端的連接埠號碼即5555,由於本程式伺服器端和用戶端在同一主機上,所以伺服器端IP地址為127.0.0.1。通過IPAddr和port兩個參數就可以得到Socket對象s,接下來的步驟就和伺服器程式類似了。                                                            對三個過程程式的詳解 Procedure1:    伺服器端核心代碼如下:               ServerSocket ss = new ServerSocket(5555);               Socket s = ss.accept();                                    OutputStream os = s.getOutputStream();               DataOutputStream dos = new DataOutputStream(os);                              InputStream is = s.getInputStream();               DataInputStream dis = new DataInputStream(is);                              InputStreamReader isr = new InputStreamReader(System.in); //重鍵盤讀入資料               BufferedReader br = new BufferedReader(isr);  //把從鍵盤讀入的資料放入緩衝                              String info;               while(true){                      info = dis.readUTF();                                          System.out.println("用戶端說:" + info);                      if(info.equals("goodbye")){                             break;                      }                      info = br.readLine();                      dos.writeUTF(info);                      System.out.println("伺服器說:" + info);                      if(info.equals("goodbye")){                             break;                      }              }                用戶端核心代碼如下:                     Socket s = new Socket("127.0.0.1",5555);                                          InputStream is = s.getInputStream();                     DataInputStream dis = new DataInputStream(is);                                          OutputStream os = s.getOutputStream();                     DataOutputStream dos = new DataOutputStream(os);                                          InputStreamReader isr = new InputStreamReader(System.in);                      BufferedReader br = new BufferedReader(isr);                                          String info;                     while(true){                            info = br.readLine();                            System.out.println("用戶端說的是:" + info);                            dos.writeUTF(info);                            if(info.equals("goodbye")){                                   break;                            }                            info = dis.readUTF(); //阻塞函數                            System.out.println("伺服器端說的是:" + info);                            if(info.equals("goodbye")){                                   break;                            }                                                 }       伺服器端和用戶端在while(true)迴圈處各不相同,伺服器端是dis.readUTF(),必須首先讀取用戶端傳過來的資訊,才能通過info = br.readLine();dos.writeUTF(info);從鍵盤中讀取資訊再發送給用戶端。相反,用戶端必須首先通過br.readLine();讀取鍵盤資訊,才能接收伺服器發送來的資訊。       思考1:這個簡易的聊天程式已經實現了伺服器和用戶端的資訊互動,但此時已經出現了一個必然出現的問題,比如拿伺服器端來講,當伺服器通過dos.writeUTF(info)發送訊息給用戶端後,在while迴圈體內,他又要執行info = dis.readUTF()代碼,而readUTF()是一個阻塞函數,如果用戶端沒有發送過來,他就阻塞在那個地方,此時下面部分的代碼dos.writeUTF(info)就不能執行,即伺服器端不能發送訊息出去。      要怎樣解決這個問題呢?怎樣readUTF()阻塞的同時又可以writeUTF(info)發送出訊息呢?顯然,一條路徑走不通時應該考慮走另一條路,於是,多線程在這裡引入了。Procedure2就是這樣出來的。  Procedure2:      Procedure2相比procedure1加入了多線程的部分,一個線程負責專門去接受訊息,另一個負責發送訊息。當伺服器端負責接收的線程因為readUTF()被阻塞不能發送訊息時,負責發送訊息的線程讓伺服器端也能發送訊息。同樣的原理,用戶端也如此。 伺服器端加入的關鍵代碼如下:class ServerReadThread extends Thread{                     private DataInputStream dis;       public ServerReadThread(DataInputStream dis){                     this.dis = dis;       }       public void run(){              String info;              try{                    while(true){                       info = dis.readUTF();                       System.out.println("用戶端說:" + info);                       if(info.equals("goodbye")){                             System.out.println("用戶端拜拜了!");                             System.exit(0);                    }              }              }catch(IOException e){                     e.printStackTrace();              }       }} class ServerWriteThread extends Thread{              private DataOutputStream dos;       private BufferedReader br;       public ServerWriteThread(DataOutputStream dos,BufferedReader br){              this.dos = dos;              this.br = br;       }       public void run(){              String info;              try{                    while(true){                       info = br.readLine();                       dos.writeUTF(info);                       if(info.equals("bye")){                              System.exit(0);                    }                }              }catch(IOException e){                     e.printStackTrace();              }       }}       負責接受的類ServerReadThread繼承Thread,並構造一個DataInputStream對象參數的建構函式,接收對方資訊。負責發送的類ServerWriteThread繼承Thread,並構造一個DataInputStream對象和BufferedRead對象的雙參數的建構函式,負責發送訊息。new ServerReadThread(dis).start();new ServerWriteThread(dos,br).start();開啟兩個線程。      用戶端原理與伺服器端類似,就不做更詳細的講解。       思考2:procedure2相比procedure1已經實現了多線程聊天,雖只是運行在控制台上,但麻雀雖小,五髒俱全,這已經體現出了java Socket編程以及多線程的核心思想,此課程設計的目的已經達到。為實現更加人性化的效果,我引入javaGUI的組件awt,與之相關的技術也就隨之引入了,比如基於事件的驅動,還有awt各個組件之間的調用。與此同時,代碼的分布和組織圖都要做相應的調整。  Procedure3      由於是基於事件驅動的組件,所以MySingleThreadServer1類要繼承ActionListener介面,實現該介面唯一的方法actionPerformed(ActionEvent e),即當觸發某一事件時,執行該方法內的代碼。關鍵代碼如下:public class MySingleThreadServer1 implements ActionListener{ private Frame f;private TextArea ta1 = newTextArea("",5,40,TextArea.SCROLLBARS_VERTICAL_ONLY);        private TextArea ta2 = new TextArea("",16,52,TextArea.SCROLLBARS_VERTICAL_ONLY);  private Button b;private String msg ="";OutputStream os;DataOutputStream dos;InputStream is;DataInputStream dis;ServerSocket ss;Socket s;           public MySingleThreadServer1(){           f = new Frame("server:小馬");           b = new Button("伺服器發送");           f.setBackground(Color.WHITE);           b.setBackground(Color.LIGHT_GRAY);           ta1.setBackground(Color.LIGHT_GRAY);           ta2.setBackground(Color.LIGHT_GRAY);           ta2.setEditable(false);               //set to only be read           f.setLayout(new FlowLayout(FlowLayout.LEFT));           f.add(ta1);              f.add(b);              f.add(ta2);                            f.setLocation(200,200);              f.setSize(400,400);              f.setResizable(false);              f.setVisible(true);              b.addActionListener(this);                      f.addWindowListener(new WindowAdapter(){                     public void windowClosing(WindowEvent e){                            System.exit(0);                        }              });              try{                  ss = new ServerSocket(7777);                  s = ss.accept();                                    is = s.getInputStream();                  dis = new DataInputStream(is);                                    os = s.getOutputStream();                  dos = new DataOutputStream(os);                                    serverReadSome();     //接受用戶端發來的資訊                             }catch(IOException e){                  e.printStackTrace();           }           }        public void actionPerformed(ActionEvent e){  //伺服器點擊按鈕觸發時間                      try{               msg = ta1.getText();               dos.writeUTF(msg);               ta2.append("小馬:"+msg+"/n");               ta1.setText("");               ta1.requestFocus();           }catch(IOException ioe){                  ioe.printStackTrace();           }    }        public void  serverReadSome(){           try{             while(true){               msg = dis.readUTF();                  ta2.append("小徐:"+msg+"/n");             }           }catch(IOException ioe){                  ioe.printStackTrace();           }    }        public static void main(String args[]){           new MySingleThreadServer1();    }}       我把GUI的初始化資訊和事件驅動的資訊放到了MySingleThreadServer1的建構函式中,使之new一個的時候就初始化該類。            思考3:由於DataInputStream,DataOutputStream, InputStream, OutputStream, ServerSocket,Socket的對象是全域變數,所以我沒有把對這些類對象的操作即輸入輸出資料流放到main()函數中,應為在main()函數中必須要求是靜態變數或方法,於是,我乾脆一道把對輸入輸出資料流的操作放到了建構函式裡面。同時,在actionPerformed(ActionEvent e)方法體內實現了相應的邏輯處理。      經過千辛萬苦的代碼調試,終於實現了兩者之間的資訊互動。由於是單線程,我本以為procedure3也會出現procedure1那樣被readUTF()阻塞的狀況,結果出乎意料,基於awt的小小聊天程式並沒有出現預想中的糟糕現象,即一方發送後必須等待對方回應才能再次發送的現象,procedure3直接出現了procedure2中靠多線程才能實現的功能。奇怪,難道是老天開眼,知道我努力就放我一馬。再把代碼仔細分析,知道為什麼了。      原因是這樣:actionPerformed()方法負責發送訊息,當一點擊按鈕時就觸發actionPerformed()發送訊息,只要一方編輯訊息並點擊按鈕,就會發送訊息到對方,不會受readUTF()阻塞函數的影響。所以,嚴格意義上講,Procedure3隻是個單線程程式,他實現了多線程才能實現的功能。    總結:只有經過親自動手實踐實現預想的功能,才能讓自己成長更快。結果雖小,但享受了逐步思考的過程。如有轉載,請說明出處!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.