计算机网络编程

网络编程

Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机, Java 仍是企业和开发人员的首选开发平台。
  

课程内容的介绍

1. 计算机网络基础
2. Socket ServerSocket
3. TCP Socket 通信模型
4. UDP 编程
  

一、计算机网络基础

1.什么是计算机网络
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大 、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息,共享硬件、软件、数据信息等资源。

  
2.了解网络通信协议
2.1.网络通信协议
要使计算机连成的网络能够互通信息,需要对数据传输速率、传输代码、代码结构、传输控制步骤、出错控制等制定一组标准,这一组共同遵守的通信标准就是网络通信协议,不同的计算机之间必须使用相同的通讯协议才能进行通信。

  
2.2 常见的网络通信协议
TCP/IP, 使用最为广泛的通讯协议。
TCP/IP 是英文 Transmission Control Protocol/Internet Protocol 的缩写,意思是 传输控制协议 / 网际协议”
  

    
2.2.1三次握手
TCP 协议建立连接需要三次会话 ( 握手 )

   
1. 第一次握手,客户端连接服务器。
2. 第二次握手,服务器接收了客户端的连接请求,服务器发送给客户端的确认消息。
3. 第三次握手,客户端获取到了服务器的确认信息,知道了服务已经知道我要连接他了,但是服务器还不知道客户端已经知道了,所以客户端发送给服务器消息说我们已经接受到了你的确认信息。
经过这三次握手,那么客户端和服务器都知道了要连接。
  
2.2.2四次挥手
TCP 协议中如果客户端要断开连接那么需要进行四次挥手操作。

  
1. 第一次,客户端发送请求关闭的消息给服务器。
2. 第二次,服务器接受到了客户端的消息,服务器发送消息给客户端确认(我收到了你的关闭请求,但是我数据还没有传完,等我传会告诉你的)。
3. 第三次,服务器给客户端要传输的数据以及传完了,服务器发送消息给客户端数据传完了,客户端你可以断开连接了。
4. 第四次,客户端发送消息给服务器,我要断开了,你也断开吧。
  
3.IP和端口号
3.1 什么是IP
网络中每台计算机的一个标识号,是一个逻辑地址, 127.0.0.1 localhost 代表本机地址。
IP 地址由四段组成,每个字段是一个字节, 8 位,最大值是 255 ,IP地址由两部分组成,即 网络地址 主机地址 。网络地址表示其属于互联网的哪一个网络,主机地址表示其属于该网络中的哪一台主机。二者是主从关系。IP 地址的四大类型标识的是网络中的某台主机。IPv4的地址长度为 32 位,共 4 个字节,但实际中我们用 点分十进制 记法。 192.168.1.1
   
3.2 分类
IP 地址根据网络号和主机号来分,分为 A B C 三类及特殊地址 D E 0 和全 1 的都保留不用。
A (1.0.0.0-126.0.0.0) (默认子网掩码: 255.0.0.0 0xFF000000 )第一个字节为网络号,后三个字节为主机号。该类IP 地址的最前面为 “0” ,所以地址的网络号取值于 1~126 之间。一般用于大型网络。
B (128.0.0.0-191.255.0.0) (默认子网掩码: 255.255.0.0 0xFFFF0000 )前两个字节为网络号,后两个字节为主机号。该类IP 地址的最前面为 “10” ,所以地址的网络号取值于 128~191 之间。一般用于中等规模网络。
C (192.0.0.0-223.255.255.0) (子网掩码: 255.255.255.0 0xFFFFFF00 )前三个字节为网络号,最后一个字节为主机号。该类IP 地址的最前面为 “110” ,所以地址的网络号取值于 192~223 之间。一般用于小型网络。
D :是多播地址。该类 IP 地址的最前面为 “1110” ,所以地址的网络号取值于 224~239 之间。一般用于多路广播用户[1]
E :是保留地址。该类 IP 地址的最前面为 “1111” ,所以地址的网络号取值于 240~255 之间。
  

     
Java 中对 IP 的操作。
package com.bobo.ip;

