日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Socket编程实现简易聊天室

發(fā)布時(shí)間:2024/1/17 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Socket编程实现简易聊天室 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?

1.Socket基礎(chǔ)知識(shí)

  Socket(套接字)用于描述IP地址和端口,是通信鏈的句柄,應(yīng)用程序可以通過(guò)Socket向網(wǎng)絡(luò)發(fā)出請(qǐng)求或者應(yīng)答網(wǎng)絡(luò)請(qǐng)求。

  Socket是支持TCP/IP協(xié)議的網(wǎng)絡(luò)通信的基本操作單元,是對(duì)網(wǎng)絡(luò)通信過(guò)程中端點(diǎn)的抽象表示,包含了進(jìn)行網(wǎng)絡(luò)通信所必需的5種信息:連接所使用的協(xié)議、本地主機(jī)的IP地址、本地進(jìn)程的協(xié)議端口、遠(yuǎn)地主機(jī)的IP地址以及遠(yuǎn)地進(jìn)程的協(xié)議端口。

1.1?Socket的傳輸模式

  Socket有兩種主要的操作方式:面向連接的和無(wú)連接的。(TCP/UDP)

  面向連接的Socket操作就像一部電話,Socket必須在發(fā)送數(shù)據(jù)之前與目的地的Socket取得連接,一旦連接建立了,Socket就可以使用一個(gè)流接口進(jìn)行打開(kāi)、讀寫(xiě)以及關(guān)閉操作。并且,所有發(fā)送的數(shù)據(jù)在另一端都會(huì)以相同的順序被接收。

  無(wú)連接的Socket操作就像一個(gè)郵件投遞,每一個(gè)數(shù)據(jù)報(bào)都是一個(gè)獨(dú)立的單元,它包含了這次投遞的所有信息(目的地址和要發(fā)送的內(nèi)容)。在這個(gè)模式下的Socket不需要連接目的地Socket,它只是簡(jiǎn)單的投出數(shù)據(jù)報(bào)。

  由此可見(jiàn),無(wú)連接的操作是快速高效的,但是數(shù)據(jù)安全性不佳;面向連接的操作效率較低,但數(shù)據(jù)的安全性較好。

  本文主要介紹的是面向連接的Socket操作。

1.2?Socket的構(gòu)造方法

  Java在包java.net中提供了兩個(gè)類Socket和ServerSocket,分別用來(lái)表示雙向連接的Socket客戶端和服務(wù)器端。

  Socket的構(gòu)造方法如下:

  (1)Socket(InetAddress?address,?int?port);

  (2)Socket(InetAddress?address,?int?port,?boolean?stream);

  (3)Socket(String?host,?int?port);

  (4)Socket(String?host,?int?port,?boolean?stream);

  (5)Socket(SocketImpl?impl);

  (6)Socket(String?host,?int?port,?InetAddress?localAddr,?int?localPort);

  (7)Socket(InetAddress?address,?int?port,?InetAddrss?localAddr,?int?localPort);

  ServerSocket的構(gòu)造方法如下:

  (1)ServerSocket(int?port);

  (2)ServerSocket(int?port,?int?backlog);

  (3)ServerSocket(int?port,?int?backlog,?InetAddress?bindAddr);

  其中,參數(shù)address、host和port分別是雙向連接中另一方的IP地址、主機(jī)名和端口號(hào);參數(shù)stream表示Socket是流Socket還是數(shù)據(jù)報(bào)Socket;參數(shù)localAddr和localPort表示本地主機(jī)的IP地址和端口號(hào);SocketImpl是Socket的父類,既可以用來(lái)創(chuàng)建ServerSocket,也可以用來(lái)創(chuàng)建Socket。

  如下的代碼在服務(wù)器端創(chuàng)建了一個(gè)ServerSocket:

