java随笔--通信小结

关于通信的知识我在这两天做了一个总结,采用思维导图的方式.作为一个毫无基础的java小白,去理解计算机的通信网络基础还是很让人抓狂的.
我现在只能够从我所学习到的皮毛,去试图将这些知识转化为我能够理解的意思,并保存下来方便今后改善和回忆.

通信的三要素

通信的三要素是协议,IP,和端口.

TCP和UDP

这是两种常见的协议,一般在视频,语音通话多采用UDP协议.它的特点是不建立连接,只管发送,速度快,但是不可靠,容易丢失数据.所以使用并不广泛,而TCP则弥补了这些.TCP采用socket管道建立客户端(或浏览器端)与服务端的连接.建立连接的过程如下:
1.客户端向服务器端发出连接请求,等待服务器确认(一次握手);
2.服务端向客户端返回一个响应,通知客户端接到了客户端的请求(二次握手);
3.客户端再次向服务端发送确认信息,确认连接(三次握手).
由于这种面向连接的特性,使得数据传输很安全,不会轻易丢失数据.所以建议采用TCP协议进行数据传输.

java的相关类

用于TCP通信的java相关类有两个,一个是Socket类,还有一个是ServerSocket类.前者表示客户端程序,而后者表示服务端程序.

利用Socket和ServerSocket类实现一个简单的文本传输(聊天)

实现聊天的过程步骤有:
1.要分别建立两个public类,Client和Server表示客户端和服务端.
2.在Client类中,需要做到以下几点:
(1).建立一个socket对象,参数有ip地址和端口号.ip地址设为本地ip,端口号随意.(大于1023小于65536)
(2).调用Socket.getOutputStream()方法获得一个字节文件输出流.将它包装成高速缓冲的字符打印流.这一段的经典代码:

Socket socket = new Socket("127.0.0.1" , 9999);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);

(3).为了使得能够不断地发送信息给服务端,我们设计一个while-true循环,除非手动退出,否则可以不断地重复让客户端输出文本内容.

PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while(true){
	System.out.print("请说:");
	String msg = sc.nextLine();
	ps.println(msg);
	ps.flush();
}

客户端的部分介绍完毕.
3.在Server类中,需要做到以下几点:
(1)首先,建立一个serverSocket对象,它只有一个参数,那就是端口号,这个端口号要和客户端设置的端口号一致.
(2).定义一个循环不断地接受客户端的连接请求.
(3).添上这句话:Socket socket = serverSocket.accept();这句话表示服务器等待接收客户端的连接请求.必须要有!
(4)一旦获得客户端的socket,就可以建立一个线程来负责这个socket的通信.
new serverThread(socket).start();//serverThread extends Thread
或new Thread(serverRunnable(socket)).start();//serverRunnable implements Runnable

package com.itheima._06TCP通信三;

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

/**
    目标:开发服务器。
         1.注册端口。
         2.接收客户端的Socket管道连接。
         3.从socket通信管道中得到一个字节输入流。
         4.从字节输入流中读取客户端发来的数据。
 */
public class ServerDemo02 {
    public static void main(String[] args) throws Exception {
        System.out.println("----服务端启动----");
        // 1.注册端口: public ServerSocket(int port)
        ServerSocket serverSocket = new ServerSocket(9999);
        // 2.定义一个循环不断的接收客户端的连接请求
        while(true){
            // 3.开始等待接收客户端的Socket管道连接。
            Socket socket = serverSocket.accept();
            // 4.每接收到一个客户端必须为这个客户端管道分配一个独立的线程来处理与之通信。
            new ServerReaderThread(socket).start();
        }
    }
}

class ServerReaderThread extends Thread{
    private Socket socket ;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try{
            // 3.从socket通信管道中得到一个字节输入流。
            InputStream is = socket.getInputStream();
            // 4.把字节输入流转换成字符输入流
            Reader isr = new InputStreamReader(is);
            // 5.把字符输入流包装成缓冲字符输入流。
            BufferedReader br = new BufferedReader(isr);
            // 6.按照行读取消息 。
            String line ;
            while((line = br.readLine())!=null){
                System.out.println(socket.getRemoteSocketAddress()+"说:"+line);
            }
        }catch (Exception e){
            System.out.println(socket.getRemoteSocketAddress()+"下线了~~~~~~");
        }
    }
}

设计多线程的原因是一般情况下客户端对服务端是多对一的.只有一个主线程不足以并发处理如此多的socket.至于线程的构造方法也有两种,继承thread或实现runnable接口.无论是哪一种,都需要重写run()方法.而run()方法的内容就是原来单线程通信时写在主线程的内容.

(5)重写run()方法.
请求一个字节输入流,并包装成高速缓冲的字符输入流BufferedReader.按行读取文本内容.

 InputStream is = socket.getInputStream();
 Reader isr = new InputStreamReader(is);
 BufferedReader br = new BufferedReader(isr);
 String line ;
 while((line = br.readLine())!=null){
     System.out.println(socket.getRemoteSocketAddress()+"说:"+line);
     }

服务端通常不会关闭,我们还可以通过向控制台输出上线和下线信息来现实客户的上线情况和下线情况.

参考效果

