1. 程式人生 > >JavaSE項目之聊天室

JavaSE項目之聊天室

失敗 equal list 利用 time body void catch write

引子:

當前,互聯網 體系結構的參考模型主要有兩種,一種是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項目之聊天室