网络通信

image.png

网络

image.png

ip地址

image.png
image.png
image.png
image.png

域名与端口号

image.png
image.png
端口就类似于家里的门,可以开很多门,只有从门才能进入家里。

通信协议

image.png
image.png

TCP与UDP

image.png
image.png
image.png
类似于发短信,并不知道对方收到没有,也可能发错了。
image.png

InetAddress类

image.pngimage.png
image.png

Socket

image.png
image.png

TCP网络通信编程

image.png
注:当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP 来分配的,是不确定且随机的。
image.png

编程举例

普通版

image.png
image.png
客户端

  1. public class Client {
  2. public static void main(String[] args) throws IOException {
  3. Socket socket = new Socket(InetAddress.getLocalHost(),9999);//连接本机的9999端口,连接成功返回socket对象
  4. System.out.println("客户端socket返回: " + socket.getClass());
  5. OutputStream outputStream = socket.getOutputStream(); //通过socket.getOutputStream()获得输出流
  6. outputStream.write("hello,server".getBytes()); //写入数据(需要调用getBytes方法)
  7. outputStream.close(); //关闭流和socket
  8. socket.close();
  9. }
  10. }

服务端

  1. class Server {
  2. public static void main(String[] args) throws IOException {
  3. ServerSocket serverSocket = new ServerSocket(9999); // 本机的9999端口开始监听(要求本机没有其它服务在监听9999)
  4. System.out.println("服务端,9999端口监听,等待连接..."); // 这个ServerSocket可以通过accept()方法 返回多个Socket,允许多个客户端连接服务器的并发
  5. //当没有客户端连接9999端口时,程序会阻塞,等待连接
  6. Socket socket = serverSocket.accept(); //如果有客户端连接,则会返回Socket对象,程序继续
  7. InputStream inputStream = socket.getInputStream(); //连接上后,通过 socket.getInputStream得到输入流对象
  8. byte[] buf = new byte[1024];
  9. int readLen = 0;
  10. while((readLen = inputStream.read(buf))!=-1){
  11. System.out.println(new String(buf,0,readLen));
  12. } //参见IO流,这里每次打印一个字符串
  13. inputStream.close(); //记得最后关闭流对象和socket,否则会占用资源
  14. socket.close();
  15. }
  16. }

注意运行的时候先运行服务器,再运行客户端。

进阶版

image.png
这次需要客户端做出回应,那么就需要考虑一个问题:什么时候服务端才算接收完了。就比如说两个聊天,A必须等B说完才能回应。我们需要加上对应的shutdownOutput() / shutdownInput()
image.png
总体的思路就是:让客户端先用OutputStream,服务端接收。然后用shutdown断开客户端的输出流。接着服务端用OutputStream,客户端接收。
客户端:

  1. public class Client {
  2. public static void main(String[] args) throws IOException {
  3. //连接本机的9999端口,连接成功返回socket对象
  4. Socket socket = new Socket(InetAddress.getLocalHost(),9999);
  5. System.out.println("客户端socket返回: " + socket.getClass());
  6. //通过socket.getOutputStream()获得输出流
  7. OutputStream outputStream = socket.getOutputStream();
  8. outputStream.write("hello,server".getBytes());
  9. socket.shutdownOutput(); //停止输出流(告诉服务端我停止输入了)
  10. InputStream inputStream = socket.getInputStream();
  11. int readLen = 0;
  12. byte[] bytes = new byte[1024];
  13. while((readLen = inputStream.read(bytes))!=-1){
  14. System.out.println(new String(bytes,0,readLen));
  15. }
  16. inputStream.close();
  17. outputStream.close(); //关闭流和socket
  18. socket.close();
  19. }

服务端:

  1. class Server {
  2. public static void main(String[] args) throws IOException {
  3. ServerSocket serverSocket = new ServerSocket(9999);
  4. System.out.println("服务端,9999端口监听,等待连接...");
  5. Socket socket = serverSocket.accept();
  6. InputStream inputStream = socket.getInputStream(); //输入流
  7. int readLen = 0;
  8. byte[] bytes = new byte[1024];
  9. while((readLen = inputStream.read(bytes))!=-1){
  10. System.out.println(new String(bytes,0,readLen));
  11. } //读入数据三部曲
  12. OutputStream outputStream = socket.getOutputStream(); //开始反输出
  13. outputStream.write("hello,client".getBytes());
  14. socket.shutdownOutput(); //停止输出流
  15. outputStream.close(); //断开
  16. inputStream.close();
  17. socket.close();
  18. }
  19. }

把字节流改成字符流版本:
修改输出:

  1. OutputStream outputStream = socket.getOutputStream();
  2. OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); //转换流
  3. BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); //用转换流为中介,把字节流转换成字符流
  4. bufferedWriter.write("hello,server");
  5. bufferedWriter.newLine(); //插入一个换行符,表示写入的内容结束,注意:要求对方使用 readLine方法接收
  6. bufferedWriter.flush(); //刷新

