前言:本文是初学Java网络编程时所记录的学习笔记,方便以后查阅学习,同时也作为资料总结分享给大家,如果有问题欢迎指出!!

1. 网络编程

1.1 网络编程基本概念

1、网络

将不同区域的电脑连接到一起,组成局域网、城域网或广域网。把分布在不同地理区域的计算机与专门的外部设备用通信线路互联成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息,共享硬件、软件、数据信息等资源

2、计算机网络

是指将地理位置相同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在==网络操作系统、网络管理软件及通信协议的管理和协调下,实现资源共享和信息传递的计算机系统

3、通信协议

计算机网络中实现通信必须有一些约定即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准

4、通信接口

为了使两个结点之间能进行对话,必须在它们之间建立通信工具(即接口),使彼此之间能进行信息交换

接口包括两部分:

  • 硬件装置:实现节点之间的信息传送

  • 软件装置:规定双方进行通信的约定协议


网络分层

  • 由于结点之间联系很复杂,在指定协议时,把复杂成份分解成一些简单的成份,再将他们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系

  • TCP/IP是一个协议族,也是按照层次划分,共四层:应用层,传输层,互连网络层,接口层(物理+数据链路层)

  • OSI网络通信协议模型,是一个参考模型,而TCP/IP协议是事实上的标准。TCP/IP协议参考了OSI模型,但是并没有严格按照OSI规定的七层标准去划分,而只划分了四层,这样会更简单点,当划分太多层时,你很难区分某个协议是属于哪个层次的
    在这里插入图片描述
    这里主要关注传输层 - TCP、UDP


1.2 网络编程三要素

1、IP地址

  • 要想让网络中的计算机能够互相通信,必须为计算机指定一个标识号,通过这个标识号来指定要接受数据的计算机和识别发送的计算机,而IP地址就是这个标识号,也就是设备的标识。

2、端口

  • 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区别这些应用程序呢?如果说IP地址可以唯一的标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序,也就是应用程序的标识。

3、协议

  • 通过计算机网络可以使多台计算机实现连接,位于同一网络中的计算机进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为计算机网络通信协议。它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
  • 常见的协议有TCP协议UDP协议

1.3 IP地址

IP地址:是网络中设备的唯一标识

IP地址分为两大类

  • IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址的长32bit,也就是4个字节。例如一个采用二进制形式的地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“ . ”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做 “点分十进制法”,这显然比1和0容易记得多
  • IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128bit地址长度,每16个字节一组,分成8组十六进制数,这就解决了网络地址资源数量不够的问题

常用DOS命令

  • ipconfig:查看本机IP地址
  • ping IP地址:检查网络是否连通

示例
在这里插入图片描述

在这里插入图片描述
特殊IP地址

  • 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
  • 190.168.0.0 – 192.168.255.255私有地址,属于非注册地址,专门为组织机构内部使用

1.4 InetAddress 的使用

为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用

InetAddress:此类表示Internet协议(IP)地址,用于封装计算机的IP地址和DNS(没有端口信息)

方法名描述
static InetAddress getByName(String host)确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址和域名
String getHostName()获取此IP地址的主机名
String getHostAddress()返回文本显示中的IP地址字符串

示例代码

public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
//        InetAddress address = InetAddress.getByName("DreamDragon");
//        InetAddress address = InetAddress.getByName("10.19.65.91");
        InetAddress address = InetAddress.getByName("www.baidu.com");

        String name = address.getHostName();
        String ip = address.getHostAddress();

        System.out.println("主机名:" + name);
        System.out.println("IP地址: " + ip);
    }
}
主机名:www.baidu.com
IP地址: 39.156.66.18
  • 主机名称可以在 Windows设置 - 系统 - 关于 里找到和重命名(Win10)

1.5 端口

端口相关概念

端口:设备上应用程序的唯一标识

端口号: 用两个字节表示的整数,它的取值范围是0 - 65535

  • 公认端口:0 - 1023之间的端口号用于一些知名的网络服务和应用,比如80端口分配给www,21端口分配给FTP**
  • 注册端口:1024 - 49151 分配给用户进程或应用程序**
  • 动态/私有端口: 49152 - 65535
  • 如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败

