浅谈java中的ServerSocket和Socket的通信原理实现聊天及多人聊天
聊天,QQ,微信,陌陌很多的即時通訊的軟件,不管是桌面端還是移動端,在當今社交的時代都是不可或缺的一部分。這時候說Socket和ServerSocket感覺有點老調重彈感覺,相信很多人早就知道如何使用了,而且利用這個通信原理可能已經開發出很多優秀的通信軟件吧,但是我感覺這個對于剛接觸java網絡編程的人來說,學會Socket通信實現聊天軟件,是必須的一步,了解其中的原理更是非常重要的一步,對,很多人可能覺得對著視頻敲出一個軟件很容易,但是你能學到什么???盲目地崇拜大神嗎??,我認為需要花更多的時間去弄懂其實現的原理,然后總結一些屬于自己的東西出來。本人菜鳥,但是喜歡分享一些自己的東西,希望能幫助需要幫助的人,不說廢話,直接上...
為了照顧一下初學者下面就大概說下Socket的介紹:
1、Scoket又稱“套接字”,應用程序通常通過“套接字”向網絡發出請求或者應答網絡請求
在 java中Socket和ServerSocket類庫位于java.net包中。ServerSocket用于服務器端
Socket是建立網絡連接時使用的,在連接成功時,應用程序兩端都會產生一個Socket實例
操作這個實例,完成所需的會話。對于一個網絡連接來說,套接字是平等的,并沒有差別
不因為在服務器端或在客戶端而產生不同的級別,不管是Socket還是ServerSocket他們的
工作都是通過Socket類和其子類來完成的
2、建立Socket鏈接可分三個步驟:
???????? 1.服務器監聽
?????? ? 2.客戶端發出請求
?????????3.建立鏈接
???????? 4.通信
3、Socket特點:
????????? 1.基于TCP鏈接,數據傳輸有保障
????????? 2.適用于建立長時間的鏈接,不像HTTP那樣會隨機關閉
????????? 3.Socket編程應用于即時通訊
4、ServerSocket的建立和使用:
public class ServerSocket_Test {
?public static void main(String[] args) {
??//port參數表示服務器監聽的端口號,從1-65535
??try {
???
???ServerSocket serverSocket =new ServerSocket(12345);
?????
???//block,當沒有客戶端連接時,改主線程會一直阻塞等待連接,一直監聽,直到有客戶端連接才會執行
??Socket socket=?serverSocket.accept();//偵聽事務的連接,accept是一個阻塞的方法,會阻塞當前的main線程,并且返回的是一個Socket類型
??//建立連接,表示serverSocket在監聽,如果監聽到有客戶端連接則會調用accept方法,然后返回一個Socket,最后建立連接
??JOptionPane.showMessageDialog(null, "有客戶端連接到了本機的12345端口");
??//然后測試在瀏覽器中輸入http://127.0.0.1:12345則會彈出相應有客戶端連接的提示框,然后原來阻塞在accept方法那里就會往下執行
??} catch (IOException e) {
???e.printStackTrace();
??}
?}
注意:但是以上的這種方法不推薦,因為這個里面有線程阻塞,會阻塞主線程,所以推薦一種更好的方法是就是單獨開啟一個線程去實現服務器監聽客戶端的連接
5、ServerSocketListener(單獨的線程實現服務器的監聽)
public class ServerListener extends Thread {
?@Override
?public void run() {
??//port參數表示服務器監聽的端口號,從1-65535
??try {
???ServerSocket serverSocket =new ServerSocket(12345);
???while (true) {???//由于可能當有多個客戶端連接時,accept方法就會產生多個Socket對象,需加一個while循環監聽來自客戶端的連接
????//block,當沒有客戶端連接時,改主線程會一直阻塞等待連接,一直監聽,直到有客戶端連接才會執行
????Socket socket=?serverSocket.accept();//偵聽事務的連接,accept是一個阻塞的方法,會阻塞當前的main線程,并且返回的是一個Socket類型
????//建立連接,表示serverSocket在監聽,如果監聽到有客戶端連接則會調用accept方法,然后返回一個Socket,最后建立連接
????JOptionPane.showMessageDialog(null, "有客戶端連接到了本機的12345端口");
????//然后測試在瀏覽器中輸入http://127.0.0.1:12345則會彈出相應有客戶端連接的提示框,然后原來阻塞在accept方法那里就會往下執行
???
????//將socket傳遞給另起的一個新的線程,即是socket通信的線程
????new ChatSocket(socket).start();
???
???}
???
??} catch (IOException e) {
???e.printStackTrace();
??}
?}
然后在主方法去開啟這個線程即可:
public class ServerSocket_Test {
?public static void main(String[] args) {
??new ServerListener().start();
?}
}
用瀏覽器打開運行結果(表示此時已經有客戶端連接到服務器了,監聽到客戶端后就會彈出提示框,此時的瀏覽器就相當于客戶端):
?
那么接下來就講解一下聊天服務器端(ServerSocket)的是實現:
主要實現原理:因為一個客戶端就相當于開啟一個Socket線程,然而要實現多人聊天,就相當于開啟多個Socket線程(即多個客戶端),然后把這些線程加入到Vector集合中去,當客戶端(Socket)發送一條信息時,也就相當于服務器(ServerSocket)讀入信息,而對于客戶端是向服務器輸入,實現輸入流Socket.InputStream,然后利用BufferReader緩沖流讀入服務器,然后在服務器(ServerSocket)中的去遍歷這個線程集合,如果不是當前客戶端對象就發送信息,這樣就是實現了把當前客戶端信息轉發給其他的客戶端使用Socket.OutputStream,即服務器向客戶端輸出流,并用PrintWriter流寫入客戶端。
具體見圖:
ServerSocket代碼:
1、ServerSocket.java:
public class ServerSocket {
?public static void main(String[] args) {
??new ServerListener().start();
??//運行的方法在command命令下輸入:"telnet localhost 12345",每建立一個就是一個客戶端,而且每個客戶端享受不同的線程,等級是平等的
?}
2.ServerListener.java
public class ServerListener extends Thread {
?@Override
?public void run() {
??//port參數表示服務器監聽的端口號,從1-65535
??try {
???ServerSocket serverSocket =new ServerSocket(12345);
???while (true) {//由于可能當有多個客戶端連接時,accept方法就會產生多個Socket對象,需加一個while循環監聽來自客戶端的連接
????Socket socket=?serverSocket.accept();//偵聽事務的連接,accept是一個阻塞的方法,會阻塞當前的main線程,并且返回的是一個Socket類型
????//建立連接,表示serverSocket在監聽,如果監聽到有客戶端連接則會調用accept方法,然后返回一個Socket,最后建立連接
????JOptionPane.showMessageDialog(null, "有客戶端連接到了本機的12345端口");
????
????ChatSocket cs= new ChatSocket(socket);
??????? cs.start();//開啟ChatSocket線程
??????? ChatManager.getchaChatManager().add(cs);
???}
???
??} catch (IOException e) {
???e.printStackTrace();
??}
?}
}
3.ChatSocket.java
public class ChatSocket extends Thread {
//創建一個Socket對象來接收SocketListener傳來的Socket對象
?Socket socket;
?public ChatSocket(Socket s) {
????????? this.socket=s;
}
?public void out(String out){
??try {
???socket.getOutputStream().write((out+"\n").getBytes("UTF-8"));//接收來自服務器端的數據
??}catch (UnsupportedEncodingException e) {
???e.printStackTrace();
??}
??catch (IOException e) {
???System.out.println("斷開了一個客戶端鏈接");
???ChatManager.getchaChatManager().remove(this);
???e.printStackTrace();
??}
?}
?@Override
??public void run() {
???? out("您已經連接到服務器");
??? try {
??
??BufferedReader br=new BufferedReader(
????new InputStreamReader(
??????socket.getInputStream(),"UTF-8"));//當前服務器會不斷讀取當前客戶端的數據
??String line=null;
???? while ((line=br.readLine())!=null) {//客戶端發送給服務器的數據
????//然后服務器再將所有的信息轉發給每一個客戶端,調用publish方法
?????
????? ChatManager.getchaChatManager().publish(this, line);
???}
???? br.close();
???? System.out.println("斷開了一個客戶端鏈接");
???ChatManager.getchaChatManager().remove(this);
?} catch (IOException e) {
??System.out.println("斷開了一個客戶端鏈接");
??ChatManager.getchaChatManager().remove(this);
??e.printStackTrace();
?}
????
?}
4、ChatManager.java
public class ChatManager {
????? //因為一個聊天服務器只有一個ChatManager所以需要創建一個單例
?
?private ChatManager(){}
?private static final ChatManager cm= new ChatManager();
?public static? ChatManager getchaChatManager(){
??return cm;
?}
?
?Vector<ChatSocket> vector=new Vector<ChatSocket>();
?public void add(ChatSocket cs){
????? vector.add(cs);//將每一個線程加入集合中
?}
?public void remove(ChatSocket cs) {
??vector.remove(cs);
?}
?public? void publish(ChatSocket cs,String chatinfo){//表示當前的線程給集合中的每一個線程發送的信息,也即當前的客戶端給每一個客戶端發送信息
??//要給集合中所有的線程發送信息就必須遍歷這個集合
??for (int i = 0; i < vector.size(); i++) {
???ChatSocket csChatSocket=vector.get(i);
???if(!csChatSocket.equals(cs)){//則需要判斷不許給當前客戶端發送信息,也即不給自己發送信息
????csChatSocket.out(chatinfo);//發送信息給其他的客戶端
???}
??}
?}
}
?
到現在其實我們就可以測試聊天和多人聊天的功能:
運行的方法:我們打開Command命令,輸入telnet localhost 12345,然后回車,就會建立起一個多人聊天室:
運行結果:
那接下來講解聊天客戶端的實現:
客戶端的實現主要是有兩部分,第一是GUI的實現,這個在這就不多講了這是GUI(Swing)界面編程的知識,第二個就是客戶端的邏輯的實現,因為客戶端既要發送信息
又要接收信息,發送信息給服務器則需要Socket.getInputStream字節流,轉換成InputStreamReader字符流,轉換成BufferReader緩沖流,read到服務器;接收信息,從服務器中接收信息,則需要Socket.OutputStream字節流,轉換成OutputStream轉換成PrintWriter流,write到客戶端。
代碼:
ChatManager:
public class ChatManager {
? private ChatManager(){}
? private static final ChatManager instance =new ChatManager();
? public static ChatManager getChatManager(){
?? return instance;
? }
? MainWindow window;
? String IP;
? Socket socket;
? BufferedReader br;
? PrintWriter pw;
? public void setWindow(MainWindow window) {
?this.window = window;
?window.appendText("文本框已經和Manage綁定了");
}
? public void connect(String ip){
?? this.IP=ip;
?? new Thread(){
??@Override
??public void run() {
????? try {
???? ??socket=new Socket(IP, 12345);//創建客戶端,連接的端口是ServerSocket的端口
???? ? pw=new PrintWriter(
???? ??? new OutputStreamWriter(
???? ????? socket.getOutputStream(),"UTF-8") );
???? ? br=new BufferedReader(
???? ??? new InputStreamReader(
???? ????? socket.getInputStream(),"UTF-8"));
???? ? String line;
???? ?
???? ? while ((line=br.readLine())!=null) {
????window.appendText("收到:"+line);
???}
???? ? br.close();
???? ?pw.close();
???? ?pw=null;
???? ?br=null;
??
????? }catch (UnknownHostException e) {
????e.printStackTrace();
???}
????? catch (IOException e) {
???e.printStackTrace();
??}
??}
?? }.start();
? }
? public void send(String out){
?? if(pw!=null){
??? pw.write(out+"\n");
??? pw.flush();
?? }else{
??window.appendText("已中斷與服務器的連接");?
?? }
? }
}
StartClient.java:
public class StartClient {
?public static void main(String[] args) {
??EventQueue.invokeLater(new Runnable() {
???public void run() {
????try {
?????MainWindow frame = new MainWindow();
?????frame.setVisible(true);
?????ChatManager.getChatManager().setWindow(frame);
????} catch (Exception e) {
?????e.printStackTrace();
????}
???}
??});
?}
}
?
GUI界面代碼:
package com.zhongqihong.client.view;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JTextArea;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.LayoutStyle.ComponentPlacement;
import com.zhongqihong.client.ChatManager;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class MainWindow extends JFrame {
?private static final long serialVersionUID = 1L;
?private JPanel contentPane;
?JTextArea txt;
?private JTextField ip;
?private JTextField send;
?/**
? * Create the frame.
? */
?public MainWindow() {
??setAlwaysOnTop(true);
??setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
??setBounds(100, 100, 450, 300);
??contentPane = new JPanel();
??contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
??setContentPane(contentPane);
??
??txt = new JTextArea();
??txt.setText("Ready...");
??
??ip = new JTextField();
??ip.setText("127.0.0.1:808");
??ip.setColumns(10);
??
??JButton button = new JButton("\u8FDE\u63A5\u5230\u670D\u52A1\u5668");
??button.addMouseListener(new MouseAdapter() {
???@Override
???public void mouseClicked(MouseEvent e) {
????ChatManager.getChatManager().connect(ip.getText());
???}
??});
??
??send = new JTextField();
??send.setText("\u60A8\u597D");
??send.setColumns(10);
??
??JButton button_1 = new JButton("\u53D1\u9001");
??button_1.addMouseListener(new MouseAdapter() {
???@Override
???public void mouseClicked(MouseEvent e) {
????ChatManager.getChatManager().send(send.getText());
???? appendText("我說:"+send.getText());
??????? send.setText("");
???}
??});
??GroupLayout gl_contentPane = new GroupLayout(contentPane);
??gl_contentPane.setHorizontalGroup(
???gl_contentPane.createParallelGroup(Alignment.LEADING)
????.addGroup(Alignment.TRAILING, gl_contentPane.createSequentialGroup()
?????.addComponent(ip, GroupLayout.DEFAULT_SIZE, 277, Short.MAX_VALUE)
?????.addGap(18)
?????.addComponent(button, GroupLayout.PREFERRED_SIZE, 119, GroupLayout.PREFERRED_SIZE)
?????.addContainerGap())
????.addGroup(Alignment.TRAILING, gl_contentPane.createSequentialGroup()
?????.addComponent(send, GroupLayout.DEFAULT_SIZE, 251, Short.MAX_VALUE)
?????.addGap(18)
?????.addComponent(button_1, GroupLayout.PREFERRED_SIZE, 135, GroupLayout.PREFERRED_SIZE)
?????.addGap(20))
????.addComponent(txt, GroupLayout.DEFAULT_SIZE, 424, Short.MAX_VALUE)
??);
??gl_contentPane.setVerticalGroup(
???gl_contentPane.createParallelGroup(Alignment.LEADING)
????.addGroup(gl_contentPane.createSequentialGroup()
?????.addContainerGap()
?????.addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)
??????.addComponent(ip, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
??????.addComponent(button))
?????.addPreferredGap(ComponentPlacement.RELATED)
?????.addComponent(txt, GroupLayout.DEFAULT_SIZE, 174, Short.MAX_VALUE)
?????.addPreferredGap(ComponentPlacement.RELATED)
?????.addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)
??????.addComponent(send, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
??????.addComponent(button_1))
?????.addContainerGap())
??);
??contentPane.setLayout(gl_contentPane);
?}
public void appendText(String in){
?txt.append("\n"+in);
}
}
public class ServerSocket_Test {
?public static void main(String[] args) {
??new ServerListener().start();
??//運行的方法在command命令下輸入:"telnet localhost 12345",每建立一個就是一個客戶端,而且每個客戶端享受不同的線程,等級是平等的
?}
}
?}
?}
運行代碼:
兩人私聊:
多人群聊:
到這里,我們的多人聊天的客戶端就成功實現了,其實一步一步來,把復雜的問題分解成一個個小問題來解決就可以了。
PS:這里是Demo的源碼:http://pan.baidu.com/s/1qWQikMC
?
?
總結
以上是生活随笔為你收集整理的浅谈java中的ServerSocket和Socket的通信原理实现聊天及多人聊天的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 马云:我不为996辩护,我向奋斗者致敬
- 下一篇: 新款奔驰S400L改装原厂360全景影像