Java Socket網路程式設計
Java Socket網路程式設計
計算機網路中,客戶機與伺服器之間進行通訊時,客戶機必須知道伺服器的IP地址和埠號。IP地址是標識Internet網路中的某臺計算機,而埠號則標識著在伺服器上執行的某個程式(程序),如果在伺服器上執行的程式,沒有埠號,則客戶端的程式就不能找到它,也不能和它進行通訊。一定要清楚,別和電腦上的物理埠號搞混了。 埠號是一個邏輯的地址,用於標識計算機中執行著的應用程式。它可以是0~65535之間的一個數,0~1023為系統保留使用,例如http的是80埠等,這些是系統已經分配給預設服務使用的。 當兩個應用程式需要通過網路通訊時,就可以使用IP地址和埠號進行通過,IP地址和埠號組合在一起就是一個Socket(網路套接字)。套接字分為服務端的和客戶端的兩種
一、客戶端套接字
客戶端的套接字用於與伺服器端的套接字進行連線。其使用方法如下:
socket=new Socket("127.0.0.1",2018);
其中127.0.0.1標識伺服器端的IP地址,2018標識伺服器端應用程式的埠號。在建立套接字的過程中,可能會發生IOException異常,要注意進行處理。
二、伺服器端套接字
客戶端與伺服器端通訊時,伺服器端負責提供服務,客戶端通過Socket向伺服器端請求服務。因此,在伺服器端也必須有相應的套接字與客戶端進行連線。使用如下:
try { serverSocket=new ServerSocket(2018); socket=serverSocket.accept(); } catch (IOException e) { e.printStackTrace(); }
ServerSocket的構造方法中,只有一個引數,即Port號,需要注意的是,伺服器端的Port號必須與客戶端的相同。
當呼叫ServerSocket的accept()方法時,會返回一個客戶端套接字物件,即Socket物件。該物件在客戶端與服務端連線的過程中,將駐留在伺服器的記憶體中,我們通過它就可以與客戶端進行資料的傳輸。
三、客戶端與服務端通訊的簡單實現
客戶端與服務端通訊時,首先服務端要在相應的埠進行監聽,等待客戶端的連線請求。然後在客戶端請求時,接受客戶端的請求,然後進行通訊。首先是服務端。程式碼如下:
package socket.demo2; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class MyServerFrame extends JFrame{ //接收客戶端傳送的訊息 private JTextArea allMessage=new JTextArea(8,20); // 輸入傳送給客戶端的訊息 private JTextField sendMessage=new JTextField(20); //訊息傳送按鈕 private JButton sendBtn=new JButton("傳送"); private JButton startServer=new JButton("開始伺服器"); private ServerSocket serverSocket=null;//伺服器端Socket private BufferedReader in=null;//用於接收客戶端傳送的資料 private BufferedWriter out=null;//用於傳送資料給客戶端 private Socket socket=null;//客戶端連線物件 private int port; public void setPort(int port){ this.port=port; } public MyServerFrame(){ init(); } private void init(){ setTitle("服務端"); setLayout(new BorderLayout()); //開始伺服器按鈕單擊事件 startServer.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { serverSocket=new ServerSocket(port); socket=serverSocket.accept(); new receiverMsgThread().start();//啟動執行緒接收客戶端資料 } catch (IOException e1) { e1.printStackTrace(); } } }); //傳送按鈕單擊事件 sendBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); out.write(sendMessage.getText()); out.newLine(); out.flush(); } catch (IOException e1) { e1.printStackTrace(); } } }); //將元件新增到窗體中 JPanel panel=new JPanel(); panel.add(startServer); panel.add(sendMessage); panel.add(sendBtn); add(allMessage,BorderLayout.CENTER); add(panel,BorderLayout.SOUTH); //配置窗體 setSize(500,300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } //接收資訊執行緒 class receiverMsgThread extends Thread{ public void run(){ try { in=new BufferedReader(new InputStreamReader(socket.getInputStream())); String str=null; while (true){ str=in.readLine()+"\n"; allMessage.append("客戶端說:"+str); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { MyServerFrame myServerFrame=new MyServerFrame(); myServerFrame.setPort(12345); } }
客戶端程式碼如下:
package socket.demo2; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.net.Socket; public class MyClientFrame extends JFrame{ //接收客戶端傳送的訊息 private JTextArea allMessage=new JTextArea(8,20); // 輸入傳送給客戶端的訊息 private JTextField sendMessage=new JTextField(20); //訊息傳送按鈕 private JButton sendBtn=new JButton("傳送"); private JButton connServer=new JButton("連線伺服器"); private Socket socket=null; private BufferedReader in=null; private BufferedWriter out=null; public MyClientFrame(){ init(); } private void init(){ setTitle("客戶端"); setLayout(new BorderLayout()); //連線伺服器按鈕單擊事件 connServer.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { socket=new Socket("127.0.0.1",12345); System.out.println("連線成功"); new receiverMsgThread().start(); } catch (IOException e1) { e1.printStackTrace(); } } }); //傳送按鈕單擊事件 sendBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); out.write(sendMessage.getText()); out.newLine(); out.flush(); System.out.println("222"); } catch (IOException e1) { e1.printStackTrace(); } } }); //將元件新增到窗體中 JPanel panel=new JPanel(); panel.add(connServer); panel.add(sendMessage); panel.add(sendBtn); add(allMessage,BorderLayout.CENTER); add(panel,BorderLayout.SOUTH); //配置窗體 setSize(500,300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } class receiverMsgThread extends Thread{ public void run(){ InputStream is=null; BufferedReader br=null; String str=null; try { is=socket.getInputStream(); br=new BufferedReader(new InputStreamReader(is)); while(true){ str=br.readLine()+"\n"; allMessage.append("伺服器說:"+str); } } catch (IOException e) { e.printStackTrace(); } System.out.println("客戶端"); } } public static void main(String[] args) { new MyClientFrame(); } }
程式執行介面如下:
目前為止,服務端只能與一個客戶端進行通訊。在編寫的過程中,由於對BufferedReader類,之前並不瞭解,因此在編寫的過程中,遇到了很大的問題,導致走了很多的彎路。最後通過查閱其文件,解決了問題。主要是因為其readLine()方法,在傳送資料時,它是以回車符作為結束的,否則它不會結束,從而導致後續的操作無法完成,因此在傳送資料時,一定要注意,程式碼如下:
out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); out.write(sendMessage.getText());//這個只是將資料寫入到快取中,並不真正進行傳送 out.newLine();//這是一個新行,標識傳輸資料的結束,這點一定要注意 out.flush();//這個是將快取中的資料進行傳送
四、服務端的改進
以上程式程式碼中,伺服器只能接收一個客戶端的連線,下面對伺服器端進行改進,實現多個客戶端的連線。
思想如下:採用多執行緒方式,每一個客戶端連線時,啟動一個執行緒負責該客戶端的通訊,將產生的客戶端Socket放入到一個集合中,然後,當客戶端傳送資料時,伺服器端將收到的資料轉發給所有的客戶端。服務端修改後的程式碼如下;
package socket.demo2; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class MyServerFrame extends JFrame{ //儲存客戶端的Socket,用於向所有的客戶端進行資料的轉發。 private static List<Socket> clients=new ArrayList<Socket>(); //接收客戶端傳送的訊息 private JTextArea allMessage=new JTextArea(8,20); // 輸入傳送給客戶端的訊息 private JTextField sendMessage=new JTextField(20); //訊息傳送按鈕 private JButton sendBtn=new JButton("傳送"); private JButton startServer=new JButton("開始伺服器"); private ServerSocket serverSocket=null;//伺服器端Socket private BufferedReader in=null;//用於接收客戶端傳送的資料 private BufferedWriter out=null;//用於傳送資料給客戶端 private Socket socket=null;//客戶端連線物件 private int port; public void setPort(int port){ this.port=port; } public MyServerFrame(){ init(); } private void init(){ setTitle("服務端"); setLayout(new BorderLayout()); //開始伺服器按鈕單擊事件 startServer.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { startServer.setEnabled(false); //啟動連線執行緒 try { serverSocket=new ServerSocket(port); } catch (IOException e1) { e1.printStackTrace(); } new connClient().start(); } }); //傳送按鈕單擊事件 sendBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { for(Socket s:clients){ out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); out.write(sendMessage.getText()); out.newLine(); out.flush(); } } catch (IOException e1) { e1.printStackTrace(); } } }); //將元件新增到窗體中 JPanel panel=new JPanel(); panel.add(startServer); panel.add(sendMessage); panel.add(sendBtn); add(allMessage,BorderLayout.CENTER); add(panel,BorderLayout.SOUTH); //配置窗體 setSize(500,300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } class connClient extends Thread{ public void run(){ while(true){ try { socket=serverSocket.accept(); clients.add(socket); new sendMsgToClients(socket).start(); new receiverMsgThread().start();//啟動執行緒接收客戶端資料 } catch (IOException e1) { e1.printStackTrace(); } } } } //接收資訊執行緒 class receiverMsgThread extends Thread{ public void run(){ try { in=new BufferedReader(new InputStreamReader(socket.getInputStream())); String str=null; while (true){ str=in.readLine()+"\n"; allMessage.append("客戶端說:"+str); } } catch (IOException e) { e.printStackTrace(); } } } class sendMsgToClients extends Thread{ private Socket clientSocket; public sendMsgToClients(Socket clientSocket){ this.clientSocket=clientSocket; } public void run(){ //首先接收客戶端傳送過來的資訊, //然後向客戶端,進行資訊的轉發。 try { while(true){ in=new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String str=in.readLine(); for(Socket s:clients){ out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); out.write(str); out.newLine(); out.flush(); } } } catch (IOException e) { e.printStackTrace(); clients.remove(socket); } } } public static void main(String[] args) { MyServerFrame myServerFrame=new MyServerFrame(); myServerFrame.setPort(12345); } }