端口相关DOS命令

  • 查看所有端口:netstat -ano
  • 查看指定端口:netstat -aon|findstr "80"
  • 查看指定进程:tasklist|findstr "12476"
  • 查看具体程序:使用任务管理器查看PID
    在这里插入图片描述

IntetSocketAddress类

  • 包含IP和端口信息,常用于Socket通信。此类实现IP套接字地址(IP地址+端口号),不依赖任何协议
    在这里插入图片描述

常用构造器

构造方法说明
InetSocketAddress(InetAddress addr, int port)从IP地址和端口号创建套接字地址
InetSocketAddress(int port)创建一个套接字地址,其中IP地址为通配符地址,端口号为指定值
InetSocketAddress(String hostname, int port)根据主机名和端口号创建套接字地址

常用方法

方法说明
InetAddress getAddress()获得 InetAddress
int getPort()获取端口号
String getHostName()获取主机名
public class PostTest {
    public static void main(String[] args) {
        //包含端口
        InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1",8080);
        InetSocketAddress socketAddress2 = new InetSocketAddress("localhost",9000);
        System.out.println(socketAddress.getHostName());	// 获取主机名
        System.out.println(socketAddress.getAddress());		// 获取地址
        System.out.println(socketAddress2.getAddress());
        System.out.println(socketAddress.getPort());		// 获取端口号
        System.out.println(socketAddress2.getPort());
    }
}
  • 关于localhost
    在这里插入图片描述
    图片转载于:https://www.cnblogs.com/zyy98877/p/9266652.html

1.6 URL

URL基本概念

URL: Uniform Resource Locator 统一资源定位符

  • 表示统一资源定位符,指向万维网上的“资源”的指针。用于区分、定位资源

  • 一个标准的URL必须包括:protocol(方案或协议)、host(主机)、port(端口)、path(路径)、parameter( 查询参数)、anchor(锚点)

  • 在www上,每一信息资源都有统一且唯一的地址

  • 如:http://www.google.com:80/index.html,分四部分组成:协议、存放资源的主机域名、端口号、资源文件名


URL类

在这里插入图片描述

构造器说明
URL(String spec)从 String表示形成一个 URL对象
常用方法说明
String getProtocol()获取此 URL的协议名称
String getHost()获取此 URL的主机名(如适用)
int getPort()获取此 URL的端口号
String getPath()获取此 URL的路径部分
String getFile()获取此 URL的文件名
String getQuery()获取参数
String getRef()获取锚点

示例代码

public class URLTest01 {
    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("http://www.baidu.com:80/index.html?uname=dream&age=18#a");
        // 获取四个值:协议、域名、端口、请求资源
        System.out.println("协议:" + url.getProtocol());
        System.out.println("域名|IP:" + url.getHost());
        System.out.println("端口:" + url.getHost());
        System.out.println("请求资源1:" + url.getPath());
        System.out.println("请求资源2:" + url.getFile());
        // 参数
        System.out.println("参数:" + url.getQuery());
        // 锚点
        System.out.println("锚点:" + url.getRef());
    }
}
协议:http
域名|IP:www.baidu.com
端口:www.baidu.com
请求资源1/index.html
请求资源2/index.html?uname=dream&age=18
参数:uname=dream&age=18
锚点:a

URI、URL和URN三者关系

关于URI、URL和URN关系,可以阅读此博文URI、URL和URN关系


1.7 协议

协议:计算机网络中,连接和通信的规则被称为网络通信协议

1、UDP协议

  • 用户数据报协议(User Datagram Protocol)

  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据

  • 由于使用UDP协议消耗资源少,通信效率高,所以通常都会用于音频、视频和普通数据的传输

  • 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议

2、TCP协议

  • 传输控制协议(Transmission Control Protocol)

  • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据出啊u念书。在TCP连接中必须明确客户端与服务器端,由于客户端向服务器端发出连接请求,每次连接的创建都需要经过“三次握手”

三次握手

  • TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

  • 第一次握手:客户端向服务器端发出连接请求,等待服务器确认

  • 第二次握手:服务器端向客户端回送一个响应,通知客户端收到了连接请求

  • 第三次握手:客户端再次向服务器端发送确认信息,确认连接
    在这里插入图片描述

  • 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用非常广泛。例如上传文件、下载文件、浏览网页