1   try { 2   ServerSocket serverSocket = new ServerSocket(50000); //創(chuàng)建一個(gè)ServerSocket,用于監(jiān)聽(tīng)客戶端Socket的連接請(qǐng)求 3    while(true) { 4    Socket socket = serverSocket.accept();     //每當(dāng)接收到客戶端的Socket請(qǐng)求,服務(wù)器端也相應(yīng)的創(chuàng)建一個(gè)Socket 5       //todo開(kāi)始進(jìn)行Socket通信 6    } 7   }catch (IOException e) { 8    e.printStackTrace(); 9   }

  其中,50000是我們自己選擇的用來(lái)進(jìn)行Socket通信的端口號(hào),在創(chuàng)建Socket時(shí),如果該端口號(hào)已經(jīng)被別的服務(wù)占用,將會(huì)拋出異常。

  通過(guò)以上的代碼,我們創(chuàng)建了一個(gè)ServerSocket在端口50000監(jiān)聽(tīng)客戶端的請(qǐng)求。accept()是一個(gè)阻塞函數(shù),就是說(shuō)該方法被調(diào)用后就會(huì)一直等待客戶端的請(qǐng)求,直到有一個(gè)客戶端啟動(dòng)并請(qǐng)求連接到相同的端口,然后accept()返回一個(gè)對(duì)應(yīng)于該客戶端的Socket。

  那么,如何在客戶端創(chuàng)建并啟動(dòng)一個(gè)Socket呢?

1   try { 2    socket = new Socket("192.168.1.101", 50000); //192.168.1.101是服務(wù)器的IP地址,50000是端口號(hào) 3     //todo開(kāi)始進(jìn)行Socket通信 4   } catch (IOException e) { 5    e.printStackTrace(); 6 }

?  至此,客戶端和服務(wù)器端都建立了用于通信的Socket,接下來(lái)就可以由各自的Socket分別打開(kāi)各自的輸入流和輸出流進(jìn)行通信了。

1.3輸入流和輸出流

  Socket提供了方法getInputStream()和getOutPutStream()來(lái)獲得對(duì)應(yīng)的輸入流和輸出流,以便對(duì)Socket進(jìn)行讀寫(xiě)操作,這兩個(gè)方法的返回值分別是InputStream和OutPutStream對(duì)象。

  為了便于讀寫(xiě)數(shù)據(jù),我們可以在返回的輸入輸出流對(duì)象上建立過(guò)濾流,如PrintStream、InputStreamReader和OutputStreamWriter等。

1.4關(guān)閉Socket

  可以通過(guò)調(diào)用Socket的close()方法來(lái)關(guān)閉Socket。在關(guān)閉Socket之前,應(yīng)該先關(guān)閉與Socket有關(guān)的所有輸入輸出流,然后再關(guān)閉Socket。

?

2.簡(jiǎn)易聊天室

  下面就來(lái)說(shuō)說(shuō)如何通過(guò)Socket編程實(shí)現(xiàn)一個(gè)簡(jiǎn)易聊天室。客戶端完成后的運(yùn)行效果如圖1所示。

  圖1?運(yùn)行效果

  在該客戶端的界面中,使用了一個(gè)TextView控件來(lái)顯示聊天記錄。為了方便查看,將兩個(gè)用戶也放到了一個(gè)界面中,實(shí)際上應(yīng)該啟動(dòng)兩個(gè)模擬器,分別作為兩個(gè)用戶的客戶端,此處是為了方便操作才這么做的。

2.1服務(wù)器端ServerSocket的實(shí)現(xiàn)

  在該實(shí)例中,我們?cè)贛yEclipse中新建了一個(gè)Java工程作為服務(wù)器端。在該Java工程中,我們應(yīng)該完成以下的操作。

  (1)指定端口實(shí)例化一個(gè)ServerSocket,并調(diào)用ServerSocket的accept()方法在等待客戶端連接期間造成阻塞。

  (2)每當(dāng)接收到客戶端的Socket請(qǐng)求時(shí),服務(wù)器端也相應(yīng)的創(chuàng)建一個(gè)Socket,并將該Socket存入ArrayList中。與此同時(shí),啟動(dòng)一個(gè)ServerThread線程來(lái)為該客戶端Socket服務(wù)。

  以上兩步操作,可以通過(guò)以下的代碼來(lái)實(shí)現(xiàn):

1   /* 2    * Class : MyServer類,用于監(jiān)聽(tīng)客戶端Socket連接請(qǐng)求 3    * Author : 博客園-依舊淡然 4    */ 5   public class MyServer { 6    7    //定義ServerSocket的端口號(hào) 8    private static final int SOCKET_PORT = 50000; 9    //使用ArrayList存儲(chǔ)所有的Socket 10    public static ArrayList<Socket> socketList = new ArrayList<Socket>(); 11    12    public void initMyServer() { 13    try { 14         //創(chuàng)建一個(gè)ServerSocket,用于監(jiān)聽(tīng)客戶端Socket的連接請(qǐng)求 15    ServerSocket serverSocket = new ServerSocket(SOCKET_PORT); 16    while(true) { 17    //每當(dāng)接收到客戶端的Socket請(qǐng)求,服務(wù)器端也相應(yīng)的創(chuàng)建一個(gè)Socket 18    Socket socket = serverSocket.accept(); 19    socketList.add(socket); 20    //每連接一個(gè)客戶端,啟動(dòng)一個(gè)ServerThread線程為該客戶端服務(wù) 21    new Thread(new ServerThread(socket)).start(); 22    } 23    }catch (IOException e) { 24    e.printStackTrace(); 25    } 26    } 27    28    public static void main(String[] args) { 29    MyServer myServer = new MyServer(); 30    myServer.initMyServer(); 31    } 32   }

?  (3)在啟動(dòng)的ServerThread線程中,我們需要將讀到的客戶端內(nèi)容(也就是某一個(gè)客戶端Socket發(fā)送給服務(wù)器端的數(shù)據(jù)),發(fā)送給其他的所有客戶端Socket,實(shí)現(xiàn)信息的廣播。ServerThread類的具體實(shí)現(xiàn)如下:

1   public class ServerThread implements Runnable { 2    3    //定義當(dāng)前線程所處理的Socket 4    private Socket socket = null; 5    //該線程所處理的Socket對(duì)應(yīng)的輸入流 6    private BufferedReader bufferedReader = null; 7    8    /* 9    * Function : ServerThread的構(gòu)造方法 10    * Author : 博客園-依舊淡然 11    */ 12    public ServerThread(Socket socket) throws IOException { 13    this.socket = socket; 14    //獲取該socket對(duì)應(yīng)的輸入流 15    bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 16    } 17    18    /* 19    * Function : 實(shí)現(xiàn)run()方法,將讀到的客戶端內(nèi)容進(jìn)行廣播 20    * Author : 博客園-依舊淡然 21    */ 22    public void run() { 23    try { 24    String content = null; 25    //采用循環(huán)不斷地從Socket中讀取客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù) 26    while((content = bufferedReader.readLine()) != null) { 27    //將讀到的內(nèi)容向每個(gè)Socket發(fā)送一次 28    for(Socket socket : MyServer.socketList) { 29    //獲取該socket對(duì)應(yīng)的輸出流 30    PrintStream printStream = new PrintStream(socket.getOutputStream()); 31    //向該輸出流中寫(xiě)入要廣播的內(nèi)容 32    printStream.println(packMessage(content)); 33    34    } 35    } 36    } catch(IOException e) { 37    e.printStackTrace(); 38    } 39    } 40    41    /* 42    * Function : 對(duì)要廣播的數(shù)據(jù)進(jìn)行包裝 43    * Author : 博客園-依舊淡然 44    */ 45    private String packMessage(String content) { 46    String result = null; 47    SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); //設(shè)置日期格式 48    if(content.startsWith("USER_ONE")) { 49    String message = content.substring(8); //獲取用戶發(fā)送的真實(shí)的信息 50    result = "\n" + "往事如風(fēng) " + df.format(new Date()) + "\n" + message; 51    } 52    if(content.startsWith("USER_TWO")) { 53    String message = content.substring(8); //獲取用戶發(fā)送的真實(shí)的信息 54    result = "\n" + "依舊淡然 " + df.format(new Date()) + "\n" + message; 55    } 56    return result; 57    } 58    59   }

?  其中,在packMessage()方法中,我們對(duì)要廣播的數(shù)據(jù)進(jìn)行了包裝。因?yàn)橐直娉龇?wù)器接收到的消息是來(lái)自哪一個(gè)客戶端Socket的,我們對(duì)客戶端Socket發(fā)送的消息也進(jìn)行了包裝,方法是在消息的頭部加上"USER_ONE"來(lái)代表用戶"往事如風(fēng)",在消息的頭部加上"USER_TWO"來(lái)代表用戶"依舊淡然"。?

  至此,服務(wù)器端的ServerSocket便算是創(chuàng)建好了。

2.2客戶端Socket的實(shí)現(xiàn)

  接下來(lái),我們便可以在Android工程中,分別為用戶"往事如風(fēng)"和"依舊淡然"創(chuàng)建一個(gè)客戶端Socket,并啟動(dòng)一個(gè)客戶端線程ClientThread來(lái)監(jiān)聽(tīng)服務(wù)器發(fā)來(lái)的數(shù)據(jù)。

  這一過(guò)程的具體實(shí)現(xiàn)如下:

1 /* 2 * Function : 初始化Socket 3 * Author : 博客園-依舊淡然 4 */ 5 private void initSocket() { 6 try { 7 socketUser1 = new Socket(URL_PATH, SOCKET_PORT); //用戶1的客戶端Socket 8 socketUser2 = new Socket(URL_PATH, SOCKET_PORT); //用戶2的客戶端Socket 9 clientThread = new ClientThread(); //客戶端啟動(dòng)ClientThread線程,讀取來(lái)自服務(wù)器的數(shù)據(jù) 10 clientThread.start(); 11 } catch (IOException e) { 12 e.printStackTrace(); 13 } 14 }

?  ClientThread的具體實(shí)現(xiàn)和服務(wù)器端的ServerThread線程相似,唯一的區(qū)別是,在ClientThread線程中接收到服務(wù)器端發(fā)來(lái)的數(shù)據(jù)后,我們不可以直接在ClientThread線程中進(jìn)行刷新UI的操作,而是應(yīng)該將數(shù)據(jù)封裝到Message中,再調(diào)用MyHandler對(duì)象的sendMessage()方法將Message發(fā)送出去。這一過(guò)程的具體實(shí)現(xiàn)如下:

1 /* 2 * Function : run()方法,用于讀取來(lái)自服務(wù)器的數(shù)據(jù) 3 * Author : 博客園-依舊淡然 4 */ 5   public void run() { 6   try { 7    String content = null; 8    while((content = bufferedReader .readLine()) != null) { 9    Bundle bundle = new Bundle(); 10    bundle.putString(KEY_CONTENT, content); 11    Message msg = new Message(); 12    msg.setData(bundle); //將數(shù)據(jù)封裝到Message對(duì)象中 13    myHandler.sendMessage(msg); 14    } 15    } catch (Exception e) { 16    e.printStackTrace(); 17    } 18   }

?  最后,我們?cè)赨I主線程中創(chuàng)建一個(gè)內(nèi)部類MyHandler,讓它繼承Handler類,并實(shí)現(xiàn)handleMessage()方法,用來(lái)接收Message消息并處理(刷新UI)。MyContent是一個(gè)用來(lái)保存聊天記錄的類,提供了get和set接口,其中,set接口設(shè)置的本條聊天記錄,而get接口獲得的是全部的聊天記錄。具體的實(shí)現(xiàn)如下:

1 /* 2 * Class : 內(nèi)部類MyHandler,用于接收消息并處理 3 * Author : 博客園-依舊淡然 4 */ 5 private class MyHandler extends Handler { 6 public void handleMessage(Message msg) { 7 Bundle bundle = msg.getData(); //獲取Message中發(fā)送過(guò)來(lái)的數(shù)據(jù) 8 String content = bundle.getString(KEY_CONTENT); 9 MyContent.setContent(content); //保存聊天記錄 10 mTextView.setText(MyContent.getContent()); 11 } 12 }

?  至此,客戶端的Socket也編寫(xiě)完成了。

轉(zhuǎn)載于:https://www.cnblogs.com/berylqliu/p/6261505.html

總結(jié)

以上是生活随笔為你收集整理的Socket编程实现简易聊天室的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。