java TCP/IP Socket編程

前一段時間剛做了個java程序和網絡上多臺機器的c程序通訊的項目,遵循的是TCP/IP協議,用到了java的Socket編程。網絡通訊是java的強項,用TCP/IP協議可以方便的和網絡上的其他程序互通消息。

先來介紹下網絡協議: 
    TCP/IP 
        Transmission Control Protocol 傳輸控制協議 
        Internet Protocol 互聯網協議 
    UDP 
        User Datagram Protocol 用戶數據協議

連接協議: 
    分爲: 
    面向連接協議: Connection Oriented Protocol 
    非連接協議: Connectionless Protocol

    1).面向連接協議是指兩臺電腦在傳輸數據前,先會建立一個專屬的連接。就如電信局的交換機會爲打電話雙方提供專屬連接一樣。 
    Internet上的面向連接協議就是TCP/IP 
    特點:確認迴應;分組序號;流量控制。 
    TCP/IP屬於可靠性傳輸,適合不容許有傳輸錯誤的網絡程序設計使用

    2).非連接協議:無專屬連接,無分組,容錯,距離短,可同時對多臺電腦進行數據傳輸 
    Internet上的非連接協議就是UDP

    TCP在網絡通信上有極強的生命力,例如遠程連接(Telnet)和文件傳輸(FTP)都需要不定長度的數據被可靠地傳輸。相比之下UDP操作簡單,而且僅需要較少的監護,因此通常用於局域網高可靠性的分散系統中client/server應用程序。


Socket 是程序與網絡間的一種接口,大部分網絡應用程序都是點對點的,所謂點就是服務器端和客戶端所執行的程序。Socket是用來接收和傳送分組的一個端點。

java的Socket編程要用到java.net包,最常用的是net包下的6個類:InetAddress(互聯網協議 (IP) 地址)類,Socket(套接字)類,ServerSocket(套接字服務器)類,DatagramSocket(發送和接收數據報包的套接字)類,DatagramPacket(數據報包)類,MulticastSocket(多播數據報套接字類用於發送和接收 IP 多播包)類,其中InetAddress、Socket、ServerSocket類是屬於TCP面向連接協議,DatagramSocket、DatagramPacket和MulticastSocket類則屬於UDP非連接協議的傳送類。

本項目因爲使用TCP/IP協議,主要用到Socket和ServerSocket類

項目代碼如下