2. UDP通信程序

2.1 UDP通信原理

  • UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象

  • 基于UDP协议的通信双方而言,没有客户端和服务器概念

Java提供了DatagramSocket类和DatagramPacket类

  • DatagramSocket:用于发送或接收数据包的套接字

  • DatagramPacket:数据包


DatagramSocket类

  • 此类表示用于发送和接收数据报数据包的套接字

  • 数据报套接字是分组传送服务的发送或接收点

常用构造器

常用构造器说明
DatagramSocket()构造数据报套接字并将其绑定到本地主机上的任何可用端口
DatagramSocket(int port)构造数据报套接字并将其绑定到本地主机上的指定端口

常用方法

常用方法说明
void send(DatagramPacket p)从此套接字发送数据报包
void receive(DatagramPacket p)从此套接字接收数据报包(阻塞式的接收)

DatagramPacket类

  • 该类表示数据报包

  • 数据报包用于实现无连接分组传送服务

常用构造器

常用构造器说明
(接收方) DatagramPacket(byte[] buf, int length)构造一个 DatagramPacket用于接收长度的数据包 length
(发送方)DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)构造用于发送指定长度的数据报包到指定主机的指定端口号上

常用方法

常用方法说明
int getLength()返回要发送的数据的长度或接收到的数据的长度
byte[] getData()返回数据缓冲区

三个概念:数据包(Packet)、数据报(Datagram)和套接字(Socket)

1、数据报(Datagram)

  • 数据报是通过网络传输的数据的基本单元,包含一个报头(header)和数据本身,其中报头描述了数据的目的地以及和其它数据之间的关系。数据报是完备的、独立的数据实体,该实体携带要从源计算机传递到目的计算机的信息,该信息不依赖以前在源计算机和目的计算机以及传输网络间交换。

  • UDP数据报的长度是指包括报头和数据部分在内的总字节数,其中报头长度固定,数据部分可变。数据报的最大长度根据操作环境的不同而各异。从理论上说,包含报头在内的数据报的最大长度为65535字节(64K)

  • 我们在用Socket编程时,UDP协议要求包小于64K,TCP没有限定

2、套接字(Socket)

  • 所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象

  • 从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口

3、数据包(Packet)

  • 包(Packet)是TCP/IP协议通信传输中的数据单位,一般也称“数据包”

2.2 UDP发送数据

发送数据的步骤

1、创建发送端的Socket对象(DatagramSocket)。如果没有指定端口,发送到本地主机所有可用端口(不常用),这里可以采用指定端口构造方法
DatagramSocket()DatagramSocket(int port)|
2、创建数据,并把数据封装成DatagramPacket包裹,数据一定要转成字节数组,同时需要指定IP地址和端口
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
3、调用DatagraSocket对象的方法发送数据包裹
void send(DatagramPacket p)
4、关闭发送端,释放资源
void close()

示例代码一

public class SendDemo {
    public static void main(String[] args) throws IOException {
        // 1、创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();

        // 2、创建数据,并把数据打包
        byte[] bys = "Java YYDS!".getBytes();
        DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("127.0.0.1"), 10086);

        // 3、调用DatagraSocket对象的方法发送数据
        ds.send(dp);

        // 4、关闭发送端
        ds.close();
    }
}

示例代码二

public class UDPClient {
    public static void main(String[] args) throws Exception {
        System.out.println("发送方启动中...");
        
        // 1、使用DatagramSocket指定端口,创建发送端
        DatagramSocket client = new DatagramSocket(8888);
        
        // 2、准备数据,一定要转成字节数组
        String data = "Java YYDS";
        
        // 3、封装成DatagramPacket包裹,需要指定目的地(IP+port)
        byte[] datas = data.getBytes();
        DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 9999));
        
        // 4、发送包裹 void send(DatagramPacket p)
        client.send(packet);
        
        // 5、释放资源
        client.close();
    }
}

2.3 UDP接收数据

接收数据的步骤

1、创建接收端的Socket对象(DatagramSocket),指定端口
DatagramSocket(int port)
2、准备容器,封装成DatagramPacket包裹,用于接收数据
DatagramPacket(byte[] buf, int length)
3、调用DatagramSocket对象的方法,阻塞式接收包裹
void receive(DatagramPacket p)
4、解析数据包,并把数据在控制台显示
byte[] getData()int getLength()
5、关闭接收端,释放资源
void close()

示例代码一

public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
        // 1、创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(10086);

        // 2、创建一个数据包,用于接收数据
        byte[] bys = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bys, bys.length);

        // 3、调用DatagramSocket对象的方法接收数据
        ds.receive(dp);
        
        // 4、解析数据包,并把数据在控制台显示
        System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));

        // 5、关闭接收端,释放资源
        ds.close();
    }
}

示例代码二

public class UDPSever {
    public static void main(String[] args) throws Exception {
        System.out.println("接收方启动中...");
        DatagramSocket server = new DatagramSocket(9999);   // 同一台机器操作时端口名不要冲突
        byte[] container = new byte[1024 * 60];
        DatagramPacket packet = new DatagramPacket(container, 0, container.length);
        server.receive(packet); //阻塞式
        byte[] datas = packet.getData();
        int len = packet.getLength();
        System.out.println(new String(datas, 0, len));
        server.close();
    }
}

注意

1、同一台机器操作时,如果发送包裹的目的地端口和接收端的端口名不一致时,将会导致包裹丢失,接收不到数据

2、如果同一台机器下采用重复端口,会报错BindException: Address already in use: bind端口重复使用


2.4 UDP程序练习

练习一:简单的信息发送

  • 发送端:数据来自键盘录入,知道输入的数据是886,发送数据结束
  • 接收端:因为接收端不知道发送端什么时候停止发送,故采用死循环接收

示例代码

发送端

/**
 * UDP发送数据:
 *      数据来自键盘录入,知道输入的数据是886,发送数据结束
 */

public class SendDemo {
    public static void main(String[] args) throws IOException {
        // 创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();

        // 自己封装键盘录入数据
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        while ((line=br.readLine()) != null) {
            if ("886".equals(line)) {
                break;
            }

            // 创建数据,把数据打包
            byte[] bys = line.getBytes();
            DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("127.0.0.1"), 12345);

            // 调用DatagramSocket对象的方法来发送数据
            ds.send(dp);
        }

        // 关闭发送端
        ds.close();
    }
}

接收端

/**
 * UDP接收数据:
 *      因为接收端不知道发送端什么时候停止发送,故采用死循环接收
 */

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
        // 1、创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(12345);

        while (true) {
            // 2、创建一个数据包,用于接收数据
            byte[] bys = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bys, bys.length);

            // 3、调用DatagramSocket对象的方法接收数据
            ds.receive(dp);

            // 4、解析数据包,并把数据在控制台显示
            // int getLength(): 返回要发送的数据的长度或接收到的数据的长度。
            System.out.println("接收到的数据是:" + new String(dp.getData(), 0, dp.getLength()));
        }

        // 5、关闭接收端
//        ds.close();
    }
}
  • 即使输入”886“,发送端结束运行,但接收端依旧继续运行
    在这里插入图片描述
    在这里插入图片描述

练习二:一对一线上咨询

需求:

  • 完成在线咨询功能
  • 学生和咨询师在线一对一交流
  • 加入多线程,可以双向交流
  • 聊天终止条件为输入"bye"

示例代码

发送端

/**
 * 发送端:使用面向对象封装
 */
public class TalkSend implements Runnable {
    private DatagramSocket client;
    private BufferedReader reader;
    private String toIP;    // 对方的IP地址
    private int toPort;     // 对方的端口

    public TalkSend(int port, String toIP, int toPort) {
        this.toIP = toIP;
        this.toPort = toPort;

        try {
            client = new DatagramSocket(port);
            reader = new BufferedReader(new InputStreamReader(System.in));
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            String data;
            try {
                data = reader.readLine();
                byte[] datas = data.getBytes();
                // 封装成DatagramPacket包裹,指定目的地
                DatagramPacket packet = new DatagramPacket(datas, 0, datas.length,
                        new InetSocketAddress(this.toIP, this.toPort));
                client.send(packet);
                // 终止条件
                if (data.equals("bye")) {
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 释放资源
        client.close();
    }
}

接收端

/**
 * 接收端:使用面向对象封装
 */
public class TalkReceive implements Runnable {
    private DatagramSocket server;
    private String from;

    public TalkReceive(int port, String from) {
        this.from = from;
        try {
            server = new DatagramSocket(port);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            // 准备容器,封装成DatagramPacket包裹
            byte[] container = new byte[1024 * 60];
            DatagramPacket packet = new DatagramPacket(container, 0, container.length);
            try {
                // 阻塞式接收包裹
                server.receive(packet);
                // 分析数据
                byte[] datas = packet.getData();
                int len = packet.getLength();
                String data = new String(datas, 0, len);
                System.out.println(from + " : " + data);
                if (data.equals("bye")) {
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 释放资源
        server.close();
    }
}

学生方

/**
 * 加入多线程,实现双向交流,模拟在线咨询
 */
public class TalkStudent {
    public static void main(String[] args) {
        new Thread(new TalkSend(7777, "localhost", 9999)).start();  // 发送

        new Thread(new TalkReceive(8888, "老师")).start();  //接收
    }
}

老师方

/**
 * 加入多线程,实现双向交流,模拟在线咨询
 */
public class TalkTeacher {
    public static void main(String[] args) {
        new Thread(new TalkReceive(9999, "学生")).start();  //接收

        new Thread(new TalkSend(5555, "localhost", 8888)).start();  // 发送
    }
}
  • 学生和老师端同时输入"bye",程序才会同时结束,只是一端输入"bye",会进行等待操作

这里强调一下,UDP编程属于底层开发,是网络编程,而不是网页编程,网页编程中这种Socket已经被实现。


3. TCP通信程序

3.1 TCP通信原理

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信

  • 使用基于TCP协议的Socket网络编程实现,使用Socket对象来代表两端的通信端口

  • TCP协议基于请求-响应模式,第一次主动发起的程序被称为客户端(Client)程序

  • 第一次通讯中等待连接的程序被称为服务器端(Sercer)程序
    在这里插入图片描述

  • 利用IO流实现数据的传输
    在这里插入图片描述

原理说明及详细步骤

1、在服务端指定一个端口号来创建ServerSocket,并使用accept方法进行侦听,这将阻塞服务器线程,等待用户请求。
2、在客户端指定服务的主机IP和端口号来创建socket,并连接服务端ServerSocket,此时服务端accept方法被唤醒,同时返回一个和客户端通信的socket。
3、在客户端和服务端分别使用socket来获取网络通信输入/输出流,并按照一定的通信协议对socket进行读/写操作。
4、通信完成后,在客户端和服务端中分别关闭socket。

  • 此部分为引用,原文链接:https://blog.csdn.net/weixin_45851945/article/details/114263043

1、服务器端

  • 创建ServerSocket(int port)对象
  • 在Socket上使用accept方法监听客户端的连接请求
  • 阻塞、等待连接的建立
  • 接收并处理请求信息
  • 将处理结果返回给客户端
  • 关闭流和Socket对象

2、客户端

  • 创建Socket(String host, int port)对象
  • 向服务器发送连接请求
  • 向服务器发送服务请求
  • 接受服务结果(服务响应)
  • 关闭流和Socket对象

Java为客户端提供了Socket类,为服务器端提供了ServerSocket类

Socket类

该类实现客户端套接字(也称为“套接字”)。 套接字是两台机器之间通讯的端点
常用构造器

构造方法说明
Socket(InetAddress address, int port)创建流套接字并将其连接到指定IP地址的指定端口号
Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号

常用方法

常用方法说明
OutputStream getOutputStream()返回此套接字的输出流
InputStream getInputStream()返回此套接字的输入流
void shutdownOutput()禁用此套接字的输出流

ServerSocket类

在这里插入图片描述

常用构造器

构造方法说明
ServerSocket(int port)创建绑定到指定端口的服务器套接字

常用方法

常用方法说明
Socket accept()侦听要连接到此套接字并接受它

3.2 TCP发送数据

发送数据的步骤

1、创建客户端的Socket对象(Socket)
Socket(String host, int port)
2、获取输出流,写数据
OutputStream getOutputStream()
3、释放资源

示例代码

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建客户端的Socket对象(Socket)
        // Socket(InetAddress address, int port): 创建流套接字并将其连接到指定IP地址的指定端口号
//        Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 10005);
        // Socket(String host, int port): 创建流套接字并将其连接到指定主机上的指定端口号。
        Socket s = new Socket("127.0.0.1", 10005);

        // 获取输出流,写数据
        // OutputStream getOutputStream(): 返回此套接字的输出流。
        OutputStream os = s.getOutputStream();
        os.write("Java YYDS!".getBytes());
        
        // 释放资源
        s.close();
    }
}

3.3 TCP接收数据

接收数据的步骤

1、创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
2、获取输入流,读数据,并把数据显示在控制台
Socket accept()
3、释放资源

示例代码

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 创建服务器端的Socket对象(ServerSocket)
        ServerSocket ss = new ServerSocket(10005);

        // Socket accept(): 侦听要连接到此套接字并接受它
        Socket s = ss.accept();

        // 获取输入流,读数据,并把数据显示在控制台
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys, 0, len);
        System.out.println("数据是:" + data);

        // 释放资源
        ss.close();
    }
}

这里端口再使用10000会报错,BindException: Address already in use,端口被占用


3.4 TCP通信程序练习

练习一:简单的发送和接收数据

  • 客户端: 发送数据,接收服务器反馈
  • 服务器: 接收数据,给出反馈

示例代码

客户端

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建客户端的Socket对象(Socket)
        Socket s = new Socket("127.0.0.1", 10005);

        // 获取输出流,写数据
        OutputStream os = s.getOutputStream();
        os.write("Java YYDS!".getBytes());

        // 接收服务器反馈
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys, 0, len);
        System.out.println("客户端:" + data);

        // 释放资源
        s.close();
    }
}

服务器端

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 创建服务器端的Socket对象(ServerSocket)
        ServerSocket ss = new ServerSocket(10005);

        // Socket accept(): 侦听要连接到此套接字并接受它
        Socket s = ss.accept();

        // 获取输入流,读数据,并把数据显示在控制台
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys, 0, len);
        System.out.println("服务器:" + data);

        // 给出反馈
        OutputStream os = s.getOutputStream();
        os.write("数据已经收到".getBytes());

        // 释放资源
        ss.close();
    }
}

先运行服务器端,再运行客户端


练习二:数据来自键盘输入,接收到的数据输出到控制台

  • 客户端: 数据来自于键盘录入,直到输入的数据是886, 发送数据结束
  • 服务器:接收到的数据在控制台输出

示例代码

客户端

public class ClientDemo{
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1", 10005);

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 封装输出流对象: 将字节输出流转为字符流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String line;
        while ((line=br.readLine()) != null) {
            if ("886".equals(line)) {
                break;
            }

//            OutputStream os = s.getOutputStream();
//            os.write(line.getBytes());
            bw.write(line);
            bw.newLine();
            bw.flush();
        }
        
        s.close();
    }
}

服务器端

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10005);

        Socket s = ss.accept();

        // 获得输入流
        InputStream is = s.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        String line;
        while ((line= br.readLine()) != null) {
            System.out.println(line);
        }

        ss.close();
    }
}

输入”886“,客户端和服务器端同时结束运行
在这里插入图片描述
在这里插入图片描述


练习三:数据来自键盘输入,接收到的数据写入文本

  • 客户端: 数据来自于键盘录入,知道输入的数据是886, 发送数据结束
  • 服务器端: 接收到的数据写入文本文件

示例代码

客户端

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1", 10005);

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 封装输出流对象: 将字节输出流转为字符流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String line;
        while ((line=br.readLine()) != null) {
            if ("886".equals(line)) {
                break;
            }

            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        s.close();
    }
}

服务器端

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10005);

        // 监听客户端连接,返回一个对应的Socket对象
        Socket s = ss.accept();

        // 接收数据
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        BufferedWriter bw = new BufferedWriter(new FileWriter("s.txt"));

        String line;
        while ((line=br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        bw.close();
        ss.close();
    }
}

在这里插入图片描述


练习四:上传文件

  • 客户端:数据来自于文本文件
  • 服务器:接收到的数据写入文本文件

示例代码

客户端

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1", 10005);

        // 封装文本文件的数据
        BufferedReader br = new BufferedReader(new FileReader("client.txt"));
        // 封装输出流写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

        String line;
        while ((line=br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        br.close();
        s.close();
    }
}

服务器

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10005);

        // 监听客户端连接,返回一个对应的Socket对象
        Socket s = ss.accept();

        // 接收数据
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        BufferedWriter bw = new BufferedWriter(new FileWriter("server.txt"));

        String line;
        while ((line=br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        bw.close();
        ss.close();
    }
}

在这里插入图片描述
在这里插入图片描述


练习五:上传文件,并得到服务器的反馈

  • 客户端:数据来自于文本文件,接收服务器反馈
  • 服务器:接收到的数据写入文本文件,给出反馈
  • 出现问题: 程序一直等待
  • 原因:读数据的方法是阻塞式的
  • 解决方法:1、自定义结束标记;2、使用shutdownOutput() :禁用此套接字的输出流推荐

示例代码

客户端

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1", 10005);

        // 封装文本文件的数据
        BufferedReader br = new BufferedReader(new FileReader("client.txt"));
        // 封装输出流写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

        String line;
        while ((line=br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        // 自定义结束标记
//        bw.write("886");
//        bw.newLine();
//        bw.flush();

        // public void shutdownOutput():禁用此套接字的输出流
        s.shutdownOutput();

        // 接受反馈
        BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String data = brClient.readLine();
        System.out.println("服务器的反馈:" + data);

        br.close();
        s.close();
    }
}

服务器

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10005);

        // 监听客户端连接,返回一个对应的Socket对象
        Socket s = ss.accept();

        // 接收数据
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        BufferedWriter bw = new BufferedWriter(new FileWriter("server.txt"));

        String line;
        while ((line=br.readLine()) != null) {
//            if ("886".equals(line)) {
//                break;
//            }
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        // 给出反馈
        BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        bwServer.write("文件上传成功");
        bwServer.newLine();
        bw.flush();

        bw.close();
        ss.close();
    }
}

练习六:服务器可以接收多个客户端上传的文件,线程封装

  • 客户端: 数据来自文本文件,接收服务器反馈
  • 服务器:接收到的数据写入文本文件,给出反馈,代码用线程封装,为每一个客户端开启一个线程

示例代码

客户端

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1", 10005);

        // 封装文本文件的数据
        BufferedReader br = new BufferedReader(new FileReader("clientDemo.txt"));
        // 封装输出流写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

        String line;
        while ((line=br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        s.shutdownOutput();

        // 接收反馈
        BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String data = brClient.readLine();
        System.out.println("服务器的反馈:" + data);

        br.close();
        s.close();
    }
}

服务器

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 创建服务器Socket对象
        ServerSocket ss = new ServerSocket(10005);

        while (true) {
            // 监听客户端连接,返回一个对应的Socket对象
            Socket s = ss.accept();
            // 为每一个客户端开启一个线程
            new Thread(new ServerThread(s)).start();
        }
    }
}
public class ServerThread implements Runnable {
    private Socket s;

    public ServerThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        // 接收数据写到文本文件
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//            BufferedWriter bw = new BufferedWriter(new FileWriter("serverDemo.txt"));

            // 解决名称冲突问题
            int count = 0;
            File file = new File("serverDemo["+count+"].txt");
            while (file.exists()) {
                count++;
                file = new File("serverDemo["+count+"].txt");
            }
            BufferedWriter bw = new BufferedWriter(new FileWriter(file));

            String line;
            while ((line=br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
                bw.flush();
            }

            // 给出反馈
            BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            bwServer.write("文件上传成功");
            bwServer.newLine();
            bwServer.flush();

            // 释放资源
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 服务器不close,一直运行,开启多个客户端,可以生成多个文本文件
  • 注意文件名冲突问题,new多个File对象
    在这里插入图片描述

4. 综合案例:在线聊天室

参见此篇博客《JAVA实现在线聊天室(层层递进)》

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