JavaSE項目之聊天室
引子:
當前,互聯網 體系結構的參考模型主要有兩種,一種是OSI參考模型,另一種是TCP/IP參考模型。
一、OSI參考模型,即開放式通信系統互聯參考模型(OSI/RM,Open Systems Interconnection Reference Model),是國際標準化組織(ISO)提出的一個試圖使各種計算機在世界範圍內互連為網絡的標準框架,簡稱OSI。
OSI參考模型將實現網絡互連的通信協議分為7層,自上而下分別是:
第7層應用層:OSI中的最高層,為用戶提供各項互聯網應用,如公司老板通過瀏覽器上網、發送電子郵件等。 常見的協議有:HTTP,HTTPS,FTP,TELNET,SSH,SMTP,POP3等。
第6層表示層:相當公司中替老板寫信的助理。
第5層會話層:相當於公司中收寄信、寫信封與拆信封的秘書。
第4層傳輸層:提供終端到終端的可靠連接,相當於公司中跑郵局的送信職員。
第3層網絡層: 確保信件通過一系列路由到達目的地。
第2層數據鏈路層: 決定訪問網絡介質的方式,並處理流控制。
第1層物理層:處於OSI參考模型的最底層,物理層的主要功能是利用物理傳輸介質為數據鏈路層提供物理連接,以便透明地傳輸比特流;該層的常用設備有網卡、集線器、中繼器、調制解調器、網線、雙絞線、同軸電纜等各種物理設備。
數據發送時,從第七層傳到第一層,接收數據則相反。
上三層總稱為“應用層”,用來控制軟件方面;下四層總稱為“數據流層”,用來管理硬件。除了物理層之外,其他層都是用軟件實現的。
二、TCP/IP參考模型。
┌────------────┐┌─┬─┬─-┬─┬─-┬─┬─-┬─┬─-┬─┬─-┐ │ ││D│F│W│F│H│G│T│I│S│U│ ││ ││N│I│H│T│T│O│E│R│M│S│其│
│第四層,應用層 ││S│N│O│P│T│P│L│C│T│E│ │
│ ││ │G│I│ │P│H│N│ │P│N│ │
│ ││ │E│S│ │ │E│E│ │ │E│它│
│ ││ │R│ │ │ │R│T│ │ │T│ │
└───────------─┘└─┴─┴─-┴─┴─-┴─┴─-┴─┴─-┴─┴-─┘
┌───────-----─┐┌─────────-------┬──--------─────────┐
│第三層,傳輸層 ││ TCP │ UDP │
└───────-----─┘└────────-------─┴──────────--------─┘
┌───────-----─┐┌───----──┬───---─┬────────-------──┐
│ ││ │ICMP│ │
│第二層,網間層 ││ └──---──┘ │
│ ││ IP │
└────────-----┘└────────────────────-------------─-┘
┌────────-----┐┌─────────-------┬──────--------─────┐
│第一層,網絡接口││ARP/RARP │ 其它 │
└────────------┘└─────────------┴─────--------──────┘
我們在對上述兩種參考模型有些了解後,接下來主要看TCP和UDP。我們先來看二者的區別:
1.TCP面向連接(如打電話要先撥號建立連接);UDP是無連接的,即發送數據之前不需要建立連接;
2.TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付,即不保證可靠交付;
3.TCP面向字節流,實際上是TCP把數據看成是一連串無結構的字節流;UDP是面向報文的,它沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低(如IP電話,實時視頻會議等)
4.每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信;
5.TCP首部開銷20字節;UDP的首部開銷小,只有8個字節;
6.TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道。
在Java中,對遵守TCP協議的類有ServerSocket和Socket,遵守UDP協議的類有DatagramSocket。我這裏提供的聊天室項目,是基於TCP協議的。
該項目分為4個包,分別是utils(提供工具),ui(提供窗體界面),server(服務器子線程),client(客戶端子線程)。
utils包下有兩個類,分別是HostInfo和Release。HostInfo.java代碼如下:
import java.net.InetAddress; import java.net.UnknownHostException; public abstract class HostInfo { public static String IP=getIP(); public static final int PORT=10086; public static final int NUM=50; public static final String NEW_LINE="\r\n"; private static String getIP(){ try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); return null; } } }
Release.java的代碼如下:
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public abstract class Release { public static void release(Socket socket,BufferedWriter bw){ release(null,socket,null,bw); } public static void release(Socket socket,BufferedReader br){ release(null,socket,br,null); } public static void release(Socket socket){ release(null,socket,null,null); } public static void release(ServerSocket server){ release(server,null,null,null); } public static void release(ServerSocket server,Socket socket,BufferedReader br,BufferedWriter bw){ if(server!=null){ try { server.close(); } catch (IOException e) { e.printStackTrace(); } } if(socket!=null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(br!=null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(bw!=null){ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
ui包下有兩個類,分別是UIserver和UIclient。UIserver.java的代碼如下:
import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import 聊天室.server.Server; import 聊天室.utils.HostInfo; public class UIserver extends JFrame { private JPanel jp=new JPanel(); private JLabel jl_ipTips=new JLabel("服務器ip:"); public static JTextField jtf_ip=new JTextField(HostInfo.IP); private JLabel jl_portTips=new JLabel("服務器端口:"); public static JTextField jtf_port=new JTextField(HostInfo.PORT+""); public static JButton bt_open=new JButton("啟動服務器"); public static JTextArea jta_log=new JTextArea(); private JScrollPane jsp_log=new JScrollPane(jta_log); public UIserver(){ jp.setLayout(new FlowLayout()); jp.add(jl_ipTips); jp.add(jtf_ip); jp.add(jl_portTips); jp.add(jtf_port); jp.add(bt_open); jp.add(jsp_log); jl_ipTips.setPreferredSize(new Dimension(100, 50)); jtf_ip.setPreferredSize(new Dimension(150, 50)); jl_portTips.setPreferredSize(new Dimension(100, 50)); jtf_port.setPreferredSize(new Dimension(150, 50)); bt_open.setPreferredSize(new Dimension(260, 50)); jsp_log.setPreferredSize(new Dimension(260, 192)); jta_log.setLineWrap(true); jta_log.setEditable(false); bt_open.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent arg0) { new Server().start(); } }); add(jp); setTitle("聊天應用控制服務器"); setBounds(100, 50, 300, 400); setResizable(false); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new UIserver(); } }
UIclient.java的代碼如下:
import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import 聊天室.client.Client; public class UIclient extends JFrame { private JPanel jp_chat=new JPanel(); public static JTextArea jta_chat=new JTextArea(); private JScrollPane jsp_chat=new JScrollPane(jta_chat); private JPanel jp_send=new JPanel(); public static JTextField jtf_desip=new JTextField("請輸入對方Ip"); public static JTextArea jta_message=new JTextArea(); private JScrollPane jsp_message=new JScrollPane(jta_message); private JButton bt_send=new JButton("發送"); private Client client; public UIclient(){ jp_chat.add(jsp_chat); jta_chat.setLineWrap(true); jta_chat.setEditable(false); jsp_chat.setPreferredSize(new Dimension(550, 400)); jp_send.add(jtf_desip); jp_send.add(jsp_message); jta_message.setLineWrap(true); jp_send.add(bt_send); jtf_desip.setPreferredSize(new Dimension(100, 50)); jsp_message.setPreferredSize(new Dimension(250, 50)); bt_send.setPreferredSize(new Dimension(100, 50)); jtf_desip.addFocusListener(new FocusAdapter(){ @Override public void focusGained(FocusEvent e) { jtf_desip.setText(""); } }); jta_message.addFocusListener(new FocusAdapter(){ @Override public void focusGained(FocusEvent e) { jta_message.setText(""); } }); bt_send.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent arg0) { client.send(); } }); add(jp_chat, BorderLayout.CENTER); add(jp_send, BorderLayout.SOUTH); setTitle("群聊窗口"); setBounds(300, 100, 600, 500); setResizable(false); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); client=new Client(); client.start(); } public static void main(String[] args) { new UIclient(); } }
server包下有兩個類,分別是Server和Transport。Server.java的代碼如下:
import java.io.IOException;import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; import 聊天室.ui.UIserver; import 聊天室.utils.HostInfo; import 聊天室.utils.Release; public class Server extends Thread { private ServerSocket server; public static List<Transport> clients=new ArrayList<>(); @Override public void run() { try { server=new ServerSocket(Integer.parseInt(UIserver.jtf_port.getText().trim())); } catch (IOException e) { Release.release(server); throw new RuntimeException("服務器端口被占!"); } UIserver.bt_open.setText("已啟動服務器"); UIserver.bt_open.setEnabled(false); UIserver.jta_log.append("服務器成功啟動!"+HostInfo.NEW_LINE); new Accept().start(); } class Accept extends Thread{ private Socket socket; @Override public void run() { int num=0; while(num<HostInfo.NUM){ try { socket=server.accept(); } catch (IOException e) { Release.release(socket); throw new RuntimeException("客戶端連接失敗!"); } num++; String str="第 "+num+" 個客戶端連接成功!==>"+socket.getInetAddress().getHostAddress()+" :"+socket.getPort()+HostInfo.NEW_LINE; UIserver.jta_log.append(str); clients.add(new Transport(socket)); } UIserver.jta_log.append("超出服務器負荷!"); Release.release(server); } } }
Transport.java的代碼如下:
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import 聊天室.utils.Release; public class Transport extends Thread { private Socket socket; private String ip; public Transport(Socket socket){ this.socket=socket; this.ip=socket.getInetAddress().getHostAddress(); this.start(); } @Override public void run() { BufferedReader br=null; BufferedWriter ownbw=null; try { br=new BufferedReader(new InputStreamReader(socket.getInputStream())); ownbw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); } catch (IOException e) { Server.clients.remove(this); Release.release(socket); throw new RuntimeException("獲取流失敗!"); } String str=null; try { while((str=br.readLine())!=null){ String[] split = str.split(":", 2); if(split.length<=1){ ownbw.write("數據格式錯誤!"); ownbw.newLine(); ownbw.flush(); } String desip=split[0]; String content=split[1]; BufferedWriter desbw=null; boolean isOnLine=false; for(Transport des:Server.clients){ if(desip.equals(des.ip)){ isOnLine=true; desbw=new BufferedWriter(new OutputStreamWriter(des.socket.getOutputStream())); desbw.write(str); desbw.newLine(); desbw.flush(); } } if(!isOnLine){ ownbw.write("對方不在線!"); ownbw.newLine(); ownbw.flush(); } } } catch (IOException e) { Server.clients.remove(this); Release.release(socket); throw new RuntimeException("獲取流失敗!"); } } }
client包下有一個類Client.java,該類的代碼如下:
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import 聊天室.ui.UIclient; import 聊天室.ui.UIserver; import 聊天室.utils.HostInfo; import 聊天室.utils.Release; public class Client extends Thread { private Socket socket; @Override public void run() { try { socket=new Socket(UIserver.jtf_ip.getText().toLowerCase(), Integer.parseInt(UIserver.jtf_port.getText().trim())); } catch (IOException e) { Release.release(socket); throw new RuntimeException("客戶端創建失敗!"); } BufferedReader br=null; try { br=new BufferedReader(new InputStreamReader(socket.getInputStream())); } catch (IOException e) { Release.release(socket); throw new RuntimeException("獲取流失敗!"); } String str=null; try { while((str=br.readLine())!=null){ UIclient.jta_chat.append(str+HostInfo.NEW_LINE); } } catch (IOException e) { Release.release(socket); throw new RuntimeException("獲取流失敗!"); } } public void send(){ new Send().start(); } class Send extends Thread{ @Override public void run() { BufferedWriter bw=null; try { bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); } catch (IOException e) { Release.release(socket,bw); throw new RuntimeException("獲取流失敗!"); } String str=UIclient.jtf_desip.getText().trim()+":"+UIclient.jta_message.getText().trim(); try { bw.write(str); bw.newLine(); bw.flush(); } catch (IOException e) { Release.release(socket,bw); throw new RuntimeException("獲取流失敗!"); } } } }
JavaSE項目之聊天室