JAVA通信编程(三)——TCP通讯
?
歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/network/java-tcp-comm/
繼上一篇小插曲之后繼續(xù)回到正題,本篇講述的是java的TCP通訊。TCP編程分為server端和client端,一般在網(wǎng)上都能搜到相關(guān)的例子,為了方便大家,我這里先整理下server端和client端的應(yīng)用案例,然后再根據(jù)在本系列中第一篇串口通訊中的結(jié)構(gòu)一樣實(shí)現(xiàn)CommBuff接口。
java tcp socket編程server端:
?
import java.io.*;import java.net.*;import java.applet.Applet;public class TalkServer{public static void main(String args[]) {try{ServerSocket server=null;try{server=new ServerSocket(4700);//創(chuàng)建一個(gè)ServerSocket在端口4700監(jiān)聽客戶請求}catch(Exception e) {System.out.println("can not listen to:"+e);//出錯(cuò),打印出錯(cuò)信息}Socket socket=null;try{socket=server.accept();//使用accept()阻塞等待客戶請求,有客戶//請求到來則產(chǎn)生一個(gè)Socket對象,并繼續(xù)執(zhí)行}catch(Exception e) {System.out.println("Error."+e);//出錯(cuò),打印出錯(cuò)信息}String line;BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));//由Socket對象得到輸入流,并構(gòu)造相應(yīng)的BufferedReader對象PrintWriter os=newPrintWriter(socket.getOutputStream());//由Socket對象得到輸出流,并構(gòu)造PrintWriter對象BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));//由系統(tǒng)標(biāo)準(zhǔn)輸入設(shè)備構(gòu)造BufferedReader對象System.out.println("Client:"+is.readLine());//在標(biāo)準(zhǔn)輸出上打印從客戶端讀入的字符串line=sin.readLine();//從標(biāo)準(zhǔn)輸入讀入一字符串while(!line.equals("bye")){//如果該字符串為 "bye",則停止循環(huán)os.println(line);//向客戶端輸出該字符串os.flush();//刷新輸出流,使Client馬上收到該字符串System.out.println("Server:"+line);//在系統(tǒng)標(biāo)準(zhǔn)輸出上打印讀入的字符串System.out.println("Client:"+is.readLine());//從Client讀入一字符串,并打印到標(biāo)準(zhǔn)輸出上line=sin.readLine();//從系統(tǒng)標(biāo)準(zhǔn)輸入讀入一字符串} //繼續(xù)循環(huán)os.close(); //關(guān)閉Socket輸出流is.close(); //關(guān)閉Socket輸入流socket.close(); //關(guān)閉Socketserver.close(); //關(guān)閉ServerSocket}catch(Exception e){System.out.println("Error:"+e);//出錯(cuò),打印出錯(cuò)信息}}}java tcp socket編程client端:
import java.io.*;import java.net.*;public class TalkClient {public static void main(String args[]) {try{Socket socket=new Socket("127.0.0.1",4700);//向本機(jī)的4700端口發(fā)出客戶請求BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));//由系統(tǒng)標(biāo)準(zhǔn)輸入設(shè)備構(gòu)造BufferedReader對象PrintWriter os=new PrintWriter(socket.getOutputStream());//由Socket對象得到輸出流,并構(gòu)造PrintWriter對象BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));//由Socket對象得到輸入流,并構(gòu)造相應(yīng)的BufferedReader對象String readline;readline=sin.readLine(); //從系統(tǒng)標(biāo)準(zhǔn)輸入讀入一字符串while(!readline.equals("bye")){//若從標(biāo)準(zhǔn)輸入讀入的字符串為 "bye"則停止循環(huán)os.println(readline);//將從系統(tǒng)標(biāo)準(zhǔn)輸入讀入的字符串輸出到Serveros.flush();//刷新輸出流,使Server馬上收到該字符串System.out.println("Client:"+readline);//在系統(tǒng)標(biāo)準(zhǔn)輸出上打印讀入的字符串System.out.println("Server:"+is.readLine());//從Server讀入一字符串,并打印到標(biāo)準(zhǔn)輸出上readline=sin.readLine(); //從系統(tǒng)標(biāo)準(zhǔn)輸入讀入一字符串} //繼續(xù)循環(huán)os.close(); //關(guān)閉Socket輸出流is.close(); //關(guān)閉Socket輸入流socket.close(); //關(guān)閉Socket}catch(Exception e) {System.out.println("Error"+e); //出錯(cuò),則打印出錯(cuò)信息}} }通過上面兩個(gè)實(shí)例可以大概的了解到j(luò)ava tcp編程的流程。
這里有必要補(bǔ)充下Socket的相關(guān)概念:
?
1. 什么是Socket
?
網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通訊連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)雙向鏈路的一端稱為一個(gè)Socket。Socket通常用來實(shí)現(xiàn)客戶方和服務(wù)方的連接。Socket是TCP/IP協(xié)議的一個(gè)十分流行的編程界面,一個(gè)Socket由一個(gè)IP地址和一個(gè)端口號唯一確定。但是,Socket所支持的協(xié)議種類也不光TCP/IP一種,因此兩者之間是沒有必然聯(lián)系的。在Java環(huán)境下,Socket編程主要是指基于TCP/IP協(xié)議的網(wǎng)絡(luò)編程。
2. Socket通訊的過程
?
Server端Listen(監(jiān)聽)某個(gè)端口是否有連接請求,Client端向Server?端發(fā)出Connect(連接)請求,Server端向Client端發(fā)回Accept(接受)消息。一個(gè)連接就建立起來了。Server端和Client?端都可以通過Send,Write等方法與對方通信。
對于一個(gè)功能齊全的Socket,都要包含以下基本結(jié)構(gòu),其工作過程包含以下四個(gè)基本的步驟:
(1)?創(chuàng)建Socket;
(2)?打開連接到Socket的輸入/出流;
(3)?按照一定的協(xié)議對Socket進(jìn)行讀/寫操作;
(4)?關(guān)閉Socket.(在實(shí)際應(yīng)用中,并未使用到顯示的close,雖然很多文章都推薦如此,不過在我的程序中,可能因?yàn)槌绦虮旧肀容^簡單,要求不高,所以并未造成什么影響。)
3. 創(chuàng)建Socket
?
java在包java.net中提供了兩個(gè)類Socket和ServerSocket,分別用來表示雙向連接的客戶端和服務(wù)端。這是兩個(gè)封裝得非常好的類,使用很方便。其構(gòu)造方法如下:
Socket(InetAddress address, int port);
Socket(InetAddress address, int port, boolean stream);
Socket(String host, int prot);
Socket(String host, int prot, boolean stream);
Socket(SocketImpl impl)
Socket(String host, int port, InetAddress localAddr, int localPort)
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
ServerSocket(int port);
ServerSocket(int port, int backlog);
ServerSocket(int port, int backlog, InetAddress bindAddr)
其中address、host和port分別是雙向連接中另一方的IP地址、主機(jī)名和端?口號,stream指明socket是流socket還是數(shù)據(jù)報(bào)socket,localPort表示本地主機(jī)的端口號,localAddr和?bindAddr是本地機(jī)器的地址(ServerSocket的主機(jī)地址),impl是socket的父類,既可以用來創(chuàng)建serverSocket又可?以用來創(chuàng)建Socket。count則表示服務(wù)端所能支持的最大連接數(shù)。例如:學(xué)習(xí)視頻網(wǎng)?http://www.xxspw.com
Socket client = new Socket("127.0.01.", 80);
ServerSocket server = new ServerSocket(80);
注意,在選擇端口時(shí),必須小心。每一個(gè)端口提供一種特定的服務(wù),只有給出正確的端口,才?能獲得相應(yīng)的服務(wù)。0~1023的端口號為系統(tǒng)所保留,例如http服務(wù)的端口號為80,telnet服務(wù)的端口號為21,ftp服務(wù)的端口號為23,?所以我們在選擇端口號時(shí),最好選擇一個(gè)大于1023的數(shù)以防止發(fā)生沖突。
在創(chuàng)建socket時(shí)如果發(fā)生錯(cuò)誤,將產(chǎn)生IOException,在程序中必須對之作出處理。所以在創(chuàng)建Socket或ServerSocket是必須捕獲或拋出例外。
介紹了tcp socket編程的相關(guān)概念,可以回到正題了,下面所示是實(shí)現(xiàn)了CommBuff的服務(wù)端Socket程序(CommBuff接口可以參看點(diǎn)擊打開鏈接)
?
package com.zzh.comm;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Map;import org.apache.log4j.Logger;public class TcpServerImpl implements CommBuff {private Logger logger = Logger.getLogger(Object.class.getName());private int port;private ServerSocket server = null;private Socket socket = null;InputStream in = null;OutputStream out = null;private static byte[] recvBuff = new byte[4096];private static int recvLen = 0;private String fileName = "/tcp.properties";public TcpServerImpl(){Map<String,String> map = new ReadProperties().getPropertiesMap(fileName);try{port = Integer.parseInt(map.get("tcp_port"));}catch (Exception e){logger.error(e.getMessage());}}@Overridepublic synchronized byte[] readBuff(){if(in ==null){close();return new byte[0];}byte[] readBuffer = new byte[1024];try{while(in.available()>0){int numBytes = in.read(readBuffer);if(recvLen + numBytes > 4096){throw new RuntimeException("接收緩存數(shù)組內(nèi)容退出");}else{logger.info("網(wǎng)口接收:"+CommUtil.bytesToHexWithLen(readBuffer,numBytes));System.arraycopy(readBuffer, 0, recvBuff, recvLen, numBytes);recvLen = recvLen + numBytes;}}}catch (IOException e){logger.error(e.getMessage());}byte[] ans = new byte[0];if(recvLen>0){ans = new byte[recvLen];System.arraycopy(recvBuff,0,ans,0,recvLen);recvLen = 0;}return ans;}@Overridepublic synchronized void writeBuff(byte[] message){if(out ==null){close();return;}try{out.write(message);out.flush();logger.info("發(fā)送成功: "+CommUtil.bytesToHex(message));}catch (IOException e){logger.error(e.getMessage());}}@Overridepublic void open() {logger.info("Try to open tcpServer");try{server = new ServerSocket(port);}catch (IOException e){logger.error(e.getMessage());}try{socket = server.accept();}catch (IOException e){logger.error(e.getMessage());}logger.info("TcpServer正在監(jiān)聽....");try{in = socket.getInputStream();out = socket.getOutputStream();}catch (IOException e){logger.error(e.getMessage());}logger.info("成功開啟TCP Server");}@Overridepublic void close() {try{if(out != null){out.close();}if(in != null){in.close();}if(socket != null){socket.close();}if(server!=null){server.close();}}catch (IOException e){e.printStackTrace();}}@Overridepublic Object getInfo(){return socket;}}可以看到TcpServerImpl類實(shí)現(xiàn)了CommBuff中的方法,如果采用簡單工廠模式就可以寫出無關(guān)TCP或者串口通訊的底層程序,即上層應(yīng)用既可以通過TCP通訊也可以通過串口進(jìn)行通訊,至于底層采用什么具體的通訊方式是可配置化的,這樣進(jìn)一步提高了程序的靈活性。
?
下面展示的是TcpClientImpl類,這個(gè)采用的是TCP Client的方式實(shí)現(xiàn)CommBuff接口,其實(shí)本質(zhì)上和TcpServerImpl并無多大差別。
?
package com.zzh.comm;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.Socket; import java.net.UnknownHostException; import java.util.Map; import java.util.concurrent.TimeUnit;import org.apache.log4j.Logger;public class TcpClientImpl implements CommBuff {private Logger logger = Logger.getLogger(Object.class.getName());private int port;private String tcp_server_ip;private Socket socket = null;InputStream in = null;OutputStream out = null;private static byte[] recvBuff = new byte[4096];private static int recvLen = 0;private String fileName = "/tcp.properties";public TcpClientImpl(){Map<String,String> map = new ReadProperties().getPropertiesMap(fileName);try{port = Integer.parseInt(map.get("tcp_port"));tcp_server_ip = map.get("tcp_server_ip");}catch (Exception e){logger.error(e.getMessage());}}@Overridepublic synchronized byte[] readBuff(){if(in ==null){close();return new byte[0];}byte[] readBuffer = new byte[1024];try{while(in.available()>0){int numBytes = in.read(readBuffer);if(recvLen + numBytes > 4096){throw new RuntimeException("接收緩存數(shù)組內(nèi)容退出");}else{logger.info("網(wǎng)口接收:"+CommUtil.bytesToHexWithLen(readBuffer,numBytes));System.arraycopy(readBuffer, 0, recvBuff, recvLen, numBytes);recvLen = recvLen + numBytes;}}}catch (IOException e){logger.error(e.getMessage());}byte[] ans = new byte[0];if(recvLen>0){ans = new byte[recvLen];System.arraycopy(recvBuff,0,ans,0,recvLen);recvLen = 0;}return ans;}@Overridepublic synchronized void writeBuff(byte[] message){if(out ==null){close();return;}try{out.write(message);out.flush();logger.info("發(fā)送成功: "+CommUtil.bytesToHex(message));}catch (IOException e){logger.error(e.getMessage());logger.info("網(wǎng)絡(luò)斷開");close();logger.info("5s后重新啟動網(wǎng)絡(luò).....");try{TimeUnit.MILLISECONDS.sleep(5000);}catch (InterruptedException ee){logger.error(ee.getMessage());}open();}}@Overridepublic void open(){logger.info("Connecting to "+tcp_server_ip+":"+port);while(true){try{socket = new Socket(tcp_server_ip,port);}catch (UnknownHostException e){logger.error(e.getMessage());}catch (ConnectException e){logger.error(e.getMessage());}catch (IOException e){logger.error(e.getMessage());}if(socket != null){logger.info("連接成功!");break;}else{logger.info("連接失敗!5s后重試連接....");try{TimeUnit.MILLISECONDS.sleep(5000);}catch (InterruptedException e){logger.error(e.getMessage());}}}try{in = socket.getInputStream();out = socket.getOutputStream();}catch (IOException e){logger.error(e.getMessage());}}@Overridepublic void close(){if(in != null){try{in.close();in = null;}catch (IOException e){logger.error(e.getMessage());}}if(out != null){try{out.close();out = null;}catch (IOException e){logger.error(e.getMessage());}}if(socket != null){try{socket.close();socket = null;}catch (IOException e){logger.error(e.getMessage());}}}@Overridepublic Object getInfo(){return socket;} }?
TCP的先講述到這里,在下一篇會講述到如何采用UDP進(jìn)行通訊的編程實(shí)踐。
?
歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/network/java-tcp-comm/
歡迎支持筆者新作:《深入理解Kafka:核心設(shè)計(jì)與實(shí)踐原理》和《RabbitMQ實(shí)戰(zhàn)指南》,同時(shí)歡迎關(guān)注筆者的微信公眾號:朱小廝的博客。總結(jié)
以上是生活随笔為你收集整理的JAVA通信编程(三)——TCP通讯的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA通信编程(二)——如何读取jav
- 下一篇: JAVA通信编程(四)——UDP通讯