大战熟女丰满人妻av-荡女精品导航-岛国aaaa级午夜福利片-岛国av动作片在线观看-岛国av无码免费无禁网站-岛国大片激情做爰视频

專注Java教育14年 全國咨詢/投訴熱線:400-8080-105
動(dòng)力節(jié)點(diǎn)LOGO圖
始于2009,口口相傳的Java黃埔軍校
首頁 學(xué)習(xí)攻略 Java學(xué)習(xí) Java實(shí)現(xiàn)tcp的示例

Java實(shí)現(xiàn)tcp的示例

更新時(shí)間:2022-07-20 06:29:12 來源:動(dòng)力節(jié)點(diǎn) 瀏覽1698次

TCP(Transmission Control Protocol),即傳輸控制協(xié)議。是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。不同于UDP,TCP更像是提供一種可靠的、像管道一樣的連接。

Java中的TCP主要涉及ServerSocket和Socket兩個(gè)類。前者被認(rèn)為是服務(wù)端的一個(gè)實(shí)體,用于接受連接。后者則被認(rèn)為是連接的一種封裝,用于傳輸數(shù)據(jù),類似于一個(gè)管道。

下面就來實(shí)現(xiàn)一下服務(wù)端與客戶端。

服務(wù)端:

public class TCPService {
    public static final String SERVICE_IP = "127.0.0.1";
    public static final int SERVICE_PORT = 10101;
    public static final char END_CHAR = '#';
    public static void main(String[] args) {
        TCPService service = new TCPService();
        //啟動(dòng)服務(wù)端
        service.startService(SERVICE_IP,SERVICE_PORT);
    }
    private void startService(String serverIP, int serverPort){
        try {
            //封裝服務(wù)端地址
            InetAddress serverAddress = InetAddress.getByName(serverIP);
            //建立服務(wù)端
            try(ServerSocket service = new ServerSocket(serverPort, 10, serverAddress)){
                while (true) {
                    StringBuilder receiveMsg = new StringBuilder();
                    //接受一個(gè)連接,該方法會(huì)阻塞程序,直到一個(gè)鏈接到來
                    try(Socket connect = service.accept()){
                        //獲得輸入流
                        InputStream in = connect.getInputStream();                        
                        //解析輸入流,遇到終止符結(jié)束,該輸入流來自客戶端
                        for (int c = in.read(); c != END_CHAR; c = in.read()) {
                            if(c ==-1)
                                break;
                            receiveMsg.append((char)c);
                        }                        
                        //組建響應(yīng)信息
                        String response = "Hello world " + receiveMsg.toString() + END_CHAR;                        
                        //獲取輸入流,并通過向輸出流寫數(shù)據(jù)的方式發(fā)送響應(yīng)
                        OutputStream out = connect.getOutputStream();
                        out.write(response.getBytes());
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

客戶端

public class TCPClient {
    public static void main(String[] args) {
        TCPClient client = new TCPClient();
        SimpleDateFormat format = new SimpleDateFormat("hh-MM-ss");
        Scanner scanner = new Scanner(System.in);
        while(true){
            String msg = scanner.nextLine();
            if("#".equals(msg))
                break;
            //打印響應(yīng)的數(shù)據(jù)
            System.out.println("send time : " + format.format(new Date()));
            System.out.println(client.sendAndReceive(TCPService.SERVICE_IP,TCPService.SERVICE_PORT,msg));
            System.out.println("receive time : " + format.format(new Date()));
        }
    }
    private String sendAndReceive(String ip, int port, String msg){
        //這里比較重要,需要給請(qǐng)求信息添加終止符,否則服務(wù)端會(huì)在解析數(shù)據(jù)時(shí),一直等待
        msg = msg+TCPService.END_CHAR;
        StringBuilder receiveMsg = new StringBuilder();
        //開啟一個(gè)鏈接,需要指定地址和端口
        try (Socket client = new Socket(ip, port)){
            //向輸出流中寫入數(shù)據(jù),傳向服務(wù)端
            OutputStream out = client.getOutputStream();
            out.write(msg.getBytes());
            //從輸入流中解析數(shù)據(jù),輸入流來自服務(wù)端的響應(yīng)
            InputStream in = client.getInputStream();
            for (int c = in.read(); c != TCPService.END_CHAR; c = in.read()) {
                if(c==-1)
                    break;
                receiveMsg.append((char)c);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return receiveMsg.toString();
    }
}

單從代碼結(jié)構(gòu)的角度來看,UDP通信服務(wù)端與客戶端代碼是相似的,都是依托于DatagramPacket 對(duì)象收發(fā)信息。而TCP通信中,只有服務(wù)端有一個(gè)實(shí)體,客戶端只要借助Socket收發(fā)信息即可,發(fā)送完關(guān)閉Socket。

上面有一點(diǎn)需要注意,在讀輸入流時(shí),必須做讀到流結(jié)束判斷,就是讀到-1,若沒有做判斷,在這樣情況下會(huì)出錯(cuò):若一個(gè)連接連接成功后,沒有發(fā)生任何信息,或信息中沒有結(jié)束字符,就關(guān)閉了連接,由于TCP連接是雙向的,導(dǎo)致另一端一直從輸入流中讀到流結(jié)束標(biāo)志,很快會(huì)導(dǎo)致OOM,所以在讀到結(jié)束符時(shí),要及時(shí)跳出循環(huán)。結(jié)束符只會(huì)在連接中斷時(shí)發(fā)出,而在等待輸入時(shí),不會(huì)出現(xiàn),所以不必?fù)?dān)心在等待響應(yīng)時(shí)由于讀到該字符導(dǎo)致服務(wù)端或客戶端提前中斷連接。

另外Socket和ServerSocket在jdk 1.7之后都實(shí)現(xiàn)了AutoCloseable接口,所以可以用try-with-resources結(jié)構(gòu)。之前的UDP里的DatagramPacket 也一樣

這就是一個(gè)簡(jiǎn)單的阻塞型服務(wù)器模型,分析代碼我們可知,如果一次請(qǐng)求時(shí)間過長,會(huì)影響到后續(xù)請(qǐng)求的執(zhí)行。我們可以在服務(wù)端輸出時(shí)加一個(gè)sleep,啟動(dòng)兩個(gè)客戶端,分別發(fā)送消息,觀察log,服務(wù)端延遲5s,結(jié)果如下:

客戶端1:
send time : 06-04-06
Hello world 1
receive time : 06-04-11
客戶端2:
send time : 06-04-08
Hello world 2
receive time : 06-04-16

其中客戶端1先發(fā)送,客戶端2后發(fā)送,可見客戶端在等待服務(wù)器處理完客戶端1的請(qǐng)求后才處理客戶端2的請(qǐng)求

由此我們可以預(yù)見,當(dāng)服務(wù)器接到一個(gè)需要長時(shí)間處理的請(qǐng)求時(shí),會(huì)阻塞后續(xù)的請(qǐng)求,這也就是這種類型服務(wù)器容易遭到攻擊的原因。為了應(yīng)對(duì)這種局面,我們可以在收到一個(gè)請(qǐng)求時(shí),調(diào)用子線程去處理,服務(wù)器時(shí)刻處在接受請(qǐng)求的狀態(tài)。

public class TCPService1 {
    public static final String SERVICE_IP = "127.0.0.1";
    public static final int SERVICE_PORT = 10101;
    public static final char END_CHAR = '#';
    public static void main(String[] args) {
        TCPService1 service1 = new TCPService1();
        service1.startService();
    }
    private void startService(){
        try {
            InetAddress address = InetAddress.getByName(SERVICE_IP);
            Socket connect = null;
            ExecutorService pool = Executors.newFixedThreadPool(5);
            try (ServerSocket service = new ServerSocket(SERVICE_PORT,5,address)){
                while(true){
                    connect = service.accept();
                    //創(chuàng)建一個(gè)任務(wù)
                    ServiceTask serviceTask = new ServiceTask(connect);
                    //放入線程池等待運(yùn)行
                    pool.execute(serviceTask);
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(connect!=null)
                    connect.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    class ServiceTask implements Runnable{
        private Socket socket;
        ServiceTask(Socket socket){
            this.socket = socket;
        }
        @Override
        public void run() {
            try {
                StringBuilder receiveMsg = new StringBuilder();
                InputStream in = socket.getInputStream();
                for (int c = in.read(); c != END_CHAR; c = in.read()) {
                    if(c ==-1)
                        break;
                    receiveMsg.append((char)c);
                }
                String response = "Hello world " + receiveMsg.toString() + END_CHAR;
                Thread.currentThread().sleep(5000);
                OutputStream out = socket.getOutputStream();
                out.write(response.getBytes());
            }catch (Exception e){
                e.printStackTrace();
            }finally {
               if(socket!=null)
                   try {
                       socket.close();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
            }
        }
    }
}

在這個(gè)服務(wù)器中,我們采用了線程池的做法,每到一個(gè)請(qǐng)求,我們就向線程池中添加一個(gè)任務(wù)。實(shí)際運(yùn)行情況如下:

客戶端1
send time : 03-04-59
Hello world 1
receive time : 03-04-04
客戶端2
send time : 03-04-01
Hello world 2
receive time : 03-04-06

可見每個(gè)客戶端能在發(fā)送信息后得到響應(yīng),不必排隊(duì)。但是這種類型的服務(wù)器并不能保證實(shí)時(shí)響應(yīng),當(dāng)請(qǐng)求數(shù)過多時(shí),服務(wù)器資源會(huì)被耗盡,或者服務(wù)器有最大線程數(shù)有限制,多余的請(qǐng)求依然會(huì)被阻塞。

第一二種服務(wù)器模型中,我們?cè)谧x取流的時(shí)候加入了自定義的結(jié)束符,同時(shí)采用Java for循環(huán),但是一次從輸入流中讀一個(gè)數(shù)據(jù),效率比較低,我們可以采用緩沖區(qū)的方法,但是這種方法不能判斷自定義的結(jié)束符,只能判斷流結(jié)束,所以要及時(shí)關(guān)閉流,如客戶端發(fā)完數(shù)據(jù)后關(guān)閉輸出流:

OutputStream out = client.getOutputStream();
out.write(msg.getBytes());
client.shutdownOutput();
InputStream in = client.getInputStream();
int len;
byte[] buffer = new byte[1024];
while((len = in.read(buffer))!=-1)
       receiveMsg.append(new String(buffer,0,len));

由于TCP通信是雙向的,所以可以單獨(dú)關(guān)閉一端,但是不能直接關(guān)閉輸入或輸出流,這樣會(huì)將整個(gè)Socket關(guān)閉。

提交申請(qǐng)后,顧問老師會(huì)電話與您溝通安排學(xué)習(xí)

免費(fèi)課程推薦 >>
技術(shù)文檔推薦 >>
主站蜘蛛池模板: 国产精品永久免费 | 123日本不卡在线观看 | 欧美在线视频网 | 成人网中文字幕色 | 2021最新国产成人精品免费 | 亚洲第一中文字幕 | 九九久久99综合一区二区 | 在线 | 一区二区三区四区 | 久草青青在线 | 麻豆成人久久精品二区三区小说 | 国产第九页 | 韩国理论片在线看2828dy | 精品国产一级毛片大全 | 一级做性色a爱片久久片 | 亚洲精品国产精品精 | 在线97| 久久99精品久久久久久牛牛影视 | 国产人成精品 | 噜噜狠狠 | 黄色在线观看网站 | 五月天丁香婷婷综合 | 久操精品视频 | jazz欧美人免费xxxxxx | 97免费视频免费视频 | 久久久久国产精品免费看 | 天天干国产 | 亚洲欧美日韩国产精品久久 | 天天干天天操天天玩 | 伊人成年综合网 | 亚洲综合激情五月色播 | a级毛片免费完整视频 | 奇米影视777me | 奇米影视播放器 | 性做久久久久免费看 | 青青久草在线 | 国产精品成人观看视频国产 | 激情爱爱视频 | 国产亚洲精品一区二区在线播放 | 一级特级女人18毛片免费视频 | 最新国产精品精品视频 | 成人精品福利 |