即时通讯基础
即時通訊系列閱讀
1. 即時通訊簡介
即時通訊(Instant Messaging)是目前Internet 上最為流行的通訊方式,各種各樣的即時通訊軟件也層出不窮;服務提供商也提供了越來越豐富的通訊服務功能。不容置疑,Internet 已經成為真正的信息高速公路。從實際工程應用角度出發,以計算機網絡原理為指導,結合當前網絡中的一些常用技術,編程實現基于C/S 架構的網絡聊天工具是切實可行的。
目前,中國市場上的企業級即時通信工具主要包括:信鴿、視高科技的視高可視協同辦公平臺、263EM、群英CC2010、通軟聯合的GoCom、騰訊公司的RTX、IBM 的Lotus Sametime、點擊科技的GKE、中國互聯網辦公室的imo、中國移動的企業飛信、華夏易聯的e-Link、擎旗的UcStar 等。相對于個人即時通信工具而言,企業級即時通信工具更加強調安全性、實用性、穩定性和擴展性。
1.1 即時聊天的解決方案
- socket:套接字,連接需要ip和端口,分為tcp和udp兩種形式
- xmpp:xmpp + openfire + asmack
1.2 常見協議
1.3 常見的術語
- xmpp:基于xml的可拓展協議.
- jabber:xmpp的前身.
- openfire:支持xmpp的開源服務器
- smack.jar:對xmpp協議封裝.方便開發的jar包.
- spark.exe:基于xmpp的pc客戶端;
- asmack.jar:smack.jar的精簡版.專門針對android端開發
2. 基本概念和原理
2.1 常用的網絡通信協議
TCP/IP:Transmission Control Protocol/Internet Protocol 的簡寫,中譯名為傳輸控制協議/因特網互聯協議,又名網絡通訊協議,是Internet 最基本的協議、Internet 國際互聯網絡的基礎,由網絡層的IP 協議和傳輸層的TCP協議組成。TCP/IP 定義了電子設備如何連入因特網,以及數據如何在它們之間傳輸的標準。協議采用了4 層的層級結構,每一層都呼叫它的下一層所提供的協議來完成自己的需求。通俗而言:TCP 負責發現傳輸的問題,一有問題就發出信號,要求重新傳輸,直到所有數據安全正確地傳輸到目的地。而IP 是給因特網的每一臺聯網設備規定一個地址。
UDP:UDP 協議全稱是用戶數據報協議,在網絡中它與TCP 協議一樣用于處理數據包,是一種無連接的協議。在OSI 模型中,在第四層——傳輸層,處于IP 協議的上一層。UDP 有不提供數據包分組、組裝和不能對數據包進行排序的缺點,也就是說,當報文發送之后,是無法得知其是否安全完整到達的。UDP 用來支持那些需要在計算機之間傳輸數據的網絡應用。包括網絡視頻會議系統在內的眾多的客戶/服務器模式的網絡應用都需要使用UDP協議。UDP 協議從問世至今已經被使用了很多年,雖然其最初的光彩已經被一些類似協議所掩蓋,但是即使是在今天UDP 仍然不失為一項非常實用和可行的網絡傳輸層協議。
TCP/IP 協議棧主要分為四層:應用層、傳輸層、網絡層、數據鏈路層,每層都有相應的協議,如下圖:
所謂的協議就是雙方進行數據傳輸的一種格式。
2.2 TCP、UDP 特點對比
TCP 協議是面向連接、保證高可靠性(數據無丟失、數據無失序、數據無錯誤、數據無重復到達)傳輸層協議。UDP 協議也是傳輸層協議,它是無連接,不保證可靠的傳輸層協議。
2.3 TCP 三次握手過程
1、請求端(通常稱為客戶)發送一個SYN 段指明客戶打算連接的服務器的端口,以及初始序號(ISN)
2、服務器發回包含服務器的初始序號的SYN 報文段(報文段2)作為應答。同時,將確認序號設置為客戶的ISN加1 以對客戶的SYN 報文段進行確認。
| 面向連接 | 面向非連接 |
| 可靠的連接 | 不可靠的連接 |
| 速度慢 | 速度快 |
| 大文件、重要的數據等 | 適合小數據、不重要 |
3、客戶必須將確認序號設置為服務器的ISN 加1 以對服務器的SYN 報文段進行確認(報文段3)這三個報文段完成連接的建立。這個過程也稱為三次握手(three-way handshake)。
上面的過程如下圖所示:
2.4 即時通訊形式
直接通訊
兩個不同客戶端之間不經過服務器,直接通過網絡進行數據的交互。常用的p2p 技術就是直接通訊的形式。
在線代理通訊
一個客戶端發送的消息先發送到服務器,服務器接收到消息后再發送給指定的另外一個客戶端。QQ 的消息
尤其是離線消息就是同在線代理的方式實現的。
離線代理通訊
一個客戶端發送消息給服務器,服務器存儲在數據庫中個,當另外一個客戶端上線后在發送過去。
離線擴展通訊
一個客戶端發送消息給服務器,服務器通過郵件、短信等其他形式將消息發送給接收者。
3. ServerSocket 和Socket
3.1 使用Java 完成簡單的Socket 通信
在Java 中Socket 可以理解為客戶端或者服務器端的一個特殊的對象,這個對象有兩個關鍵的方法,一個是getInputStream 方法,另一個是getOutputStream 方法。getInputStream 方法可以得到一個輸入流,客戶端的Socket對象上的getInputStream 方法得到的輸入流其實就是從服務器端發回的數據流。GetOutputStream 方法得到一個輸出流,客戶端Socket 對象上的getOutputStream 方法返回的輸出流就是將要發送到服務器端的數據流,(其實是一個緩沖區,暫時存儲將要發送過去的數據)。
下面就讓我們寫一個簡單的Demo 來演示Socket 是如何使用的。
建立服務器類
服務類使用到的核心類的是ServerSocket。這里我們只需要建立一個Java Project 即可。
public class IMServer {private static ServerSocket serverSocket;private static BufferedReader reader;public static void main(String[] args) {try {serverSocket = new ServerSocket(7788);/*** 等待接收客戶端連接進來,該方法是線程阻塞的*/Socket accept = serverSocket.accept();/*** 獲取輸入流,用于接收客戶端發來的數據*/InputStream inputStream = accept.getInputStream();/*** 將字節輸入流轉化為字符輸出流*/reader = new BufferedReader(new InputStreamReader(inputStream));/*** 打印數據*/String tmp = null;while ((tmp = reader.readLine()) != null) {System.out.println(tmp);}} catch (Exception e) {e.printStackTrace();} finally {try {if (serverSocket != null) {serverSocket.close();}} catch (IOException e) {e.printStackTrace();}if (reader != null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}}}建立客戶端類
public class IMClient {private static Socket socket;private static BufferedWriter writer;/*** @param args*/public static void main(String[] args) {try {socket = new Socket("127.0.0.1", 7788);/*** 獲取輸出流*/OutputStream outputStream = socket.getOutputStream();writer = new BufferedWriter(new OutputStreamWriter(outputStream));writer.write("hello wo shi wzy!" + new Date().getTime());writer.close();} catch (IOException e) {e.printStackTrace();} finally {if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}if (writer != null) {try {writer.close();} catch (IOException e) {e.printStackTrace();}}}}}在上面的代碼中我們僅僅實現了一個最簡單的服務器和客戶端,服務器啟動起來后只能接受到一次消息,然后就關閉了。如果想讓服務器一直運行,應該通過死循環來處理不同的發送進來的消息。
Socket調試工具
TCP/UDP Socket調試工具提供了TCP Server,TCP Client,UDP Server,UDP Client,UDP Group 五種Socket調試方案
TCP
手機作為Client,PC作為Server
public class MyClientActivity extends Activity {private EditText mEditText = null;private Button connectButton = null;private Button sendButton = null;private TextView mTextView = null;private Socket clientSocket = null;private OutputStream outStream = null;private Handler mHandler = null;private ReceiveThread mReceiveThread = null;private boolean stop = true;/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);mEditText = (EditText) this.findViewById(R.id.edittext);mTextView = (TextView) this.findViewById(R.id.retextview);connectButton = (Button) this.findViewById(R.id.connectbutton);sendButton = (Button) this.findViewById(R.id.sendbutton);sendButton.setEnabled(false);// 連接按鈕監聽connectButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubtry {// 實例化對象并連接到服務器clientSocket = new Socket("172.27.35.1", 60000);} catch (UnknownHostException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}displayToast("連接成功!");// 連接按鈕使能connectButton.setEnabled(false);// 發送按鈕使能sendButton.setEnabled(true);mReceiveThread = new ReceiveThread(clientSocket);stop = false;// 開啟線程mReceiveThread.start();}});// 發送數據按鈕監聽sendButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubbyte[] msgBuffer = null;// 獲得EditTex的內容String text = mEditText.getText().toString();try {// 字符編碼轉換msgBuffer = text.getBytes("GB2312");} catch (UnsupportedEncodingException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}try {// 獲得Socket的輸出流outStream = clientSocket.getOutputStream();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}try {// 發送數據outStream.write(msgBuffer);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 清空內容mEditText.setText("");displayToast("發送成功!");}});// 消息處理mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// 顯示接收到的內容mTextView.setText((msg.obj).toString());}};}// 顯示Toast函數private void displayToast(String s) {Toast.makeText(this, s, Toast.LENGTH_SHORT).show();}private class ReceiveThread extends Thread {private InputStream inStream = null;private byte[] buf;private String str = null;ReceiveThread(Socket s) {try {// 獲得輸入流this.inStream = s.getInputStream();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}@Overridepublic void run() {while (!stop) {this.buf = new byte[512];try {// 讀取輸入數據(阻塞)this.inStream.read(this.buf);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 字符編碼轉換try {this.str = new String(this.buf, "GB2312").trim();} catch (UnsupportedEncodingException e) {// TODO Auto-generated catch blocke.printStackTrace();}Message msg = new Message();msg.obj = this.str;// 發送消息mHandler.sendMessage(msg);}}}@Overridepublic void onDestroy() {super.onDestroy();if (mReceiveThread != null) {stop = true;mReceiveThread.interrupt();}}}手機作為Server,PC作為Client
public class MyServerActivity extends Activity {private TextView ipTextView = null;private EditText mEditText = null;private Button sendButton = null;private TextView mTextView = null;private OutputStream outStream = null;private Socket clientSocket = null;private ServerSocket mServerSocket = null;private Handler mHandler = null;private AcceptThread mAcceptThread = null;private ReceiveThread mReceiveThread = null;private boolean stop = true;/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);ipTextView = (TextView) this.findViewById(R.id.iptextview);mEditText = (EditText) this.findViewById(R.id.sedittext);sendButton = (Button) this.findViewById(R.id.sendbutton);sendButton.setEnabled(false);mTextView = (TextView) this.findViewById(R.id.textview);// 發送數據按鈕監聽sendButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubbyte[] msgBuffer = null;// 獲得EditTex的內容String text = mEditText.getText().toString();try {// 字符編碼轉換msgBuffer = text.getBytes("GB2312");} catch (UnsupportedEncodingException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}try {// 獲得Socket的輸出流outStream = clientSocket.getOutputStream();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}try {// 發送數據outStream.write(msgBuffer);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 清空內容mEditText.setText("");displayToast("發送成功!");}});// 消息處理mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 0: {// 顯示客戶端IPipTextView.setText((msg.obj).toString());// 使能發送按鈕sendButton.setEnabled(true);break;}case 1: {// 顯示接收到的數據mTextView.setText((msg.obj).toString());break;}}}};mAcceptThread = new AcceptThread();// 開啟監聽線程mAcceptThread.start();}// 顯示Toast函數private void displayToast(String s) {Toast.makeText(this, s, Toast.LENGTH_SHORT).show();}private class AcceptThread extends Thread {@Overridepublic void run() {try {// 實例化ServerSocket對象并設置端口號為7100mServerSocket = new ServerSocket(60000);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}try {// 等待客戶端的連接(阻塞)clientSocket = mServerSocket.accept();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}mReceiveThread = new ReceiveThread(clientSocket);stop = false;// 開啟接收線程mReceiveThread.start();Message msg = new Message();msg.what = 0;// 獲取客戶端IPmsg.obj = clientSocket.getInetAddress().getHostAddress();// 發送消息mHandler.sendMessage(msg);}}private class ReceiveThread extends Thread {private InputStream mInputStream = null;private byte[] buf;private String str = null;ReceiveThread(Socket s) {try {// 獲得輸入流this.mInputStream = s.getInputStream();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}@Overridepublic void run() {while (!stop) {this.buf = new byte[512];// 讀取輸入的數據(阻塞讀)try {this.mInputStream.read(buf);} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}// 字符編碼轉換try {this.str = new String(this.buf, "GB2312").trim();} catch (UnsupportedEncodingException e) {// TODO Auto-generated catch blocke.printStackTrace();}Message msg = new Message();msg.what = 1;msg.obj = this.str;// 發送消息mHandler.sendMessage(msg);}}}@Overridepublic void onDestroy() {super.onDestroy();if (mReceiveThread != null) {stop = true;mReceiveThread.interrupt();}}}UDP
public class MainActivity extends Activity {private static String TAG = "CallActivity";private Handler handler = new Handler() {public void handleMessage(android.os.Message msg) {// 播放聲音initBeepSound();playBeepSoundAndVibrate();Log.i(TAG, "reciever_msg");String result = (String) msg.obj;System.out.println(result);tv_result.setText(result);};};private String ip;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODOsuper.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ip = IpUtil.getIp(this);Toast.makeText(this, ip, Toast.LENGTH_LONG).show();System.out.println("ip:" + ip);tv_result = (TextView) findViewById(R.id.tv_result);}/*** 發送者*/public void send(View v) {new Thread() {@Overridepublic void run() {try {//1. 創建一個DatagramSocket對象DatagramSocket socket = new DatagramSocket(5678);//2. 創建一個 InetAddress , 相當于是地址,就是想要發送的ip地址InetAddress serverAddress = InetAddress.getByName("172.27.35.1");//3. 這是隨意發送一個數據String str = "來自android手機的問候";//4. 轉為byte類型byte data[] = str.getBytes("GBK");//5. 創建一個DatagramPacket 對象,并指定要講這個數據包發送到網絡當中的哪個地址,以及端口號DatagramPacket pack = new DatagramPacket(data, data.length, serverAddress, 5678);//6. 調用DatagramSocket對象的send方法 發送數據socket.send(pack);} catch (SocketException e) {Log.i(TAG, "error");e.printStackTrace();} catch (UnknownHostException e) {Log.i(TAG, "error");e.printStackTrace();} catch (IOException e) {Log.i(TAG, "error");e.printStackTrace();}}}.start();}/*** 接收者* @param v*/public void receive(View v) {new Thread() {@Overridepublic void run() {// 執行完畢后給handler發送一個空消息try {// 1. 創建一個DatagramSocket對象,并指定監聽的端口號/\DatagramSocket socket = new DatagramSocket(5678);// 2. 創建一個byte數組用于接收byte data[] = new byte[1024];// 3. 創建一個空的DatagramPackage對象DatagramPacket pack = new DatagramPacket(data, data.length);// 4. 使用receive方法接收發送方所發送的數據,同時這也是一個阻塞的方法while (true) {Log.i(TAG, "reciever_1");socket.receive(pack);Log.i(TAG, "reciever_2");// 5. 得到發送過來的數據// String result = new String(pack.getData(), pack.getOffset(), pack.getLength(),"GBK");String result = new String(pack.getData(), pack.getOffset(), pack.getLength());Message msg = new Message();msg.obj = result;handler.sendMessage(msg);Log.i(TAG, "sendmsg_1");}} catch (UnknownHostException e) {// TODO Auto-generated catch blocke.printStackTrace();Log.i(TAG, "error");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();Log.i(TAG, "error");}}}.start();}@Overrideprotected void onDestroy() {super.onDestroy();}// 播放聲音private static final float BEEP_VOLUME = 0.10f;private MediaPlayer mediaPlayer;private void initBeepSound() {if (mediaPlayer == null) {setVolumeControlStream(AudioManager.STREAM_MUSIC);mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setOnCompletionListener(beepListener);AssetFileDescriptor file = getResources().openRawResourceFd(R.raw.beep);try {mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength());file.close();mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);mediaPlayer.prepare();} catch (IOException e) {mediaPlayer = null;}}}private void playBeepSoundAndVibrate() {if (mediaPlayer != null) {mediaPlayer.start();}// 震動Vibrator mVibrator = (Vibrator) getApplication().getSystemService(Service.VIBRATOR_SERVICE);mVibrator.vibrate(2000);long[] pattern = { 0, 100, 200, 100, 200 };mVibrator.vibrate(pattern, -1);}private final OnCompletionListener beepListener = new OnCompletionListener() {public void onCompletion(MediaPlayer mediaPlayer) {mediaPlayer.seekTo(0);}};// 退出提醒private long exitTime;private TextView tv_result;@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {if ((System.currentTimeMillis() - exitTime) > 2000) {Toast.makeText(getApplicationContext(), "再按一次退出" + getResources().getString(R.string.app_name),Toast.LENGTH_SHORT).show();exitTime = System.currentTimeMillis();} else {finish();}return true;}return super.onKeyDown(keyCode, event);} }IpUtil.java
public class IpUtil {/*** 獲取手機ip* * @return*/public static String getLocalIpAddress() {try {for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {NetworkInterface intf = en.nextElement();for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {InetAddress inetAddress = enumIpAddr.nextElement();if (!inetAddress.isLoopbackAddress()) {String ip = inetAddress.getHostAddress().toString();System.out.println("getLocalIpAddressIP:"+ip);return ip;}}}} catch (SocketException ex) {Log.e("ifo", ex.toString());}return "";}public static String getIp(Activity activity) {WifiManager wifiManager = (WifiManager) activity.getSystemService(Context.WIFI_SERVICE);WifiInfo wifiInfo = wifiManager.getConnectionInfo();int ipAddress = wifiInfo.getIpAddress();// 格式化IP address,例如:格式化前:1828825280,格式化后:192.168.1.109String ip = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff), (ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));System.out.println("getIpIP:"+ip);return ip;}}總結
- 上一篇: 揭秘Java网络爬虫程序原理
- 下一篇: 即时通讯:XMPP基础