我们首先运行Client类,再运行Server类,顺序不要错.
然后我们再运行一个Client类,通过idea右上角edit configurations 将选项allow parallel run勾选并点击Apply.
就可以同时跑多个线程了.
在这里插入图片描述
在这里插入图片描述
在其中一个客户端控制台里输入:“欢迎来到合肥”,在另一个客户端控制太输入"欢迎来到南京",会发现这个时候服务端的控制台已经输出了这两个客户端的话.因为是按行读取的,所以我们只要一回车就相当于发送了一段文本.这也符合聊天软件的习惯.
要退出聊天,只要手动将这个进程退出,则退出信息也在服务器的控制台上显示出来了.
在这里插入图片描述
TCP虽然保证了安全性,但是如果是过多线程,数据的传输会非常缓慢,甚至死机.所以有利用线程池控制上线人数不能超过人,只有在一个人退出与这个服务器的连接通道以后才容许其他用户进入的机制,避免了一拥而入导致的卡顿或死机.尽管如此,TCP的安全性还是它强于UDP的地方.

文件传输

用socket和serverSocket不仅可以模拟在线聊天,还可以模拟文件传输.文件一般来说是以二进制的方式传输的,比如图片,音频等.我们利用BufferedOutputStream和BufferedInputStream就可以了.

我希望实现从本地的一个地方传输一个图片到另一个路径上,建立从客户端到服务端的通道.事实上,如果我的电脑跟其他电脑有连接,就完全可以实现文件的传输,那样会更逼真.
代码如下:
这是客户端的代码

package com.itheima._09文件上传演示;

import java.io.*;
import java.net.Socket;

/**
     目标:实现客户端上传图片给服务端保存起来。

     开发客户端:本地图片:  C:\Users\doudo\Pictures\Saved Pictures\美女.jpg
     开发服务端:服务器路径:D:\美女图片服务器\
 */
public class ClientDemo {
    // 本地图片路径、
    public static void main(String[] args) throws Exception {
        // 1.请求于服务端的Socket管道连接。
        Socket socket = new Socket(Constants.SERVER_IP , Constants.SERVER_PORT);
        // 2.从socket管道中得到一个字节输出流包装成缓冲字节输出流
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        // 3.提取本机的图片上传给服务端
        // 4.得到一个缓冲字节输入流与本地图片接通
        BufferedInputStream bis =
                new BufferedInputStream(new FileInputStream(Constants.SRC_IMAGE));
        // 5.定义一个字节数组
        byte[] buffer = new byte[1024];
        int len ;
        while((len = bis.read(buffer)) != -1) {
            bos.write(buffer, 0 ,len);
        }
        bos.flush(); // 刷新图片数据到服务端!!
        socket.shutdownOutput(); // 告诉服务端我的数据已经发送完毕,请不要在等我了!
        bis.close(); // 可以关闭

        // 6.等待着服务端的响应数据!!
        BufferedReader  br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        System.out.println("收到服务端响应:"+br.readLine());
    }
}

设计一个Constants类用来保存这些路径,ip和端口号信息:

package com.itheima._09文件上传演示;

/**
 * 客户端常量包
 */
public class Constants {
    public static final String SRC_IMAGE = "C:\\Users\\doudo\\Pictures\\Saved Pictures\\美女.jpg";
    public static final String SERVER_DIR = "D:\\美女图片服务器\\";
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8888;

}

然后是服务端的代码:

package com.itheima._09文件上传演示;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;

/**
      功能点:
      1.接收多个客户端传输来的图片数据存储到服务器路径:
      2.响应一个成功的消息给当前客户端。
 */
public class ServerDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("----服务端启动----");
        // 1.注册端口: public ServerSocket(int port)
        ServerSocket serverSocket = new ServerSocket(Constants.SERVER_PORT);
        // 2.定义一个循环不断的接收客户端的连接请求
        while(true){
            // 3.开始等待接收客户端的Socket管道连接。
            Socket socket = serverSocket.accept();
            // 4.每接收到一个客户端必须为这个客户端管道分配一个独立的线程来处理与之通信。
            new ServerReaderThread(socket).start();
        }
    }
}

class ServerReaderThread extends Thread{
    private Socket socket ;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try{
            // 1.从socket通信管道中得到一个字节输入流读取客户端发来的图片数据!
            InputStream is = socket.getInputStream();
            // 2.包装成高级的缓冲字节输入流
            BufferedInputStream bis = new BufferedInputStream(is);
            // 3.定义一个缓冲字节输出流通向目标路径(服务端路径)
            BufferedOutputStream bos =
                    new BufferedOutputStream(new FileOutputStream(Constants.SERVER_DIR+ UUID.randomUUID().toString()+".jpg"));
            byte[] buffer = new byte[1024];
            int len ;
            while((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0 ,len);
            }
            bos.close();
            System.out.println("服务端接收完毕了!");

            // 4.响应数据给客户端
            PrintStream ps = new PrintStream(socket.getOutputStream());
            ps.println("您好,已成功接收您上传的图片!");
            ps.flush();

            Thread.sleep(100000); // 等消息发送完毕被客户端接收后死亡!
        }catch (Exception e){
            System.out.println(socket.getRemoteSocketAddress()+"下线了~~~~~~");
        }
    }
}

效果就是我每执行一次clientDemo都会传输一张图片到我的指定的服务器路径内.

在这里插入图片描述

小结

通信好玩的地方还有很多,这里只是基础中的基础,根本登不上台面.
通过这两天的学习,我觉得要学习通信基础,需要了解计算机网络的相关基础.不必了解太深入,浅尝辄止,原理的东西可以让你更能够理解一些经典代码的背后思想.细节方面则考验一个优秀程序员的功底了.另外,大家需要熟练掌握IO流相关知识,线程相关知识,时刻查阅api文档.这对于我们的知识串联也是一个很好的锻炼机会.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值