import java.net.InetAddress;

public class IpDemo01 {

    /**
     * Java中对IP的封装操作
     * @param args
     */
    public static void main(String[] args) throws Exception {
        System.out.println(InetAddress.getLocalHost());
        System.out.println(InetAddress.getByName("127.0.0.1"));

    }
}
    
科普:为什么局域网中的 ip 地址都是 192.168 开头的?
私有地址
A 类地址: 10.0.0.0 - 10.255.255.255
B 类地址: 172.16.0.0 - 172.31.255.255
C 类地址: 192.168.0.0 - 192.168.255.255
    

3.3 IPv4IPv6
目前的全球因特网所采用的协议族是TCP/IP协议族。IP是TCP/IP协议族中网络层的协议,是TCP/IP协议族的核心协议。目前IP协议的版本号是4(简称为IPv4),发展至今已经使用了30多年。
IPv4的地址位数为32位,也就是最多有2的32次方的电脑可以联到Internet上。
近十年来由于互联网的蓬勃发展,IP位址的需求量愈来愈大,使得IP位址的发放愈趋严格,各项资料显示全球IPv4位址可能在2005至2008年间全部发完。
  
什么是IPv6?
IPv6是下一版本的互联网协议,也可以说是下一代互联网的协议,它的提出最初是因为随着互联网的迅速发展,IPv4定义的有限地址空间将被耗尽,地址空间的不足必将妨碍互联网的进一步发展。
为了扩大地址空间,拟通过IPv6重新定义地址空间。IPv6采用128位地址长度,几乎可以不受限地提供地址。按保守方法估算IPv6实际可分配的地址,整个地球的每平方米面积上仍可分配1000多个地址。在IPv6的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4中解决不好的其它问题,主要有端到端IP连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。
  
IPv6与IPv4相比有什么特点和优点?
更大的地址空间。IPv4中规定IP地址长度为32,即有2^32-1个地址;而IPv6中IP地址的长度为128,即有2^128-1个地址。
更小的路由表。IPv6的地址分配一开始就遵循聚类(Aggregation)的原则,这使得路由器能在路由表中用一条记录(Entry)表示一片子网,大大减小了路由器中路由表的长度,提高了路由器转发数据包的速度。
增强的组播(Multicast)支持以及对流的支持(Flow-control)。这使得网络上的多媒体应用有了长足发展的机会,为服务质量(QoS)控制提供了良好的网络平台。
加入了对自动配置(Auto-configuration)的支持。这是对DHCP协议的改进和扩展,使得网络(尤其是局域网)的管理更加方便和快捷。
更高的安全性.在使用IPv6网络中用户可以对网络层的数据进行加密并对IP报文进行校验,这极大的增强了网络安全。
  
3.4 端口号
具有网络功能的应用软件的标识号。
特点:
1. 端口是一个软件结构,被客户程序或服务程序用来发送和接收数据,一台服务器有 256*256 个端口。
2. 0-1023 是公认端口号,即已经公认定义或为将要公认定义的软件保留的。
3. 1024-65535 是并没有公共定义的端口号,用户可以自己定义这些端口的作用。
4. 端口与协议有关: TCP UDP 的端口互不相干。
  

二、网络通信

1.SocketServerSocket
1.1 什么是Socket
两个应用程序可以通过一个双向的网络通信连接实现数据的交换,这个双向的链路的一端我们称为一个Socket
  
1.2 Socket案例
1.2.1 基本连接操作
   
服务端的创建
package com.bobo.socket01;

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

public class TestServerSocket {