Java代碼  01.package com.sse.monitor.serv;  
02.  
03.import java.io.DataInputStream;  
04.import java.io.IOException;  
05.import java.io.InputStream;  
06.import java.io.OutputStream;  
07.import java.io.BufferedOutputStream;  
08.  
09.import java.net.Socket;  
10.import java.net.UnknownHostException;  
11.import java.util.ArrayList;  
12.  
13.import com.sse.monitor.bean.Message;  
14.import com.sse.monitor.bean.MessageHead;  
15.import com.sse.monitor.bean.ResponseMessage;  
16.import com.sse.monitor.form.ListenerInvoke;  
17.import com.sse.monitor.form.MainForm;  
18.import com.sse.monitor.util.SwingUtils;  
19.  
20./** 
21. * Socket套接字工廠,對外接口是靜態方法 SocketFactory.request(String, String, String, int)  
22. * Copyright: Copyright (c) 2008  
23. * Company: conserv 
24. * @author cuishen 
25. * @version 1.3 
26. */  
27.public class SocketFactory {  
28.    private Socket socket = null;  
29.    private String targetIpAddress = null;  
30.    private int targetPort = 0;  
31.    private static SocketFactory sf = new SocketFactory();  
32.  
33.    public SocketFactory() {  
34.    }  
35.  
36.    /** 
37.     * 建立一條TCP/IP連接 
38.     * @param targetIpAddress String 目標ip地址 
39.     * @param targetPort String 目標端口 
40.     * @throws IOException 
41.     */  
42.    private void connect(String targetIpAddress, int targetPort) throws IOException {  
43.        setTargetIpAddress(targetIpAddress);  
44.        setTargetPort(targetPort);  
45.        if(socket == null)  
46.            socket = new Socket(targetIpAddress, targetPort);  
47.    }  
48.  
49.    /** 
50.     * 這是對外接口。發送命令,接收反饋和接收message放兩個線程, 
51.     * 發送命令並接收反饋是短連接,所以每次執行成功後,將銷燬socket並終止線程, 
52.     * 接收message是長連接,所以可能會new出n個線程,建議對接收message的線程做緩存 
53.     * @param commandType String 命令類型 
54.     * @param commandContent String 命令內容 
55.     * @param targetIP String 目標ip 
56.     * @param targetPort int 目標端口 
57.     */  
58.    public static void request(String commandType, String commandContent, String targetIP, int targetPort) {  
59.        if (commandType.equalsIgnoreCase(MessageFactory.SCAN_COMMAND)) {  
60.            sf.new GetMessageSocketThread(commandType, commandContent, targetIP, targetPort);  
61.        } else {  
62.            sf.new RequestSocketThread(commandType, commandContent, targetIP, targetPort);  
63.        }  
64.    }  
65.  
66.    /** 
67.     * 發送請求 
68.     * @param commandType String 命令類型 
69.     * @param commandContent String 命令內容 
70.     * @param targetIp String 目標ip 
71.     */  
72.    private void sendRequest(String commandType, String commandContent, String targetIp) {  
73.        OutputStream os = null;  
74.        BufferedOutputStream bs = null;  
75.        try {  
76.            os = socket.getOutputStream();  
77.            bs = new BufferedOutputStream(os);  
78.            char[] message = MessageFactory.makeRequestMessage(targetIp, commandType, commandContent, MessageFactory.COMMAND_TRADE_CODE, MessageFactory.RIGHT_COMMAND, MessageFactory.MESSAGE_END_FLAG);  
79.            for (int i = 0; i < message.length; i++)  
80.                bs.write(new String(message).getBytes(), i, 1);  
81.            bs.flush();  
82.            SwingUtils.appendLog(MainForm.jTextArea, "發送請求:'" + commandType + "' '" + commandContent + "' '" + targetIp + "'", ReadConfig.commandStateShowLineCount);  
83.        } catch (IOException e) {  
84.            SwingUtils.appendLog(MainForm.jTextArea, "Error!!! 發送請求:'" + commandType + "' '" + commandContent + "' '" + targetIp + "'失敗!! " + e.getMessage(), ReadConfig.commandStateShowLineCount); 
85.            e.printStackTrace();  
86.        } catch (Exception e) {  
87.            e.printStackTrace();  
88.        } finally {  
89.        }  
90.    }  
91.  
92.    /** 
93.     * 獲得反饋 
94.     *  
95.     * @return 如果成功獲得反饋,則返回true;否則返回false 
96.     */  
97.    private boolean getResponse() {  
98.        InputStream is = null;  
99.        DataInputStream di = null;  
100.        boolean returnFlag = false;  
101.        try {  
102.            is = socket.getInputStream();  
103.            di = new DataInputStream(is);  
104.            byte[] temp = new byte[1];  
105.            int flag = 0;  
106.            ArrayList tempByteList = new ArrayList();  
107.            int i = 0;  
108.            while (flag != -1) {  
109.                i++;  
110.                flag = di.read(temp = new byte[1]);  
111.                if (flag != -1)  
112.                    tempByteList.add(temp);  
113.                if (i == 38)  
114.                    break;  
115.            }  
116.            if (i == 1) {  
117.                SwingUtils.Error("未收到response!!!");  
118.                return false;  
119.            }  
120.            MessageHead messageHead = MessageFactory.readHead(tempByteList);  
121.  
122.            SwingUtils.appendLog(MainForm.jTextArea, "收到 response", ReadConfig.commandStateShowLineCount);  
123.  
124.            tempByteList = new ArrayList();  
125.            i = 0;  
126.            while (flag != -1) {  
127.                i++;  
128.                flag = di.read(temp = new byte[1]);  
129.                if (flag != -1)  
130.                    tempByteList.add(temp);  
131.                if (i == 26)  
132.                    break;  
133.            }  
134.            byte[] length = new byte[4];  
135.            di.read(length);  
136.            int len = Integer.parseInt(new String(length, MessageFactory.DEFAULT_CHAR_SET).trim());  
137.            flag = 0;  
138.            for (int j = 0; j < (len + 37); j++) {  
139.                flag = di.read(temp = new byte[1]);  
140.                if (flag == -1)  
141.                    break;  
142.                tempByteList.add(temp);  
143.            }  
144.  
145.            ResponseMessage rm = MessageFactory.readResponseMessage(tempByteList, len);  
146.  
147.            if (messageHead.getErrorCode().equals(MessageFactory.SUCCESS))  
148.                returnFlag = true;  
149.            else  
150.                SwingUtils.Error("errorCode: " + messageHead.getErrorCode() + "; content: " + rm.getCommandContent());  
151.        } catch (IOException e) {  
152.            e.printStackTrace();  
153.        } finally {  
154.        }  
155.        return returnFlag;  
156.    }  
157.  
158.    /** 
159.     * 分發消息的方法,將消息按進程名發送到對應的消息緩存  
160.     * 
161.     * 消息緩存ListenerInvoke.messageMap,key = machineName + 
162.     * '|' + programName + '|' + processId, value = messageList 
163.     * 存放messageMap裏面的鍵名的List -- ListenerInvoke.messageMapKeyList 
164.     * 進程狀態緩存ListenerInvoke.processStateMap, key = machineName + '|' + 
165.     * programName + '|' + processId, value = String 
166.     *  
167.     * @param message Message 
168.     */  
169.    private void distributeMess(Message message) {  
170.        String machineName = message.getMachineName();  
171.        String programName = message.getProgramName();  
172.        String processId = message.getProcessId();  
173.        String content = message.getContent();  
174.        String key = machineName + '|' + programName + '|' + processId;  
175.        key = key.trim();  
176.        ArrayList messageList = null;  
177.        if (ListenerInvoke.messageMap.get(key) == null) {  
178.            synchronized (ListenerInvoke.messageMap) {  
179.                if (ListenerInvoke.messageMap.get(key) == null) {  
180.                    messageList = new ArrayList();  
181.                    messageList.add(content);             
182.                    ListenerInvoke.messageMap.put(key, messageList);  
183.                }  
184.            }  
185.        } else {  
186.            messageList = (ArrayList) ListenerInvoke.messageMap.get(key);  
187.            synchronized (messageList) {  
188.                if (ListenerInvoke.messageMap.get(key) != null) {  
189.                    messageList.add(content);  
190.                    if (!ReadConfig.threadDeleteMessCacheOrFIFO  
191.                            && messageList.size() >= ReadConfig.messageCacheSizeLimit)  
192.                        messageList.remove(0);  
193.                }  
194.            }  
195.        }  
196.        if (!ListenerInvoke.messageMapKeyList.contains(key)) {  
197.            synchronized (ListenerInvoke.messageMapKeyList) {  
198.                if (!ListenerInvoke.messageMapKeyList.contains(key))  
199.                    ListenerInvoke.messageMapKeyList.add(key);  
200.            }  
201.        }  
202.    }  
203.  
204.    /** 
205.     * 接收message 
206.     * @return Message 
207.     */  
208.    private boolean getMessage() {  
209.        InputStream is = null;  
210.        DataInputStream di = null;  
211.        Message message = null;  
212.        try {  
213.            if (this.socket == null)  return false;  
214.            is = this.socket.getInputStream();  
215.            if (is == null)  return false;  
216.            di = new DataInputStream(is);  
217.            byte[] temp = new byte[1];  
218.            int flag = 0;  
219.            ArrayList tempByteList = new ArrayList();  
220.            int i = 0;  
221.            while (flag != -1) {  
222.                i++;  
223.                flag = di.read(temp = new byte[1]);  
224.                if (flag != -1)  
225.                    tempByteList.add(temp);  
226.                if (i == 38)  
227.                    break;  
228.            }  
229.            if (i == 1)  return false;  
230.  
231.            tempByteList = new ArrayList();  
232.            i = 0;  
233.            while (flag != -1) {  
234.                i++;  
235.                flag = di.read(temp = new byte[1]);  
236.                if (flag != -1)  
237.                    tempByteList.add(temp);  
238.                if (i == 74)  
239.                    break;  
240.            }  
241.            byte[] length = new byte[4];  
242.            di.read(length);  
243.            int len = Integer.parseInt(new String(length,  
244.                    MessageFactory.DEFAULT_CHAR_SET).trim());  
245.            flag = 0;  
246.            for (int j = 0; j < len; j++) {  
247.                flag = di.read(temp = new byte[1]);  
248.                if (flag == -1)  
249.                    break;  
250.                tempByteList.add(temp);  
251.            }  
252.            message = MessageFactory.readMessage(tempByteList, len);  
253.            SwingUtils.appendLog(MainForm.jTextArea, "收到新 Message",  
254.                    ReadConfig.commandStateShowLineCount);  
255.            distributeMess(message);// 分發message  
256.        } catch (IOException e) {  
257.            e.printStackTrace();  
258.        } finally {  
259.        }  
260.        return true;  
261.    }  
262.  
263.    /** 
264.     * 負責發送請求接收反饋的內部線程類,每new一個RequestSocketThread線程, 
265.     * 就new一個socket,建立一條專屬連接,成功接收反饋後將銷燬socket,終止線程。 
266.     * 將發送請求,接收反饋放進內部線程處理,是爲了防止套接字阻塞造成主線程掛死。 
267.     * @author cuishen 
268.     * @version 1.2 
269.     */  
270.    class RequestSocketThread implements Runnable {  
271.        private SocketFactory socketFactory;  
272.        private String commandType = null;  
273.        private String commandContent = null;  
274.        private String targetIP = null;  
275.        Thread t;  
276.  
277.        public RequestSocketThread(String commandType, String commandContent, String targetIP, int targetPort) {  
278.            this.socketFactory = new SocketFactory();  
279.            try {  
280.                this.socketFactory.connect(ReadConfig.targetIpAddress, ReadConfig.targetPort);  
281.            } catch (UnknownHostException e) {  
282.                SwingUtils.Error("主機 IP 地址無法確定,無法建立連接! targetIP=" + ReadConfig.targetIpAddress + ", targetPort=" + ReadConfig.targetPort);  
283.                e.printStackTrace();  
284.            } catch (IOException e) {  
285.                SwingUtils.Error("訪問被拒絕,無法建立連接,請檢查網絡! targetIP=" + ReadConfig.targetIpAddress + ", targetPort=" + ReadConfig.targetPort);  
286.                e.printStackTrace();  
287.            }  
288.            this.commandType = commandType;  
289.            this.commandContent = commandContent;  
290.            this.targetIP = targetIP;  
291.            t = new Thread(this);  
292.            t.start();  
293.        }  
294.  
295.        public void run() {  
296.            this.socketFactory.sendRequest(commandType, commandContent, targetIP);  
297.            this.socketFactory.getResponse();  
298.            stopThread();  
299.        }  
300.  
301.        public void stopThread() {  
302.            try {  
303.                this.commandType = null;  
304.                this.commandContent = null;  
305.                this.targetIP = null;  
306.                socketFactory.closeSocket();  
307.                socketFactory = null;  
308.                this.t.join(100);  
309.            } catch (InterruptedException e) {  
310.                e.printStackTrace();  
311.            } finally {  
312.                t = null;  
313.            }  
314.        }  
315.    }  
316.  
317.    /** 
318.     * 負責接收message的內部線程類,每new一個GetMessageSocketThread線程, 
319.     * 就new一個socket,建立一條專屬TCP/IP連接,getMessage是長連接,所以建議 
320.     * 將該線程放入緩存方便管理 
321.     * @author cuishen 
322.     * @version 1.2 
323.     */  
324.    class GetMessageSocketThread implements Runnable {  
325.        private SocketFactory socketFactory;  
326.        private String commandType = null;  
327.        private String commandContent = null;  
328.        private String targetIP = null;  
329.        Thread t;  
330.        private boolean flag = false;  
331.        private boolean ifGetResponse = true;  
332.        private boolean ifGetMessage = true;  
333.        private boolean ifSendRequest = true;  
334.        private boolean ifCycle = true;  
335.  
336.        public GetMessageSocketThread(String commandType, String commandContent, String targetIP, int targetPort) {  
337.              
338.            this.socketFactory = new SocketFactory();  
339.            try {  
340.                this.socketFactory.connect(ReadConfig.targetIpAddress, ReadConfig.targetPort);  
341.            } catch (UnknownHostException e) {  
342.                SwingUtils.Error("主機 IP 地址無法確定,無法建立連接! targetIP="  
343.                        + ReadConfig.targetIpAddress + ", targetPort="  
344.                        + ReadConfig.targetPort);  
345.                e.printStackTrace();  
346.            } catch (IOException e) {  
347.                SwingUtils.Error("訪問被拒絕,無法建立連接,請檢查網絡! targetIP="  
348.                        + ReadConfig.targetIpAddress + ", targetPort="  
349.                        + ReadConfig.targetPort);  
350.                e.printStackTrace();  
351.            }  
352.            this.commandType = commandType;  
353.            this.commandContent = commandContent;  
354.            this.targetIP = targetIP;  
355.            t = new Thread(this);  
356.            t.start();  
357.        }  
358.  
359.        public void run() {  
360.            while (ifCycle) {  
361.                if (ifSendRequest) {  
362.                    this.socketFactory.sendRequest(commandType, commandContent, targetIP);  
363.                    ifSendRequest = false;  
364.                }  
365.                if (ifGetResponse) {  
366.                    flag = socketFactory.getResponse();  
367.                    ifGetResponse = false;  
368.                }  
369.                if (flag && ifGetMessage && socketFactory.socket != null) {  
370.                    if (!socketFactory.getMessage()) {  
371.                        try {  
372.                            Thread.sleep(ReadConfig.getMessageThreadSleep);  
373.                        } catch (InterruptedException e) {  
374.                            e.printStackTrace();  
375.                        }  
376.                    }  
377.                }  
378.            }  
379.        }  
380.  
381.        public void stopThread() {  
382.            try {  
383.                this.commandType = null;  
384.                this.commandContent = null;  
385.                this.targetIP = null;  
386.                ifGetMessage = false;  
387.                ifCycle = false;  
388.                socketFactory.closeSocket();  
389.                socketFactory = null;  
390.                this.t.join(100);  
391.            } catch (InterruptedException e) {  
392.                e.printStackTrace();  
393.            } finally {  
394.                t = null;  
395.            }  
396.        }  
397.    }  
398.  
399.    /** 
400.     * 關閉套接字 
401.     */  
402.    private void closeSocket() {  
403.        try {  
404.            if (!socket.isClosed())  
405.                socket.close();  
406.            socket = null;  
407.        } catch (IOException e) {  
408.            e.printStackTrace();  
409.        }  
410.    }  
411.  
412.    /** 
413.     * @return the targetIpAddress 
414.     */  
415.    public String getTargetIpAddress() {  
416.        return targetIpAddress;  
417.    }  
418.  
419.    /** 
420.     * @param targetIpAddress 
421.     *            the targetIpAddress to set 
422.     */  
423.    public void setTargetIpAddress(String targetIpAddress) {  
424.        this.targetIpAddress = targetIpAddress;  
425.    }  
426.  
427.    /** 
428.     * @return the targetPort 
429.     */  
430.    public int getTargetPort() {  
431.        return targetPort;  
432.    }  
433.  
434.    /** 
435.     * @param targetPort 
436.     *            the targetPort to set 
437.     */  
438.    public void setTargetPort(int targetPort) {  
439.        this.targetPort = targetPort;  
440.    }  
441.  
442.} 


