Java網路編程以及簡單的聊天程式
網路編程技術是互連網技術中的主流編程技術之一,懂的一些基本的操作是非常必要的。這章主要講解網路編程,UDP和Socket編程,以及使用Socket做一個簡單的聊天軟體。
全部代碼下載:連結
1.網路編程簡要概述:
網路編程實質實質就是兩個(或多個)裝置(例如電腦)之間的資料轉送。而要實現兩台電腦通過互連網串連進行資料轉送,必輸要滿足電腦網路的5層協議(物理層,資料連結層,網路層,運輸層,應用程式層);當然有劃分可能不同,但現在大家比較容易接受理解的是五層模型。而其中前三層物理層,資料連結層以及網路層,作為java程式員暫時是不能直接對他進行控制的。而本章講的網路編程是處在運輸層和應用程式層中。對與電腦網路,可以參看我的部落格互連網協議入門
1. 運輸層主要有兩種協議TCP和UDP,TCP通過握手協議進行可靠的串連,UDP則是不可靠串連。
2. 應用程式層主要就是應用程式層直接和應用程式介面並提供常見的網路應用服務,主要協議有HTTP,FTP,DNS等。
3. IP地址:用與標記一台電腦的身份證,此去需要注意的是網路位址轉譯技術NAT的存在,我們的個人電腦基本上都是專用網路IP(192.168..),這個IP只能本地電腦訪問。只有當我們有公網IP後,才能使自己的電腦被世界所有串連在互連網中的電腦訪問到。
4. 服務連接埠:電腦通過不同的服務連接埠來區分不同網路服務,就像通過進程號區分不同的進程一樣;常見服務的連接埠,HTTP(80),FTP(21).
5. URL:統一資源定位器。只能定位互連網資源。
URL基本格式:protocol://hostname:port/resourcename#anchorprotocol:使用的協議,可以是http,ftp,news,telnet等hostname:主機名稱port:連接埠號碼,可選resourcename:資源名,主機上能訪問到的目錄或檔案anchor:標記,可選,指定檔案中的有特定標記的位置如:http://localhost:8080/HttpSer/index.html。
下面主要通過UDP和socket編程來講解網路編程;
2. UDP編程介紹:簡介:
UDP(User Datagram Protocol),中文意思是使用者資料包通訊協定,方式類似於發簡訊息,是一種物美價廉的通訊方式,使用該種方式無需建立專用的虛擬串連,由於無需建立專用的串連,所以對於伺服器的壓力要比TCP小很多。是一種儘可能可靠傳輸的協議。主要用於視頻電話等對資訊準確傳輸性不高追求速度的應用程式。 主要類的講解:
DatagramSocket:
DatagramSocket類實現“網路連接”,包括用戶端網路連接和伺服器端網路連接。雖然UDP方式的網路通訊不需要建立專用的網路連接,但是畢竟還是需要發送和接收資料,DatagramSocket實現的就是發送資料時的發射器,以及接收資料時的監聽器的角色
DatagramPacket:
DatagramPacket類實現對於網路中傳輸的資料封裝,也就是說,該類的對象代表網路中交換的資料。在UDP方式的網路編程中,無論是需要發送的資料還是需要接收的資料,都必須被處理成DatagramPacket類型的對象,該對象中包含發送到的地址、發送到的連接埠號碼以及發送的內容等。 一個簡單的udp通訊:
用戶端:
DatagramSocket ds = null; //定義一個UDP來發送資料 ds = new DatagramSocket(); //假設發送的資料是個字串 String hello = hello world; //定義一個UDP的資料發送包來發送資料,inetSocketAddress表示要接收的地址 DatagramPacket dp = new DatagramPacket(hello.getBytes() ,hello.getBytes().length,new InetSocketAddress(127.0.0.1, 9999)); for(int i=0;i<10;i++) { //資料發送 ds.send(dp); //線程睡眠1s Thread.sleep(1000); }
服務端:
DatagramSocket ds = null; //UDP接受端串連 ds = new DatagramSocket(9999); //定義將UDP的資料包接收到什麼地方 byte[] buf = new byte[1024]; //定義UDP的資料接收包 DatagramPacket dp = new DatagramPacket(buf, buf.length); while(true) { //接收資料包 ds.receive(dp); //將資料轉換輸出: String str = new String(dp.getData(),0,dp.getLength()); System.out.println(str); }
3.socket編程介紹:socket簡介:
Socket是TCP/IP協議的一個十分流行的編程介面,一個Socket由一個IP地址和一個連接埠號碼唯一確定。
而TCP是Tranfer Control Protocol的 簡稱,是一種連線導向的保證可靠傳輸的協議。通過TCP協議傳輸,得到的是一個順序的無差錯的資料流。發送方和接收方的成對的兩個socket之間必須建立串連,以便在TCP協議的基礎上進行通訊,當一個socket(通常都是server socket)等待建立串連時,另一個socket可以要求進行串連,一旦這兩個socket串連起來,它們就可以進行雙向資料轉送,雙方都可以進行發送 或接收操作。 主要的類介紹:
1.建立伺服器類:ServerSocket
可以用伺服器需要使用的連接埠號碼作為參數來建立伺服器對象:
ServerSocket serverSocket = new ServerSocket(8888)
這條語句建立了一個伺服器對象,這個伺服器使用8888號連接埠。當一個用戶端程式建立一個Socket串連,所串連的連接埠號碼為8888時,伺服器對象server便響應這個串連,並且server.accept()方法會建立一個Socket對象。伺服器端便可以利用這個Socket對象與客戶進行通訊。
//進行監聽,當用戶端沒資料發送過來時,停在這裡; Socket s=serverSocket.accept();
2.通訊類:Socket
伺服器端和用戶端可以通過Socket類進行通訊:
用戶端建立socket類過程:
//建立socket用戶端:第一個參數:ip地址;第二個參數:發送的連接埠,假如沒有伺服器,會停在這裡,然後扔出異常; Socket socket=new Socket(192.168.1.105, 8888);
通過socket類可以獲得輸入輸出資料流進行通訊:
//獲得socket的輸出資料流PrintWriter out=new PrintWriter(socket.getOutputStream()); //獲得socket的輸入資料流BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
一個簡單的Socket通訊:
用戶端:
public static void main(String[] args) { Socket socket=null; PrintWriter out=null; BufferedReader in=null; try { //建立socket用戶端:第一個參數:ip地址;第二個參數:發送的連接埠,假如沒有伺服器,會停在這裡,然後扔出異常; socket=new Socket(192.168.1.105, 8888); //獲得本機分配的當地socket連接埠 System.out.println(當地連接埠:+socket.getLocalPort()); //獲得本機分配的當地socket連接埠 System.out.println(遠程連接埠:+socket.getPort()); //獲得socketAddress System.out.println(遠程adress:+socket.getRemoteSocketAddress()); System.out.println(本地adress:+socket.getLocalSocketAddress()); //獲得inetAddress System.out.println(遠程inetAddress:+socket.getInetAddress()); System.out.println(本地inetAddress:+socket.getLocalAddress()); //獲得socket的輸出資料流 out=new PrintWriter(socket.getOutputStream()); //獲得socket的輸入資料流 in=new BufferedReader(new InputStreamReader(socket.getInputStream())); //發送資料 out.println(peace); //重新整理才會立即發送 out.flush(); //接收資料 String str=null; //此去會一直等待服務端發送一個資料; str=in.readLine(); System.out.println(收到:+str); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { //關閉串連 if(socket!=null){ try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
服務端:
public static void main(String[] args) { ServerSocket serverSocket=null; PrintWriter out=null; BufferedReader in=null; Socket s=null; int i=0; try { //伺服器的建立:建立相應的serversocket serverSocket=new ServerSocket(8888); while(true){ try { //進行監聽,當用戶端沒資料發送過來時,停在這裡; s=serverSocket.accept(); //獲得本機分配的當地socket連接埠 System.out.println(當地連接埠:+s.getLocalPort()); //獲得本機分配的當地socket連接埠 System.out.println(遠程連接埠:+s.getPort()); //獲得socketAddress System.out.println(遠程adress:+s.getRemoteSocketAddress()); System.out.println(本地adress:+s.getLocalSocketAddress()); //獲得inetAddress System.out.println(遠程inetAddress:+s.getInetAddress()); System.out.println(本地inetAddress:+s.getLocalAddress()); //獲得socket的輸出資料流,關閉socket時,會自動關閉socket的輸出輸入留 out=new PrintWriter(new OutputStreamWriter(s.getOutputStream())); //獲得socket的輸出資料流,關閉socket時,會自動關閉socket的輸出輸入留 in=new BufferedReader(new InputStreamReader(s.getInputStream())); //將用戶端發過來的資料輸出: String str=null; str=in.readLine(); System.out.println(收到+str); out.println(hello+i++); out.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { if(s!=null){ s.close(); } } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(serverSocket!=null){ try { serverSocket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
4.利用socket建立簡單聊天程式:
該聊天進程可以實現指定好友聊天,和群聊。只是簡單的示範。
1. 用戶端
線程數:2個,一個主線程用於發送訊息,一個線程用於接收伺服器發送過來的訊息
單聊實現:通過single標誌進行判斷。
退出實現:控制抬輸入:quit就會提示退出
單聊退出實現:當進入單聊後,控制台輸入q就退出單聊
2. 服務端:
線程數:由用戶端決定,當用戶端鍵入名字之後就啟動一個線程用於處理用戶端的各種請求,以及發送資料。將所有線程放入map集合,鍵為使用者名稱,值為線程對象;
單聊實現:當收到用戶端的singl標誌後使sin為true實現單聊,同時獲得指定使用者的線程進行發送資料。
退出實現:當接收到用戶端發送的quit時,向用戶端發送disconnect中斷連線,關閉線程
單聊退出:將單聊標誌置為false就行;
3. 用戶端代碼如下:
package com.rlovep.clinet;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.io.UnsupportedEncodingException;import java.net.Socket;/** * * @ClassName: TalkClinet* @Description: 聊天用戶端:* @author peace w_peace@163.com * @date 15 Oct 2015 7:21:22 pm* */public class TalkClinet { // 獲得控制台輸入 private BufferedReader sysin = null; // socket private Socket s = null; private BufferedReader in = null; private PrintWriter out = null; // 線程停止flag private boolean flag = true; //單聊標誌 private final static String single=single; public static void main(String[] args) { // 啟動用戶端: new TalkClinet().start(); } /** * * @Title: start * @Description: 主要作用:啟動發送和接收任務 * @return:void * @throws * @author peace w_peace@163.com */ public void start(){ try { // 獲得系統輸入: sysin = new BufferedReader(new InputStreamReader(System.in, utf-8)); // 設定名字: System.out.println(請輸入使用者名稱:); String name = sysin.readLine(); // 建立用戶端socket s = new Socket(127.0.0.1, 8888); // 獲得socket輸出out out = new PrintWriter(s.getOutputStream(), true); out.println(name); // 獲得socket輸入 in in = new BufferedReader(new InputStreamReader(s.getInputStream())); // 建立接收線程;原因擷取系統輸入會阻塞主線程 new Thread(new ClinetThread()).start(); // 發送訊息: String str = null; while (flag && ((str = sysin.readLine()) != null)) { //判斷是否為單聊標誌。如果不是單聊,就群發訊息 if(single.equalsIgnoreCase(str)){ System.out.println(請輸入想跟誰聊 :); //擷取系統輸入: if(flag && ((str = sysin.readLine()) != null)) { //發送單聊標誌 out.println(single) ; } } //向服務端發送內容: out.println(str); } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ //關閉資源 if(sysin!=null){ try { sysin.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(s!=null){ try { s.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } private class ClinetThread implements Runnable{ /** * * @Title: recive * @Description: 接收訊息,當訊息為disconnect時退出,此去需要在按下一次斷行符號用來終止系統輸入; * @return:void * @throws * @author peace w_peace@163.com */ private void recive(){ try { //接收服務端訊息 String str=in.readLine(); if(str!=null){ //如果是結束聊天,就退出線程 if(disconnect.equals(str)){ stop(); System.out.println(斷行符號退出:); } else { //否則顯示訊息 System.out.println(str); } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //線程停止方法 public void stop() { flag=false; } //線程主要任務 @Override public void run() { while(flag){ //接收訊息函數調用 recive(); } } }}
4.服務端代碼如下:
package com.rlovep.server;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketException;import java.util.HashMap;import java.util.Map;public class TalkServer { //儲存聊天線程: private Map map = new HashMap(); public static void main(String[] args) { //啟動伺服器 new TalkServer().start(); } /** * * @Title: start * @Description: 為每一個用戶端建立一個獨立線程 * @return:void * @throws * @author peace w_peace@163.com */ public void start(){ //伺服器servesocket ServerSocket serverSocket=null; //伺服器socket Socket socket=null; try { //伺服器建立 serverSocket=new ServerSocket(8888); //死迴圈,用於接收更多的用戶端 while(true){ //聊天線程建立 socket=serverSocket.accept(); //為每一個用戶端建立一個服務線程。 ServerThread st=new ServerThread(socket); new Thread(st).start(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ //釋放資源 try { if(serverSocket!=null){ serverSocket.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * * @ClassName: ServerThread * @Description: 聊天線程 * @author peace w_peace@163.com * @date 15 Oct 2015 8:31:44 pm * */ private class ServerThread implements Runnable{ //相應的socket private Socket socket=null; private BufferedReader in=null; private PrintWriter out=null; //每個聊天的名字 private String name=null; //聊天頭 private String meshead=null; //停止線程標誌位 private boolean flag=true; //單聊標誌位: private boolean sin=false; /** * *Title:線程構造器
*Description:用來建立線程,以及初始化,和將線程加入map
* @param socket * @throws IOException */ public ServerThread(Socket socket) throws IOException { //獲得socket this.socket=socket; //獲得輸出和輸入 out=new PrintWriter(socket.getOutputStream(),true); //獲得使用者名稱字 in=new BufferedReader(new InputStreamReader(socket.getInputStream())); name=in.readLine(); System.out.println(name+上線了); //製作聊天頭 meshead=name+[+socket.getInetAddress().getHostAddress()+:+socket.getPort()+]; //將線程加入map:key為名字;value為線程 map.put(name, this); //提醒所有使用者 send(meshead+上線了); } /** * * @Title: send * @Description: 發送訊息(群發) * @param msg * @return:void * @throws * @author peace w_peace@163.com */ public void send(String msg){ //迭代群發 for(ServerThread thread:map.values()){ thread.out.println(msg); } } /** * * @Title: Receiver * @Description: 接收訊息,並轉寄 * @throws IOException * @return:void * @author peace w_peace@163.com */ public void Receiver() throws IOException{ String str=null; ServerThread qq=null; //接收訊息 while((str=in.readLine())!=null){ //如果訊息為quit則退出 if(quit.equalsIgnoreCase(str)){ stop(); //給用戶端發送關閉連結命令 out.println(disconnect); break; } //判斷是否為單聊 if(sin==false) { if(str.equals(single)){ //如果為單聊就獲得想要跟誰聊 if((str=in.readLine())!=null){ //獲得另外一個用戶端線程 qq=map.get(str); if(qq!=null) sin=true; } } else { //轉寄訊息 群發 send(meshead+str); } } //如果是單聊就給對應的用戶端發送訊息; else{ //如果為q則退出單聊 if(str.equals(q)){ sin=false; qq.out.println(name+對話結束); } //否則發送訊息 else qq.out.println(name+對你說:+str); } } } /** * * @Title: stop * @Description: 停止函數 * @return:void * @throws * @author peace w_peace@163.com */ public void stop(){ flag=false; //下線移去碼map; map.remove(name); send(meshead+已經下線了); } /** * run方法 */ @Override public void run() { try { while(flag){ //不停接收訊息 Receiver(); } } catch(SocketException e){ stop();//用戶端直接關閉引發的錯誤; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ //釋放資源 try { if(socket!=null){ socket.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }}