Java網絡編程(一)-Socket編程

從本篇博客開始,後面幾篇博客會着重介紹Java網絡編程相關方面的知識,主要涉及Socket編程,Http協議編程。
在網絡通訊中,我們把主動發起通信請求的程序稱爲客戶端,而在通訊中等待客戶端發起請求建立連接的程序稱爲服務端。因而網絡編程最重要的就是分別開發客戶端程序和服務端程序。
這裏寫圖片描述

對於請求建立連接客戶端,Java提供了Socket類用於客戶端開發,主要完成以下四個基本操作:連接遠程主機,發送數據,接收數據,關閉連接。
對於接收連接的服務端,Java提供了ServerSocket類表示服務器Socket,主要完成以下三個基本操作:綁定端口,監聽入站數據,在綁定端口上接受來自遠程機器的連接。
Java利用socket實現了客戶端-服務端全雙工及時通訊,即客戶端和服務端可以同時發送和接收數據。

一、客戶端Socket
1、利用Socket構造器構造套接字
2、Socket嘗試連接服務器主機
3、建立連接後,利用socket獲得輸入輸出流,實現相互交互數據
4、通信結束後,關閉輸入輸出流和Socket連接
下面看一個具體的Client端示例:

package com.wygu.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;

public class TestClient {

    public static void main(String[] args) {
        Socket socket = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        try {
            //創建Socket套接字,建立和服務端:127.0.0.1,端口號:8080的連接
            socket = new Socket("127.0.0.1",8080);
            //設置Socket響應超時時間,超時時間按照毫秒度量,下面設置爲10秒
            //socket.setSoTimeout(100000);

            //返回輸出流,向服務端寫入數據
            outputStream = socket.getOutputStream();
            //包裝輸出流OutputStream爲Writer,向服務端寫入數據
            Writer writer = new OutputStreamWriter(outputStream);
            String sendData = "Hello,Server!!";
            System.out.println("Client send:"+sendData);    
            writer.write(sendData);
            //強制輸出
            writer.flush();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                if(null!=socket){
                    //關閉socket連接
                    socket.close();
                }
                if(null!=outputStream){
                    //關閉輸出流
                    outputStream.close();
                }
                if(null!=inputStream){
                    //關閉輸入流
                    inputStream.close();
                }       
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }           
        }


    }

}

二、服務端ServerSocket
1、利用ServerSocket()構造器在一個特定端口創建ServerSocket實例
2、ServerSocket使用accept()方法監聽特定端口的入站連接。其中,accept()會一直阻塞,直到一個客戶端嘗試建立連接後,accept()會返回一個客戶端和服務端的Socket實例
3、利用socket獲得輸入輸出流,實現相互交互數據
4、交互完畢後,關閉連接
5、服務端返回到步驟2,等待下一次的連接
下面看一個具體的Server端實例:

package com.wygu.client;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;

public class TestServer {

    public static void main(String[] args) {
        ServerSocket server = null;
        try {
            //創建端口號爲8080的ServerSocket實例
            server = new ServerSocket(8080);
            while(true){
                Socket connection = null;
                InputStreamReader reader = null;
                Writer writer = null;
                try {
                    //阻塞式等待客戶端連接
                    connection = server.accept();
                    //創建輸入流,並讀取客戶端發送的數據
                    reader= new InputStreamReader(connection.getInputStream());
                    StringBuilder receiveData = new StringBuilder();
                    for(int c=reader.read();c!=-1;c=reader.read()){
                        receiveData.append((char)c);
                    }
                    System.out.println("Server receive:"+receiveData.toString());   
                    Thread.sleep(1000);
                }catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if(null!=connection){
                            connection.close();
                        }
                        if(null!=reader){
                            reader.close();
                        }
                        if(null!=writer){
                            writer.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(null!=server){
                try {
                    server.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}

三、Socket通信插件項目
項目中涉及到的功能點:長連接,多線程,消息隊列。
1、客戶端
客戶端的啓動可以通過main()方法啓動,不管被啓動多少次,保證只建立一條鏈路
1)Client

package com.wygu.client;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import com.wygu.queue.MsgInQueue;

public class Client implements Runnable{

    //將Socket連接Client設置爲單例模式
    private volatile static Client clientInsance = null;

    public static Client getInstance(){
        if(null==clientInsance){
            synchronized (Client.class) {
                if(null==clientInsance){
                    clientInsance = new Client();
                }
            }
        }
        return clientInsance;
    }

    private String serverHost = null;
    private String serverPort = null;
    private Socket socketClient = null;
    private InputStream inputStream = null;
    private OutputStream outputStream = null;


    private long systemCurTime;
    //設置線程沉睡時間
    private long minSleepTime = 2;
    private long maxSleepTime = 20;
    private long sleepTime;
    private long heartBeatTime = 60;

    private boolean running = true;

    public void startClient(String host,String port){
        initSocket(host, port);
        Thread thread = new Thread(this);
        thread.setDaemon(true);
        thread.start();
        try {
            Thread.sleep(3 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void initSocket(String host, String port){
        this.serverHost = host;
        this.serverPort = port;
    }

    private void connect() throws NumberFormatException, IOException{
        this.socketClient = new Socket();
        this.socketClient.connect(new InetSocketAddress(serverHost, Integer.valueOf(serverPort)));
        this.socketClient.setSoTimeout(10000);
        this.inputStream = new DataInputStream(socketClient.getInputStream());
        this.outputStream = new DataOutputStream(socketClient.getOutputStream());
        this.sleepTime = this.minSleepTime;
    }

    @Override
    public void run() {
        try {
            connect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        setActiveTime();
        while(running){
            try {
                Thread.sleep(sleepTime);
                //從發送隊列中取出消息,然後發送
                if(sendMessage()) {                     
                    setActiveTime();
                }
                //從接收隊列中取出消息,然後轉換
                if(inputStream.available() > 0){
                    onMessage();
                    setActiveTime();
                    sleepTime = this.minSleepTime;
                }
                else{
                    sleepTime = this.maxSleepTime;
                }
                if((this.systemCurTime + this.heartBeatTime * 1000L) < System.currentTimeMillis()){
                    activeTest();
                    setActiveTime();
                }
            } catch (InterruptedException e) {                  
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }   
        }

    }

    private void onMessage() throws IOException {       
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String receiveData = bufferedReader.readLine(); 
        System.out.println("Client receive:"+receiveData);  
    }

    private boolean sendMessage() {
        try {
            String mString = MsgInQueue.getInstance().getMsg();         
            if(null!=mString){
                System.out.println("Client send:"+mString); 
                Writer writer = new OutputStreamWriter(outputStream);
                writer.write(mString+'\n');//加入換行符,按行讀取
                writer.flush();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    //心跳包測試
    private void activeTest() throws IOException {
        Writer writer = new OutputStreamWriter(outputStream);
        String heatBeatStr = "00"+'\n';//加入換行符,按行讀取
        writer.write(heatBeatStr);
        writer.flush();
    }

    private void setActiveTime() {
        this.systemCurTime = System.currentTimeMillis();

    }

}

2)消息隊列

package com.wygu.queue;

import java.util.concurrent.ConcurrentLinkedQueue;

public class MsgInQueue {

    private volatile static MsgInQueue msgInQueue = null;
    //單例模式,保證整個機制中只有一個消息隊列實例    
    public static MsgInQueue getInstance(){
        if(null==msgInQueue){
            synchronized (MsgInQueue.class) {
                if(null==msgInQueue){
                    msgInQueue = new MsgInQueue();
                }
            }
        }
        return msgInQueue;
    }

    private ConcurrentLinkedQueue<String> msgQueue = new ConcurrentLinkedQueue<String>();

    public String getMsg(){
        return msgQueue.poll();
    }

    public void putMsg(String inputMsg){
        msgQueue.add(inputMsg);
    }

}

3)Main程序

package com.wygu.client;

import java.util.Scanner;

import com.wygu.queue.MsgInQueue;

public class Main {

    @SuppressWarnings("resource")
    public static void main(String[] args) {
        String clientIp = "127.0.0.1";
        String clientport = "10800";
        Client.getInstance().startClient(clientIp, clientport);
        Scanner in=new Scanner(System.in);
        String inputStr = null;
        //只建立一條鏈路,實現多線程客戶端訪問服務端
        while(!"quit".equals(inputStr=in.nextLine())){
            MsgInQueue.getInstance().putMsg(inputStr);
        }

    }

}

代碼可以實現客戶端和服務端之間只需要建立一條鏈路,通過消息隊列機制實現多線程同時發送消息,爲保證不同的線程收到屬於自身的服務端應答,可以加入消息ID機制實現。

2、服務端
服務端接收到一個客戶端連接後,會創建一個線程處理該連接的消息機制
1)Server端

package com.wygu.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server implements Runnable{

    private String serverPort;

    private Server(String port){
        this.setServerPort(port);
    }

    public static void startServer(String port){
        Thread thread = new Thread(new Server(port));
        thread.start();
    }

    @Override
    public void run() {
        try {
            ServerSocket _server = new ServerSocket(Integer.valueOf(this.getServerPort()));
            System.out.println(String.format("Server [%s] startup success!", _server.getLocalSocketAddress()));
            while (true) {
                try {
                    Socket socket = _server.accept();
                    Connection connection = new Connection(socket);
                    System.out.println("Accept socket from " + socket.getInetAddress().getHostAddress() + " " + socket.getPort());
                    new Thread(connection).start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public String getServerPort() {
        return serverPort;
    }

    public void setServerPort(String serverPort) {
        this.serverPort = serverPort;
    }

}

2)消息處理線程

package com.wygu.server;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;

public class Connection implements Runnable{

    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;

    public Connection(Socket socket) {
        this.socket = socket;
    }

    private void init() throws IOException{
        this.inputStream = socket.getInputStream();
        this.outputStream = socket.getOutputStream();
    }


    @Override
    public void run() {
        try {
            init();
            Writer writer = new OutputStreamWriter(outputStream);
            while (true) {
                try {
                    String receiveData = null;
                    if (inputStream.available() > 0) {
                        receiveData = readMsg(inputStream);
                        if(null!=receiveData){
                            System.out.println("Server receive:"+receiveData);  
                            String sendData = "I have received!"+'\n';   //服務端給予客戶端應答
                            System.out.println("Server send:"+sendData);    
                            writer.write(sendData);
                            writer.flush();
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                Thread.sleep(1000);
            }   
        } catch (Exception e) {
            e.printStackTrace();
        }       
    }

    private String readMsg(InputStream in) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
        String receiveData = bufferedReader.readLine();
        //檢查心跳包
        if ("00".equals(receiveData)) {
            System.out.println("Server heart beat!");
            return null;
        }
        return receiveData;
    }
}

3)Main程序

package com.wygu.server;

public class Main {

    public static void main(String[] args) {
        String serverPort = "10800";
        Server.startServer(serverPort);
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章