以上是Socket編程,ServerSocket在項目裏沒有用到,但是我也寫了個包裝類供參考

Java代碼  01.package com.sse.monitor.serv;  
02.  
03.import java.io.IOException;  
04.import java.net.ServerSocket;  
05.import java.net.Socket;  
06.  
07./** 
08. * 服務器套接字工廠 
09. * Copyright: Copyright (c) 2008 
10. * @author cuishen 
11. * @version 1.0 
12. */  
13.public class ServerSocketFactory {  
14.    private static ServerSocket server;  
15.    private static Socket client;  
16.    private boolean ifRunServer = true;  
17.      
18.    public void runServer(int port) throws IOException {  
19.        //本地建立一個套接字服務器,等待其他機器訪問  
20.        server = new ServerSocket(port);  
21.        System.out.println("Socket Server Start...");  
22.        new ServerThread();  
23.    }  
24.      
25.  
26.    class ServerThread implements Runnable {  
27.        Thread t;  
28.          
29.        public ServerThread() {  
30.            t = new Thread(this);  
31.            t.start();  
32.        }  
33.  
34.        public void run() {  
35.            try {  
36.                while(ifRunServer) {  
37.                    if(client == null) client = server.accept();  
38.                    if(client != null) //getMessage();  
39.                    Thread.sleep(ReadConfig.serverThreadSleep);  
40.                }  
41.            } catch (InterruptedException e) {  
42.                e.printStackTrace();  
43.            }   
44.            catch (IOException e) {  
45.                e.printStackTrace();  
46.            }  
47.        }  
48.        public void stopThread() {  
49.            try {  
50.                ifRunServer = false;  
51.                this.t.join(100);  
52.            } catch (InterruptedException ex) {  
53.                System.out.println("socket服務器線程終止異常!!!");  
54.            } finally {  
55.                t = null;  
56.            }  
57.        }  
58.    }  
59.} 


Socket編程就是運用Socket或者ServerSocket類搭配線程來使用(由於TCP/IP屬於可靠性傳輸,不會丟包)。可能會因爲在發送請求或者接受消息時Socket阻塞而導致主線程掛死,因此發送請求、接收消息的方法要放進子線程裏處理;對於同一目標ip和端口,在同一個子線程裏只能new一個Socket,也就是說,要對同一地址建立多條連接,就要開啓多個線程。而且注意連接可能會因作用不同分長連接和短連接,要分別處理,本項目中發送請求和接受message就分別屬於短連接和長連接,因此分別開發了RequestSocketThread和GetMessageSocketThread兩個子線程區分對待。可以同時開發個Message類來封裝打包和解包消息的方法(項目中開發MessageFactory.java),方便調用


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章