    /**
     * Socket的第一个案例
     *
     * @param args
     */
    public static void main(String[] args) {

        try {
            // 创建一个ServerScoket对象  如果服务器关闭了,那么我们是连接不上的
            ServerSocket ss = new ServerSocket(8888);
            System.out.println("服务器启动了....");
            // 接受客户端的访问
            while(true){
                Socket s = ss.accept(); // 这是一个阻塞的方法,等待客户端的访问。
                System.out.println(s.getInetAddress().getHostAddress() + " : 客户端来访问了");
                s.close();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  
客户端的创建
package com.bobo.socket01;

import java.net.Socket;
import java.rmi.server.ExportException;

public class TestClientSocket {

    /**
     * Socket 客户端程序
     * @param args
     */
    public static void main(String[] args) {
        try{
            // 127.0.0.1 / localhost 表示的当前的主机
            // 获取一个Socket对象 指定要访问的服务器 ip+端口 可以定位到要访问的是哪台计算机上的服务
            Socket socket = new Socket("192.168.8.71",8888);
            System.out.println("客户端开始链接了...");
            socket.close();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}
  
当前计算机的描述
1. 127.0.0.1
2. localhost
3. 当前电脑分配的 IP(192.168.8.71)
  
1.2.2 客户端发送消息给服务器
通过字节输入输出流实现。
  
服务端代码
package com.bobo.socket02;

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

public class TestServerSocket {

    /**
     * 服务端 接收客户端的消息
     * @param args
     */
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8899);
            while(true){
                // 等待获取客户端的连接
                Socket s = ss.accept();
                System.out.println(s.getInetAddress().getHostAddress()+"连接了...");
                // 获取一个字节输入流,获取客户端传递的信息
                InputStream inputStream = s.getInputStream();
                byte[] b = new byte[1024];
                int num = 0 ;
                while((num = inputStream.read(b)) != -1){
                    System.out.println(new String(b,0,num));
                }
                // 关闭相关的资源
                inputStream.close();
                s.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}
     
客户端代码
package com.bobo.socket02;

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

public class TestClientSocket {

    /**
     * 客户端
     *    发送消息到服务器
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",8899);
            // 通过socket对象获取一个字节输出流
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("服务器,你好啊,我是XXXXX".getBytes());
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  
效果:

    
用缓冲流来操作。
  
服务端:
package com.bobo.socket03;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class TestServerSocket {

    /**
     * 服务端 接收客户端的消息
     * @param args
     */
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8899);
            while(true){
                // 等待获取客户端的连接
                Socket s = ss.accept();
                System.out.println(s.getInetAddress().getHostAddress()+"连接了...");
                BufferedReader bufferedReader = new BufferedReader(
                        new InputStreamReader(s.getInputStream()));
                System.out.println(bufferedReader.readLine());
                bufferedReader.close();
                s.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}
     
客户端代码
package com.bobo.socket03;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class TestClientSocket {

    /**
     * 客户端
     *    发送消息到服务器
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",8899);
            // 通过socket对象获取一个字节输出流
            BufferedWriter bufferedWriter = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write("哈哈哈哈哈....");
            bufferedWriter.newLine(); // 必须要显示的调用
            bufferedWriter.flush();
            bufferedWriter.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  
输出效果

  
1.2.3 服务端发送消息给客户端
  
服务端代码:
package com.bobo.socket04;

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

public class TestServerSocket {

    /**
     * 服务端
     *    发送消息给客户端
     * @param args
     */
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8989);
            while(true){
                Socket s = ss.accept();
                System.out.println(s.getInetAddress().getHostAddress()+" 连接了");
                // 服务器发送消息给客户端
                BufferedWriter bufferedWriter = new BufferedWriter(
                        new OutputStreamWriter(s.getOutputStream()));
                bufferedWriter.write(s.getInetAddress().getHostAddress()+" 你好啊,你的连接我已经收到了");
                bufferedWriter.newLine();
                bufferedWriter.flush();
                bufferedWriter.close();
                s.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
    
客户端代码
package com.bobo.socket04;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class TestClientSocket {

    /**
     * 客户端 接收服务的消息
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket s  = new Socket("localhost",8989);
            System.out.println("客户端启动了...");
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(s.getInputStream()));
            System.out.println(bufferedReader.readLine());
            bufferedReader.close();
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
   
输出结果:

    
1.2.4 课堂案例
客户端接收键盘输入信息,然后将该信息发送给服务器,服务器读取客户端发送来的信息。
    
服务端代码
package com.bobo.socket05;

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

public class TestServerSocket {

    /**
     * 服务端
     *     接收客户端的消息
     * @param args
     */
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            while(true){
                Socket s = ss.accept();
                BufferedReader bufferedReader = new BufferedReader(
                        new InputStreamReader(s.getInputStream()));
                System.out.println(s.getInetAddress().getHostAddress()+":"+bufferedReader.readLine());
                bufferedReader.close();
                s.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
    
客户端代码
package com.bobo.socket05;

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

public class TestClientSocket {

    /**
     * 客户端接收键盘输入信息,然后将该信息发送给服务器,服务器读取客户端发送来的信息
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",9999);
            System.out.println("客户端启动了,请输入要发送的消息:");
            // 接收键盘输入
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

            // 客户端发送消息
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write(bufferedReader.readLine());
            bufferedWriter.newLine();
            bufferedWriter.flush();
            bufferedWriter.close();
            bufferedReader.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  
效果

  
1.2.5 一对一聊天实现
实现一个客户端发送消息给服务器,然后服务器获取到消息后再返还消息给客户端,一对一通信的效果。
    
服务端代码:
package com.bobo.socket06;

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

public class TestServerSocket {

    /**
     * 服务端
     * @param args
     */
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8080);
            while(true){
                Socket s = ss.accept();
                // 服务端通过键盘录入的方式 返回消息给客户端
                BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
                // 读取客户端传递的消息
                BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
                // 返回消息给客户端
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
                // 实现频繁读取写入的效果
                while(true){
                    System.out.println(s.getInetAddress().getHostAddress()+":" + br.readLine()) ;
                    // 返回信息给客户端q
                    bw.write(input.readLine());
                    bw.newLine();
                    bw.flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
      
客户端代码:
package com.bobo.socket06;

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

public class TestClientSocket {

    /**
     * 客户端
     *    实现和服务器一对一的聊天
     *
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",8080);
            // 接收键盘输入
            BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
            // 读取信息
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 写入信息
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

            while(true){
                bw.write(input.readLine());
                bw.newLine();
                bw.flush();
                System.out.println("服务器说:" + br.readLine());
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  
效果:

  
效果虽然是实现了,但是总的体验还是不是很好,因为是在读写在一个线程中,就造成了读写操作的阻塞。
  
1.2.6 自由聊天的实现
在上面的案例中客户端和服务器的读写操作同一个线程中进行的,所以会造成消息的阻塞,那么我们可以结合前面讲过的多线程的知识来解决当前的问题。将读的操作交给一个线程,将写的操作交给一个线程,那么读写之间就没有阻塞现象了。具体实现如下:
    
读操作的线程:
package com.bobo.socket07.thread;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * 专门用来负责读取数据的线程
 *     线程只需要获取到对应的Socket对象
 *     就可以获取Socket中的数据及向Socket中写入数据
 */
public class ReadSocketThread implements Runnable{

    private Socket socket;

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

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while(true){
                String str = br.readLine();
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
    
写操作的线程
package com.bobo.socket07.thread;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * 专门用来发送数据的线程
 */
public class WriterSocketThread implements Runnable {

    private Socket socket;

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

    @Override
    public void run() {
        try{
            // 获取键盘数据
            BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
            // 输出信息
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            while(true){
                String str = input.readLine();
                bw.write(str);
                bw.newLine();
                bw.flush();
            }
        }catch (Exception e){

        }
    }
}
    
服务端代码
package com.bobo.socket07;


import com.bobo.socket07.thread.ReadSocketThread;
import com.bobo.socket07.thread.WriterSocketThread;

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

public class TestServerSocket {

    /**
     * 服务器
     *    客户端和服务器实现自由聊天
     * @param args
     */
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(7891);
            while(true){
                Socket s = ss.accept();
                // 读客户端数据和写客户端数据都交给子线程去处理
                // 读取数据的线程
                new Thread(new ReadSocketThread(s)).start();
                // 写入数据的线程
                new Thread(new WriterSocketThread(s)).start();
                // 读写操作分别交给两个不同的线程来处理,就不会出现 读取必须要等待输入完成后再执行的情况
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  
客户端代码
package com.bobo.socket07;

import com.bobo.socket07.thread.ReadSocketThread;
import com.bobo.socket07.thread.WriterSocketThread;

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

public class TestClientSocket {

    /**
     * 实现客户端和服务自由聊天
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",7891);
            // 读取数据的线程
            new Thread(new ReadSocketThread(socket)).start();
            new Thread(new WriterSocketThread(socket)).start();

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

    
1.2.7 有退出的自由聊天
上面的案例除了显示的把服务关掉之外程序会一直运行,那么如果我们要显示的断开聊天,我们只需要在读写线程中添加断开的逻辑即可。
    
读取数据的线程
package com.bobo.socket08.thread;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class ReadSocketThread implements Runnable{

    private Socket socket;

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

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while(true){
                String str = br.readLine();
                System.out.println(socket.getInetAddress().getHostAddress()+":" + str);
                // 添加退出聊天的逻辑
                if("exit".equals(str)){ // 表示退出
                    System.out.println(socket.getInetAddress().getHostAddress()+"退出了...");
                    br.close();
                    break; // 结束循环,终止线程
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
    
写入数据的线程
package com.bobo.socket08.thread;

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

public class WriterSocketThread implements Runnable{

    private Socket socket;

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

    @Override
    public void run() {

        try {
            BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            while(true){
                String str = input.readLine();
                bw.write(str);
                bw.newLine();
                bw.flush();
                // 如果我发送的是 exit 的话,那么读到这个信息的 会退出,此时发送也应该要退出
                if("exit".equals(str)){
                    // 读取数据的线程也要退出
                    System.out.println(socket.getInetAddress().getHostAddress()+"退出了");
                    bw.close();
                    input.close();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  

三、TCP Socket 通信模型

1.通信原理

   
2.课堂案例
实现 TCP 通信模型实现客户端 1 1 的通信,实现案例。
读写的线程和上面案例是一样的,就不再复制了。
    
客户端代码
package com.bobo.socket09;

import com.bobo.socket09.thread.ReadSocketThread;
import com.bobo.socket09.thread.WriterSocketThread;

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

public class TestClientSocket {

    /**
     * 实现客户端对客户端的一对一聊天
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",7799);
            new Thread(new ReadSocketThread(socket)).start(); // 启动读取信息的线程
            new Thread(new WriterSocketThread(socket)).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
    
服务器代码
package com.bobo.socket09;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class TestServerSocket {

    /**
     * ServerSocket
     *    模拟客户端和客户端一对一的聊天
     * @param args
     */
    public static void main(String[] args) {
        // 记录以及建立连接的Socket
        List<Socket> list = new ArrayList<>();
        try {
            ServerSocket ss = new ServerSocket(7799);
            for(int i = 0 ; i < 2 ; i++){
                Socket s = ss.accept();
                // 客户端建立连接后 将连接对应的Socket对象保存起来
                list.add(s);
            }
            // 第一个Socket接收到信息之后 应该将信息在第二个Socket中输出
            // 第二个Socket接收到信息之后 应该将信息在第一个Socket中输出
            new Thread(new ServerThread(list.get(0),list.get(1))).start();
            new Thread(new ServerThread(list.get(1),list.get(0))).start();

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

/**
 * ServerSocket端的线程
 *   专门处理信息的中转
 */
class ServerThread implements  Runnable{

    private Socket s1 ; // 发送者
    private Socket s2 ; // 接收者

    public ServerThread(Socket s1, Socket s2) {
        this.s1 = s1;
        this.s2 = s2;
    }

    @Override
    public void run() {
        try {
            while(true){
                BufferedReader br = new BufferedReader(new InputStreamReader(s1.getInputStream()));
                // 获取 发送者发送的消息
                String str = br.readLine();
                // 将消息转发给另一个客户端
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s2.getOutputStream()));
                bw.write(str);
                bw.newLine();
                bw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  
IDEA 中默认是不支持同一个主方法执行多次的,要并发执行需要如下设置。

    
3.文件上传下载
文件上传:客户端将 File 传输给服务器, File 会保存在服务中。
文件下载:客户端从服务器获取 File
   
第一种实现的方式:
package com.bobo.socket10;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class TestServerSocket {

    /**
     * ServerSocket
     *    接收客户端传递的文件
     * @param args
     */
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            while(true){
                Socket s = ss.accept();
                // 读取文件
                BufferedInputStream bi = new BufferedInputStream(s.getInputStream());
                // 将上传的文件保存到服务指定的位置
                BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("D:/aaa.txt"));
                // 又是一个文件复制
                byte[] b = new byte[1024*1024];
                int num = 0;
                while((num = bi.read(b)) != -1){
                    bo.write(b,0,num);
                }
                bi.close();
                bo.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.bobo.socket10;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;

public class TestClientSocket {


    /**
     * Client
     *    发送文件到服务器中  上传操作
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",9999);
            // 先读取某个文件
            BufferedInputStream bi = new BufferedInputStream(new FileInputStream("d:/IO/1.mp4"));
            // 将读取的文件写入到Socket中
            BufferedOutputStream bo = new BufferedOutputStream(socket.getOutputStream());
            // 文件的复制
            byte[] b = new byte[1024*1024];
            int num = 0 ;
            //bo.write("1.mp4".getBytes());
            while((num = bi.read(b)) != -1){
                bo.write(b,0,num);
            }
            bo.close();
            bi.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
  
这种实现方式我们发现服务端可以获取到客户端传递的数据,但是并不清楚传递的是什么类型的数据,名称叫什么也不知道,所以处理起来比较麻烦。
package com.bobo.socket11;

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

public class TestClientSocket {

    /**
     * 客户端
     * @param args
     */
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",9988);
            File file = new File("d:/IO/1.mp4");
            // 读取文件
            BufferedInputStream bi = new BufferedInputStream(new FileInputStream(file));
            // 文件写入Socket ObjectOutputStream
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            // 先写入文件名称
            oos.writeUTF(file.getName());
            oos.writeLong(file.length());
            // 文件的传输
            byte[] b = new byte[1024*1024];
            int num = 0;
            while((num = bi.read(b)) != -1){
                oos.write(b,0,num);
            }
            // 关闭资源
            oos.close();
            bi.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.bobo.socket11;

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

public class TestServerSocket {

    /**
     * 服务端
     * @param args
     */
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9988);
            while(true){
                Socket s = ss.accept();
                // 读取文件 ObjectInputStream
                ObjectInputStream bis = new ObjectInputStream(s.getInputStream());
                // 获取文件名称
                String fileName = bis.readUTF();
                // 获取文件的大小
                long length = bis.readLong();
                System.out.println("文件名称:" + fileName + "文件大小:" + length);

                // 保存文件到服务器
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("d:/", fileName)));
                byte[] b = new byte[1024*1024];
                int num = 0;
                int i = 1 ;
                while((num = bis.read(b)) != -1){
                    bos.write(b,0,num);
                    System.out.println("保存的进入:" + (1024*1024*i)/length * 100 + "%" );
                    i++;
                }
                bis.close();
                bos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
   

四、了解UDP

1.UDP协议
UDP(User Datagram Protocol) 用户数据报协议, UDP TCP 位于同一层 - 传输层,但它对于数据包的顺序错误或重发没有TCP 来的可靠。
TCP: 相当于打电话。
UDP: 相当于发电报。
  
UDP 是一种面向无连接的通信协议。

UDP 向应用程序提供了一种发送封装的原始 IP 数据报的方法,并且发送时无需建立连接,不保证可靠数据的传输。
    
2.TCP协议和UDP协议的差别

  
面向无连接的数据传输,不可靠的,但效率高。
一次发送的数据不能超过 64KB

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/238898.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

射频功率放大器的参数有哪些

射频功率放大器是射频通信系统中重要的组件&#xff0c;用于将输入的射频信号放大到需要的功率水平。在设计和选择射频功率放大器时&#xff0c;需要考虑多种参数。下面西安安泰将详细介绍射频功率放大器的常见参数。 1、P1dB功率压缩点 当放大器的输入功率比较低时&#xff0c…

2024年AI云计算专题研究报告:智算带来的变化

今天分享的人工智能系列深度研究报告&#xff1a;《2024年AI云计算专题研究报告&#xff1a;智算带来的变化》。 &#xff08;报告出品方&#xff1a;华泰证券&#xff09; 报告共计&#xff1a;32页 Al 云计算 2024:关注智算带来的新变化 通过对海内外主要云厂商及其产业链…

Godot Engine:跨平台游戏开发的新境界 | 开源日报 No.92

godotengine/godot Stars: 62.6k License: MIT Godot Engine 是一个功能强大的跨平台游戏引擎&#xff0c;可用于创建 2D 和 3D 游戏。它提供了一套全面的常见工具&#xff0c;让用户可以专注于制作游戏而不必重复造轮子。该引擎支持将游戏一键导出到多个平台上&#xff0c;包…

crmeb后台自定义菜单并生成代码

crmeb v5 版本&#xff0c; 前后端分端 后台菜单的生成 进入后台界面之后&#xff0c;我们可以看到界面如下 找到 维护->开发配置->权限维护->添加规则按扭 我们要在设置的 菜单之下&#xff0c;添加一个 基础配置的 子菜单 提交之后&#xff0c;刷新页面就会在列…

在开发微信小程序的时候,报错navigateBack:fail cannot navigate back at firstpage

这个错误的意思是&#xff1a;在这个页面已经是第一个页面了&#xff0c;没办法再返回了 报错原因 这个错误原因其实也简单&#xff0c;就是在跳转的时候使用了wx.redirectTo()&#xff0c;使用wx.redirectTo()相当于重定向&#xff0c;不算是从上一个页面跳转过来的&#xf…

消费升级:无人零售的崛起与优势

消费升级&#xff1a;无人零售的崛起与优势 随着人们生活水平的提高&#xff0c;消费内容正在从生存型消费转向以精神体验和享乐为主的发展型消费。社会居民的消费结构不断变迁&#xff0c;明显呈现消费升级趋势。个性化和多元化消费势头正在崛起&#xff0c;特别是无人零售的自…

nextcloud如何将一个文件共享给所有人

nextcloud能够将文件/文件夹共享给某个用户或者用户组或者生成链接分享&#xff0c;但是无法直接将某个文件共享给nextcloud内部所有用户&#xff0c;并且nextcloud只有分组的概念&#xff0c;没有分组上下级的概念。 我们可以一个用户一个用户的共享&#xff0c;或者创建一个…

使用rancher rke快速安装k8s集群

概述 Rancher Kubernetes Engine&#xff08;RKE&#xff09;是一个用于部署、管理和运行Kubernetes集群的开源工具。旨在简化Kubernetes集群的部署和操作。 RKE具有以下特点和功能&#xff1a; 简化的部署过程 RKE提供了一个简单的命令行界面&#xff0c;使您可以轻松地部署…

WhatsApp全球获客怎么做?

一、导语 随着全球数字化趋势的加速&#xff0c;WhatsApp作为一种即时通讯工具&#xff0c;已经成为了连接全球用户的桥梁。 对于企业和营销人员来说&#xff0c;利用WhatsApp拓展全球业务是一种非常有效的策略&#xff0c;本文将为您揭示WhatsApp全球获客的秘密&#xff0c;…

【pytest】单元测试文件的写法

前言 可怜的宾馆&#xff0c;可怜得像被12月的冷雨淋湿的一条三只腿的黑狗。——《舞舞舞》 \;\\\;\\\; 目录 前言test_1或s_test格式非测试文件pytest.fixture()装饰器pytestselenium test_1或s_test格式 要么 test_前缀 在前&#xff0c;要么 _test后缀 在后&#xff01; …

从头到尾的数据之旅

目录 引言 链表介绍 单向链表的接口实现 结构 创建节点 头插 尾插 头删 尾删 打印 节点查找 节点前插入 节点删除 内存释放 总结 引言 在前面的学习中&#xff0c;我们深入了解了顺序表&#xff0c;通过其增删查改的操作&#xff0c;我们发现了顺序表在某些情况…

MistralAI发布全球首个MoE大模型-Mixtral 8x7B,创新超越GPT-4

引言 MistralAI&#xff0c;一家法国的初创企业&#xff0c;近期在AI界引发了轰动&#xff0c;刚刚发布了全球首个基于MoE&#xff08;Mixture of Experts&#xff0c;混合专家&#xff09;技术的大型语言模型——Mistral-8x7B-MoE。这一里程碑事件标志着AI技术的一个重要突破…

【文心一言】使用飞桨 AI Studio 快速搭建,看图识猜成语应用

目录 一、背景二、实践三、创建应用3.1、零代码开发3.2、应用名称3.2、模型训练3.3、开始训练 四、应用部署4.1、发布项目4.2、搜索应用4.3、应用部署4.4、获取令牌4.4、导入依赖4.5、配置CORS4.6、使用测试API4.7、运行4.8、测试API接口4.9、前端API接口 五、启动前端5.1、模块…

百度文库下载要用券?Kotlin爬虫几步解决

百度作为国内知名的网站&#xff0c;尤其是文库里面有各种丰富的内容&#xff0c;对我们学习生活都有很大的帮助&#xff0c;就因为其内容丰富&#xff0c;如果看见好用有意思的文章还用复制粘贴等方式就显得有点落后了&#xff0c;今天我将用我所学的爬虫知识给你们好好上一课…

git 相关操作说明

1.先下载git相关软件 下载地址&#xff1a; https://git-scm.com/download/win下载其中一个安装 2.打开gitee网站&#xff0c;注册账号 3.打开个人中心&#xff0c;选择ssh公钥&#xff0c;查看如何生成公钥 4.生成公钥后&#xff0c;添加相应的公钥 具体仓库操作 1.第一…

Java二十一章 网络通信

1 网络程序设计基础 网络程序设计编写的是与其他计算机进行通信的程序。 局域网与互联网 服务器是指提供信息的计算机或程序&#xff0c;客户机是指请求信息的计算机或程序。网络用于连接服务器与客户机&#xff0c;实现两者间的相互通信。 网络协议 网络协议规定了计算机…

数字孪生的项目类型

数字孪生是一种通过数字模型对实际系统、产品或过程进行实时仿真和监测的技术。它可以在虚拟环境中模拟和反映现实世界中的物理对象、系统或过程。数字孪生技术有广泛的应用领域&#xff0c;以下是一些数字孪生可以涉及的项目类型&#xff0c;希望对大家有所帮助。北京木奇移动…

STM32储存器和总线构架

一、引言 本篇文章旨在介绍STM32小容量、中容量和大容量的储存器和系统构架&#xff0c;文中涉及到一些专有名词和概念较为抽象和陌生&#xff0c;建议读者能够查阅相关资料和知识加深了解。 二、正文 &#xff08;一&#xff09;、系统构架 在小容量、中容量和 大容量产品中…

犹豫不决先排序,步步紧逼双指针---力扣刷题

目录 第一题&#xff1a;和为s的两个数 第二题&#xff1a;和为0的三个数 第三题&#xff1a;四数之和 第一题&#xff1a;和为s的两个数 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 思路&#xff1a; 法一先想到暴力枚举&#xff0c;即利用…

GoEasy使用手册

GoEasy官网 登录 - GoEasy 即时通讯聊天案例 GoEasy - GoEasy (gitee.com) 注意事项 接口使用人数上限为15&#xff0c;超出之后会请求超时返回408状态码&#xff0c;可以新建一个应用用来更换common Key 创建应用 ​ 添加应用名称&#xff0c;其余默认&#xff0c;点击…
最新文章