修改输入:

  1. //用转换流作为中介创建 BufferedReader
  2. BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
  3. String s = bufferedReader.readLine(); //用readLine读入
  4. System.out.println(s);

收发文件案例

image.png
image.png
大致思路:把磁盘上图片输入到文件字节数组,然后通过Socket发送到服务端的Socket,重新解码为文件数据,然后放到某个目录上。服务端发送收到图片的消息,客户端显示收到的消息。
解释一下StreamUtils:这是一个自己定义的库,里面有各种各样的方法。我们要用到的是下面这几个:
image.png
image.png
首先是客户端:
1. 连接本机的8888端口。

  1. //连接本机的8888端口,连接成功返回socket对象
  2. Socket socket = new Socket(InetAddress.getLocalHost(),8888);

2. 把要传输的文件先转化为byte数组,存储数据。

  1. String filePath = "d:\\tupian1.png"; //要传输的文件路径
  2. BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
  3. byte[] bytes = StreamUtils.streamToArray(bis); //把文件存到byte数组中

3. 客户端传输数据到服务器。

  1. BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); bos.write(bytes); //输出到服务器 socket.shutdownOutput();
  2. //暂停输出,让服务器回应

4. 接收服务器的回应。

  1. InputStream inputStream = socket.getInputStream(); //接收服务器回应
  2. String s = StreamUtils.streamToString(inputStream); //转化成字符串
  3. System.out.println(s);

5. 关闭各种流与socket。

  1. inputStream.close();
  2. bis.close();
  3. bos.close();
  4. socket.close();

然后是服务器:
1. 创建端口等待连接。

  1. ServerSocket serverSocket = new ServerSocket(9999);
  2. System.out.println("服务端,8888端口监听,等待连接...");
  3. Socket socket = serverSocket.accept(); //返回端口对应的socket,等待连接

2. 接收客户端发来的数据,保存在byte数组里。

  1. BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
  2. //因为图片是二进制文件,因此用InputStream
  3. byte[] bytes = StreamUtils.streamToByteArray(bis);
  4. //接收客户端数据,bytes数组存储的就是图片的内容

3. 把byte数组里的数据还原到指定文件中。

  1. String desFilePath = "src:\\tupian2.png"; //存放文件的位置
  2. BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(desFilePath));
  3. //输出到指定文件中,用输出流
  4. bos.write(bytes);//输出,此时文件已复制完成

4. 告诉客户端自己已经收到数据。

  1. //接下来就是告诉客户端自己收到信息了(用字符流输出汉字信息)
  2. BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
  3. //用OutputStreamWriter修改为字符流输出
  4. writer.write("收到图片");
  5. writer.flush(); //注意刷新
  6. socket.shutdownOutput(); //暂停输出

5. 关闭各种流与socket。

  1. bos.close();
  2. bis.close();
  3. socket.close();
  4. serverSocket.close();

netstat指令

image.png

UDP网络编程

基本介绍

image.png
image.png

基本流程

image.png

应用案例

image.png
接收端A:

  1. public class Receiver {
  2. public static void main(String[] args) throws IOException {
  3. DatagramSocket socket = new DatagramSocket(9999); // 1.创建一个 DatagramSocket对象,准备在9999接收数据
  4. byte[] buf = new byte[1024 * 64];
  5. DatagramPacket packet = new DatagramPacket(buf,buf.length); // 2.构建一个 DatagramPacket 对象,准备存放数据 (一个数据包最大64k)
  6. socket.receive(packet);//3.调用接收方法,将传输的数据填充到 packet中
  7. //4. 可以把packet进行拆包,取出数据并显示。
  8. int length = packet.getLength(); //实际接收到的数据字节长度
  9. byte[] data = packet.getData(); // 接收到的数据(以字节数组形式保存)
  10. String s = new String(data,0,length);
  11. System.out.println(s);
  12. //5.关闭资源
  13. socket.close();
  14. }
  15. }

接收端B:

  1. public class Sender {
  2. public static void main(String[] args) throws IOException {
  3. //1.创建 DatagramSocket对象,准备在9998端口接收数据
  4. DatagramSocket socket = new DatagramSocket(9998);
  5. //注意:在UDP中,即使是发送端也是可以接收数据的。
  6. //2.将需要发送的数据,封装到 DatagramPacket对象
  7. byte[] data = "hello,明天吃火锅".getBytes();
  8. //3. 有内容的DatagramPacket构造器:内容字节数组 数组长度 主机IP 端口
  9. DatagramPacket packet =
  10. new DatagramPacket(data,data.length, InetAddress.getByName("192.168.43.8"),9999);
  11. socket.send(packet);//4.发送内容
  12. socket.close(); //关闭资源
  13. }
  14. }

想要反过来发信息,只需要角色对换重新写一遍send和receive即可。注意 receive和 send方法都是socket的,即使是在9998端口监听的socket,也可以向9999端口发送数据。