Java网络编程

本文是课上资料的总结非原创没有转载地址

Java网络编程

什么是网络

  • 由点和线构成,表示诸多对象间的相互联系。

计算机网络

  • 为实现资源共享和信息传递,通过通信线路连接起来的若干主机(Host)。
  • 网络分类:广域网、城域网、局域网
    • 互联网:(Internet)点与点相连;
    • 万维网:(WWW - World Wide Web)端与端相连;
    • 物联网:(IoT - Internet of things)物与物相连;
    • 网络编程:让计算机与计算机之间建立连接、进行通信。

网络模型

OSI参考模型(七层)

  • ISO组织发布的OSI(Oper System Interconnection)开放式系统互联。
  • 层层独立、下层为上层服务
    OSI参考模型
    • 第七层(应用层):应用层负责文件访问和管理、可靠运输服务、远程操作服务。(HTTP、FTP、SMTP)
    • 第六层(表示层):表示层负责定义转换数据格式及加密,允许选择以二进制或ASCII格式传输。
    • 第五层(会话层):会话层负责使应用建立和维持会话,使通信在失效时继续恢复通信。(断点续传)
    • 第四层(传输层):传输层负责是否选择差错恢复协议、数据流重用、错误顺序重排。(TCP、UDP)
    • 第三层(网络层):网络层负责定义了能够标识所有网络节点的逻辑地址。(IP地址)
    • 第二层(链路层):链路层在物理层上,通过规程或协议(差错控制)来控制传输数据的正确性。(MAC)
    • 第一层(物理层):物理层为设备之间的数据通信提供传输信号和物理介质。(双绞线、光导纤维)

TCP/IP参考模型(四层)

  • 一组用于实现网络互连的通信协议,将协议分层四个层次。
    TCP/IP参考模型
    • 第四层(应用层):应用层负责传送各种最终形态的数据,是直接与用户打交道的层,典型协议是HTTP、FTP等。
    • 第三层(传输层):传输层负责传送文本数据,主要协议是TCP、UDP协议。
    • 第二层(网络层):网络层负责分配地址和传送二进制数据,主要协议是IP协议。
    • 第一层(接口层):接口层负责建立电路连接,是整个网络的物理基础典型的协议包括以太网、ADSL等等。

OSI与TCP/IP区别

OSI与TCP/IP的区别联系

  1. TCP/IP支持快层封装;OSI不支持
  2. TCP/IP仅仅支持IP网络协议;OSI支持多种网络层协议(IP、IPX、APPLE、TALK、NOVELLNSAP)

通信协议

TCP协议(Transmission Control Protocol):传输控制协议【重点】

是一种面向连接的、可靠的、基于字节流的传输层通信协议。数据大小无限制。建立连接的过程需要三次握手,断开连接的过程需要四次挥手。

  • 三次握手【重点】
    三次握手
  • 四次挥手【重点】
    四次挥手

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

  • 是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,每个包的大小64KB。
  • UDP支持单播、广播、多播发送。
    • 单播Unicast:每次只向一台主机发送;
    • 广播Broadcast:每次向同一网络中所有主机发送;
    • 多播Multicast:只向属于同一组中的主机发送。

TCP与UDP区别

  1. TCP提供面向连接的传输,通信前要先建立连接(三次握手机制);UDP提供无连接的传输,通信前不需要建立连接。
  2. TCP提供可靠的传输(有序、无差错、不丢失、不重复);UDP提供不可靠的传输。
  3. TCP面向字节流的传输,因此它能将信息分割成组,并在接收端将其重组;UDP是面向数据报的传输,没有分组开销。
  4. TCP传输速度慢, UDP传输数据快(实际上现在TCP并不比UDP慢多少)
  5. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
  6. TCP提供拥塞控制和流量控制机制;UDP不提供拥塞控制和流量控制机制。

IP与端口

IP

  • IP协议:Internet Protocol Address 互联网协议地址/网际协议地址

    • 分配给互联网设备的数字标签(唯一标识),IP地址。
  • IP地址分为两种:

    • IPV4:4字节32位整数,并分成4段8位的二进制数,每8位之间用圆点隔开,每8位整数可以转换为一个0~255的十进制整数。
      格式:D.D.D.D 例如:255.255.255.255
      • IPV4的应用分类:
        • A类:政府机构,1.0.0.1~126.255.255.254
        • B类:中型企业,128.0.0.1~191.255.255.254
        • C类:个人用户,192.0.0.1~223.255.255.254
        • D类:用于组播,224.0.0.1~239.255.255.254
        • E类:用于实验,240.0.0.1~255.255.255.254
        • 回环地址:127.0.0.1,指本机,一本用于测试使用。
    • IPV6:16字节128位整数,并分成8段十六进制数,每16位之间用圆点隔开,每16位整数可以转换为一个0~65535的十进制数。
      格式:X.X.X.X.X.X.X.X 例如:FFFF.FFFF.FFFF.FFFF.FFFF.FFFF.FFFF.FFFF
    • 查看IP命令:ipconfig(windows)、ifconfig(liunx)
    • 测试IP命令:ping D.D.D.D

Post(端口号)

  • 端口号:在通信实体上进行网络通信的程序的唯一标识。
  • 区分不同的网络程序,一个端口号同一时刻只能被一个网络程序使用。
  • 端口分类:
    • 公认端口:0~1023
    • 注册端口:1024~49151
    • 动态或私有端口:49152~65535
  • 常用端口:
    • MySQL:3306
    • Oracle:1521
    • Tomcat:8080
    • SMTP:25
    • Web服务器:80
    • FTP服务器:21
  • 查看端口号的命令:netstat -ano

网络编程

InetAddress类

  • JavaAPI中介绍:此类表示互联网协议 (IP) 地址。
  • 概念:表示互联网协议(IP)地址对象,封装了与该IP地址相关的所有信息,
  • 并提供获取信息的常用方法。
  • 方法:
方法声明方法描述
boolean equals(Object obj)将此对象与指定对象比较。
byte[] getAddress()返回此 InetAddress 对象的原始 IP 地址。
static InetAddress[] getAllByName(String host)在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。
static InetAddress getByAddress(byte[] addr)在给定原始 IP 地址的情况下,返回 InetAddress 对象。
static InetAddress getByAddress(String host, byte[] addr)根据提供的主机名和 IP 地址创建 InetAddress。
static InetAddress getByName(String host)在给定主机名的情况下确定主机的 IP 地址。
String getCanonicalHostName()获取此 IP 地址的完全限定域名。
String getHostAddress()返回 IP 地址字符串(以文本表现形式)。
String getHostName()获取此 IP 地址的主机名。
static InetAddress getLocalHost()返回本地主机。
int hashCode()返回此 IP 地址的哈希码。
boolean isAnyLocalAddress()检查 InetAddress 是否是通配符地址的实用例行程序。
boolean isLinkLocalAddress()检查 InetAddress 是否是链接本地地址的实用例行程序。
boolean isLoopbackAddress()检查 InetAddress 是否是回送地址的实用例行程序。
boolean isMCGlobal()检查多播地址是否具有全局域的实用例行程序。
boolean isMCLinkLocal()检查多播地址是否具有链接范围的实用例行程序。
boolean isMCNodeLocal()检查多播地址是否具有节点范围的实用例行程序。
boolean isMCOrgLocal()检查多播地址是否具有组织范围的实用例行程序。
boolean isMCSiteLocal()检查多播地址是否具有站点范围的实用例行程序。
boolean isMulticastAddress()检查 InetAddress 是否是 IP 多播地址的实用例行程序。
boolean isReachable(int timeout)测试是否可以达到该地址。
boolean isReachable(NetworkInterface netif, int ttl, int timeout)测试是否可以达到该地址。
boolean isSiteLocalAddress()检查 InetAddress 是否是站点本地地址的实用例行程序。
String toString()将此 IP 地址转换为 String。
public class InetAddressDemo {
    public static void main(String[] args) throws Exception {
        //  1.获得本地主机地址对象
        //  1.1getLocalHost();
        InetAddress localAddress = InetAddress.getLocalHost();
        System.out.println("利用getLocalHost()查询本机的IP地址:" + localAddress.getHostAddress() + " 本机的主机名:" + localAddress.getHostName());
        //  1.2根据本机ip地址查询本机
        //InetAddress localAddress1 = InetAddress.getByName("192.168.169.1");
        InetAddress localAddress1 = InetAddress.getByAddress(new byte[]{(byte) 192, (byte) 168, (byte) 169, 1});
        System.out.println("根据IP地址查询本机的IP地址:" + localAddress1.getHostAddress() + " 本机的主机名:" + localAddress1.getHostName());
        //  1.3根据本机名查询本机
        InetAddress localAddress2 = InetAddress.getByName("DESKTOP-AL2FJ06");
        System.out.println("根据本机名查询本机的IP地址:" + localAddress2.getHostAddress() + " 本机的主机名:" + localAddress2.getHostName());
        //  1.4 127.0.0.1查询本机
        //InetAddress localAddress3 = InetAddress.getByName("127.0.0.1");
        InetAddress localAddress3 = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});
        System.out.println("127.0.0.1查询本机的IP地址:" + localAddress3.getHostAddress() + " 本机的主机名:" + localAddress3.getHostName());
        //  1.5 localhost查询本机
        InetAddress localAddress4 = InetAddress.getByName("localhost");
        System.out.println("localhost查询本机的IP地址:" + localAddress4.getHostAddress() + " 本机的主机名:" + localAddress4.getHostName());

        System.out.println("-------------------");

        //  2.局域网(同一局域网)
        //  2.1根据ip
        InetAddress remoteAddress1 = InetAddress.getByAddress(new byte[]{10, 9, 31, (byte) 141});
        System.out.println("根据IP地址查询的IP地址:" + remoteAddress1.getHostAddress() + " 主机名:" + remoteAddress1.getHostName());
        //  2.2根据主机名
        InetAddress remoteAddress2 = InetAddress.getByName("打虎滴武松");
        System.out.println("根据主机名查询的IP地址:" + remoteAddress2.getHostAddress() + " 主机名:" + remoteAddress2.getHostName());

        System.out.println("-------------------");

        //  3.外网
        //  3.1根据IP地址查询
        //InetAddress remoteAddress3 = InetAddress.getByName("61.135.169.141);
        InetAddress remoteAddress3 = InetAddress.getByAddress(new byte[]{61, (byte) 135, (byte) 169, (byte) 141});
        System.out.println("根据IP地址查询的IP地址:" + remoteAddress3.getHostAddress() + " 主机名:" + remoteAddress3.getHostName());
        //  3.2根据域名查询
        InetAddress remoteAddress4 = InetAddress.getByName("www.baidu.com");
        System.out.println("根据域名查询的IP地址:" + remoteAddress4.getHostAddress() + " 主机名:" + remoteAddress4.getHostName());

        System.out.println("-------------------");

        //  3.3根据域名查询所有IP地址
        InetAddress[] inetAddresses = InetAddress.getAllByName("www.baidu.com");
        for (InetAddress inetAddress : inetAddresses) {
            System.out.println(inetAddress.getHostAddress());
        }
    }
}

基于TCP的网络编程(Transmission Control Protocol)

  • Socket编程:
    • Socket(套接字)是网络中的一个通信节点、是操作系统提供的一种通信机制。
    • 分为客服端Socket与服务器ServerSocket。
    • 通信要求:IP地址 + 端口号。

Java通信是在操作心态的Socket基础上的一种封装。

  • TCP的Socket:分为服务器套接字和客户端套接字
    • ServerSocket:服务器套接字,监听客户端的连接,不能实现通信
    • Socket:客户端套接字,实现通信
Socket(套接字)与ServerSocket(服务器套接字)
ServerSocket
  • JavaAPI中介绍:此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
    服务器套接字的实际工作由 SocketImpl 类的实例执行。应用程序可以更改创建套接字实现的套接字工厂来配置它自身,从而创建适合本地防火墙的套接字。
  • 构造方法
方法描述
ServerSocket()创建非绑定服务器套接字。
ServerSocket(int port)创建绑定到特定端口的服务器套接字。
ServerSocket(int port, int backlog)利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
ServerSocket(int port, int backlog, InetAddress bindAddr)使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
  • 方法
方法声明方法描述
Socket accept()侦听并接受到此套接字的连接。
void bind(SocketAddress endpoint)将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
void bind(SocketAddress endpoint, int backlog)将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
void close()关闭此套接字。
ServerSocketChannel getChannel()返回与此套接字关联的唯一 ServerSocketChannel 对象(如果有)。
InetAddress getInetAddress()返回此服务器套接字的本地地址。
int getLocalPort()返回此套接字在其上侦听的端口。
SocketAddress getLocalSocketAddress()返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
int getReceiveBufferSize()获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是将用于从此 ServerSocket 接受的套接字的建议缓冲区大小。
boolean getReuseAddress()测试是否启用 SO_REUSEADDR。
int getSoTimeout()获取 SO_TIMEOUT 的设置。
protected void implAccept(Socket s)ServerSocket 的子类使用此方法重写 accept() 以返回它们自己的套接字子类。
boolean isBound()返回 ServerSocket 的绑定状态。
boolean isClosed()返回 ServerSocket 的关闭状态。
void setPerformancePreferences(int connectionTime, int latency, int bandwidth)设置此 ServerSocket 的性能首选项。
void setReceiveBufferSize(int size)为从此 ServerSocket 接受的套接字的 SO_RCVBUF 选项设置默认建议值。
void setReuseAddress(boolean on)启用/禁用 SO_REUSEADDR 套接字选项。
static void setSocketFactory(SocketImplFactory fac)为应用程序设置服务器套接字实现工厂。
void setSoTimeout(int timeout)通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
String toString()作为 String 返回此套接字的实现地址和实现端口。
Socket
  • JavaAPI中介绍:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
    套接字的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。
  • 构造方法
方法描述
Socket()通过系统默认类型的 SocketImpl 创建未连接套接字
Socket(InetAddress address, int port)创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
Socket(InetAddress host, int port, boolean stream)已过时。 Use DatagramSocket instead for UDP transport.
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)创建一个套接字并将其连接到指定远程地址上的指定远程端口。
Socket(Proxy proxy)创建一个未连接的套接字并指定代理类型(如果有),该代理不管其他设置如何都应被使用。
protected Socket(SocketImpl impl)使用用户指定的 SocketImpl 创建一个未连接 Socket。
Socket(String host, int port)创建一个流套接字并将其连接到指定主机上的指定端口号。
Socket(String host, int port, boolean stream)已过时。 使用 DatagramSocket 取代 UDP 传输。
Socket(String host, int port, InetAddress localAddr, int localPort)创建一个套接字并将其连接到指定远程主机上的指定远程端口。
  • 方法
方法声明方法描述
void bind(SocketAddress bindpoint)将套接字绑定到本地地址。
void close()关闭此套接字。
void connect(SocketAddress endpoint)将此套接字连接到服务器。
void connect(SocketAddress endpoint, int timeout)将此套接字连接到服务器,并指定一个超时值。
SocketChannel getChannel()返回与此数据报套接字关联的唯一 SocketChannel 对象(如果有)。
InetAddress getInetAddress()返回套接字连接的地址。
InputStream getInputStream()返回此套接字的输入流。
boolean getKeepAlive()测试是否启用 SO_KEEPALIVE。
InetAddress getLocalAddress()获取套接字绑定的本地地址。
int getLocalPort()返回此套接字绑定到的本地端口。
SocketAddress getLocalSocketAddress()返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
boolean getOOBInline()测试是否启用 OOBINLINE。
OutputStream getOutputStream()返回此套接字的输出流。
int getPort()返回此套接字连接到的远程端口。
int getReceiveBufferSize()获取此 Socket 的 SO_RCVBUF 选项的值,该值是平台在 Socket 上输入时使用的缓冲区大小。
SocketAddress getRemoteSocketAddress()返回此套接字连接的端点的地址,如果未连接则返回 null。
boolean getReuseAddress()测试是否启用 SO_REUSEADDR。
int getSendBufferSize()获取此 Socket 的 SO_SNDBUF 选项的值,该值是平台在 Socket 上输出时使用的缓冲区大小。
int getSoLinger()返回 SO_LINGER 的设置。
int getSoTimeout()返回 SO_TIMEOUT 的设置。
boolean getTcpNoDelay()测试是否启用 TCP_NODELAY。
int getTrafficClass()为从此 Socket 上发送的包获取 IP 头中的流量类别或服务类型。
boolean isBound()返回套接字的绑定状态。
boolean isClosed()返回套接字的关闭状态。
boolean isConnected()返回套接字的连接状态。
boolean isInputShutdown()返回是否关闭套接字连接的半读状态 (read-half)。
boolean isOutputShutdown()返回是否关闭套接字连接的半写状态 (write-half)。
void sendUrgentData(int data)在套接字上发送一个紧急数据字节。
void setKeepAlive(boolean on)启用/禁用 SO_KEEPALIVE。
void setOOBInline(boolean on)启用/禁用 OOBINLINE(TCP 紧急数据的接收者) 默认情况下,此选项是禁用的,即在套接字上接收的 TCP 紧急数据被静默丢弃。
void setPerformancePreferences(int connectionTime, int latency, int bandwidth)设置此套接字的性能偏好。
void setReceiveBufferSize(int size)将此 Socket 的 SO_RCVBUF 选项设置为指定的值。
void setReuseAddress(boolean on)启用/禁用 SO_REUSEADDR 套接字选项。
void setSendBufferSize(int size)将此 Socket 的 SO_SNDBUF 选项设置为指定的值。
static void setSocketImplFactory(SocketImplFactory fac)为应用程序设置客户端套接字实现工厂。
void setSoLinger(boolean on, int linger)启用/禁用具有指定逗留时间(以秒为单位)的 SO_LINGER。
void setSoTimeout(int timeout)启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。
void setTcpNoDelay(boolean on)启用/禁用 TCP_NODELAY(启用/禁用 Nagle 算法)。
void setTrafficClass(int tc)为从此 Socket 上发送的包在 IP 头中设置流量类别 (traffic class) 或服务类型八位组 (type-of-service octet)。
void shutdownInput()此套接字的输入流置于“流的末尾”。
void shutdownOutput()禁用此套接字的输出流。
String toString()将此套接字转换为 String。
开发步骤
  • 建立通信连接(会话):
    • 创建ServerSocket,指定端口号
    • 调用accept等待客户端接入
  • 客户端请求服务器:
    • 创建Socket,指定服务器IP + 端口号
    • 使用输出流,发送请求数据给服务器
    • 使用输入流,接收响应数据到客户端(等待)
  • 服务器响应客户端:
    • 使用输入流,接受请求数据到服务器(等待)
    • 使用输出流,发出响应数据给客户端
案例一 使用TCP实现客户端发送数据给服务器并回复
  • Tcp协议服务器端程序代码
/**
 * 基于Tcp协议的服务器端程序
 * 步骤:
 * (1)创建服务器套接字,并指定端口号
 * (2)服务器侦听(监听)客户端的连接,返回客户端套接字
 * (3)获取输入输出流,实现通信
 * (4)处理数据并回复
 * (5)关闭
 * Socket:套接字(通信的端点(IP地址、端口号)),是操作系统提供的一种通信机制
 * Java通信是在操作心态的Socket基础上的一种封装。
 * Socket:分为服务器套接字和客户端套接字
 *      ServerSocket:服务器套接字,监听客户端的连接,不能实现通信
 *      Socket:客户端套接字,实现通信
 */
public class TcpServer {
    public static void main(String[] args) throws Exception {
        //(1)创建服务器套接字,并指定端口号
        ServerSocket listener = new ServerSocket(10010);
        //(2)服务器侦听(监听)客户端的连接,返回客户端套接字,调用accept()方法接受请求,阻塞方法,如果没有客户端请求,则阻塞
        System.out.println("服务器已启动...");
        Socket socket = listener.accept();
        //(3)获取输入输出流,实现通信
        InputStream is = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        OutputStream os = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
        //(4)处理数据并回复
        String date = br.readLine();    //  读取一行(必须要读取到换行符,才认为读取了一行)阻塞
        System.out.println("客户说:" + date);
        bw.write("您好!");
        bw.newLine();
        bw.flush();
        //(5)关闭
        br.close();
        bw.close();
        socket.close();
        listener.close();
        System.out.println("服务器关闭...");
    }
}

注意:BufferedWriter中的readLine();方法必须读到换行符

  • 客户端程序代码
/**
 * Tcp客户端的编码步骤
 * (1)创建客户端Socket,并指定服务器的IP地址和端口号,建立三次握手
 * (2)获取输入输出流
 * (3)发送数据并处理响应数据
 * (4)关闭
 */
public class TcpClient {
    public static void main(String[] args) throws Exception {
        //(1)创建客户端Socket,并指定服务器的IP地址和端口号,建立三次握手
        Socket socket = new Socket(InetAddress.getLocalHost(), 10010);
        //(2)获取输入输出流
        InputStream is = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        OutputStream os = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
        //(3)处理数据
        bw.write("好久不见");
        bw.newLine();
        bw.flush();
        String replay = br.readLine();
        System.out.println("客服说:" + replay);
        //(4)关闭
        br.close();
        bw.close();
        socket.close();
    }
}
案例二 使用TCP实现客户端上传文件到服务器
  • 服务器端程序代码
/**
 * 文件客户端
 * 接受文件
 */
public class FileServer {
    public static void main(String[] args) throws Exception {
        //  1.创建服务器套接字,并指定端口号
        ServerSocket listener = new ServerSocket(9999);
        //  2.监听,并返回客户端套接字
        System.out.println("准备接收");
        Socket socket = listener.accept();
        //  3.获取输入输出流
        InputStream is = socket.getInputStream();
        FileOutputStream fos = new FileOutputStream("22.jpg");
        byte[] buf = new byte[1024];
        int len = 0;
        //  4.读取并写入硬盘
        while ((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
        }
        //  5.关闭
        fos.close();
        is.close();
        socket.close();
        listener.close();
        System.out.println("接收完毕");
    }
}
  • 客户端程序代码
/**
 * 文件客户端
 * 发送文件
 */
public class FileClient {
    public static void main(String[] args) throws Exception {
        //  1.创建Socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        //  2.获取输出流
        OutputStream os = socket.getOutputStream();
        FileInputStream fis = new FileInputStream("11.png");
        //  3.读取并输出
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = fis.read(buf)) != -1) {
            os.write(buf, 0, len);
        }
        //  4.关闭
        fis.close();
        os.close();
        socket.close();
        System.out.println("发送完毕");
    }
}
案例三 使用TCP实现服务器多线程接受客户端
  • 服务器端程序代码
/**
 * 使用Tcp实现聊天服务器
 */
public class ChatServer {
    public static void main(String[] args) {
        ServerSocket listener = null;
        //  创建线程池
        //ExecutorService es = new ThreadPoolExecutor(10, 10,0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2));
        ExecutorService es = Executors.newCachedThreadPool();
        try {
            //  1.创建服务器套接字
            listener = new ServerSocket(8888);
            System.out.println("聊天室已启动...");
            //  2.监听
            while (true) {
                Socket socket = listener.accept();
                System.out.println(socket.getInetAddress().getHostAddress() + "进入聊天室");
                //  开启线程
                es.submit(new SocketThread(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器出现异常...");
        } finally {
            try {
                listener.close();
                es.shutdown();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 服务器端客户线程池
public class SocketThread implements Runnable {
    private Socket socket;

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

    @Override
    public void run() {
        if (socket != null) {
            BufferedReader br = null;
            try {
                InputStream is = socket.getInputStream();
                br = new BufferedReader(new InputStreamReader(is));
                while (true) {
                    String str = br.readLine();
                    if (str == null) {
                        System.out.println(socket.getInetAddress().getHostAddress() + "退出了...");
                        break;
                    }
                    System.out.println(socket.getInetAddress().getHostAddress() + "说:" + str);
                    if (str.equals("886") || str.equals("over")) {
                        System.out.println(socket.getInetAddress().getHostAddress() + "退出了...");
                        break;
                    }
                }
            } catch (IOException e) {
                System.out.println(socket.getInetAddress().getHostAddress() + "异常退出了...");
            } finally {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 客户端程序代码
public class ChatClient {
    public static void main(String[] args) throws Exception {
        //  1.创建Socket,并指定服务器的地址和端口号
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        //  2.获取流
        OutputStream os = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
        //  3.循环发送
        Scanner sc = new Scanner(System.in);
        while (true) {
            String str = sc.next();
            bw.write(str);
            bw.newLine();
            bw.flush();
            if (str.equals("886") || str.equals("over")) {
                break;
            }
        }
        bw.close();
        socket.close();
    }
}
案例四 用户注册登录
  • 注册

    • 注册信息保存在users.bin文件中。
    • 注册成功后返回字符串“注册成功”。
  • 登录

    • 获取users.bin文件中的用户信息,进行用户名与密码的校验。
    • 校验成功后返回字符串“登陆成功”。
  • 实体类代码

public class User implements Serializable{
	private static long serivalVersionUID = 10000L;	//	设置序列化版本号

	private int type;	//	操作数:1-注册、2-登录、0-退出
	private String username;
	private String password;
	private int age;
	private String sex;
	private String address;
	/*
	无参、有参构造方法
	get()、set()方法等
	toString()方法
	已省略,请手动生成
	*/
}
  • 服务器端代码
public class UserServer{
	public static void main(String[] args) {
		ConcurrentHashMap<String, User> userMap = null;
		ExecutorService es = null;
		ServerSocket listener = null;
		try {
			//	1.判断本地是否存在users.bin文件
			File file = new File("users.bin");
			if (file.exists()) {
				//	对文件进行反序列化
				ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
				userMap = (ConcurrentHashMap<String, User>) ois.readObject();
				ois.close();
			} else {
				userMap = new ConcurrentHashMap<>();
			}
			//	2.创建线程池
			es = Executors.newCachedThreadPool();
			//	3.创建服务套接字
			listener = new ServerSocket(8989);
			System.out.println("服务器已开启...");
			//	4.循环监听
			while (true) {
				Socket socket = listener.accept();
				System.out.println(socket.getInetAddress().getHostAddress() + "进入服务器...");
				//	5.线程池提交线程
				es.submit(new UserThread(socket, userMap));
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("服务器已退出...");
		} finally {
			es.shutdown();
			try {
				listener.close();	
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
  • 服务器端线程类
public class UserThread implements Runnable{
	private Socket socket;
	private ConcurrentHashMap<String, User> userMap;

	public UserThread(Socket socket, ConcurrentHashMap<String, User> userMap) {
		this.socket = socket;
		this.userMap = userMap;
	}

	@Override
	public void run() {
		ObjectInputStream ois = null;
		BufferedWriter bw = null;
		try {
			if (socket != null) {
				//	获取输入输出流
				InputStream is = socket.getInputStream();
				ois = new ObjectInputStream(is);
				OutputStream os = socket.getOutputStream();
				bw = new BufferedWriter(new OutputStreamWriter(os));
				while (true) {
					User user = (User) ois.readObject();
					if (user.getType() == 1) {
						if (userMap.containsKey(user.getUsername())) {
							bw.write("用户名已存在!");
						} else {
							userMap.put(user.getUsername(), user);
							bw.write("注册成功!");
							ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users.bin"));
							oos.writeObject(userMap);
							oos.close();
						}
					} else if (user.getType() == 2) {
						if (userMap.containsKey(user.getUsername())) {
							if (user.getPassword().equals(userMap.get(user.getUsername()).getPassword())) {
								bw.write("登陆成功!");
							} else {
								bw.write("密码错误!");
							}
						} else {
							bw.write("用户名不存在!");
						}
					} else {
						bw.write("再见");
						System.out.println(socket.getInetAddress().getHostAddress() + "已退出...");
						break;
					}
					bw.newLine();
					bw.flush();
				}
			}
		} catch (Exception e) {
			System.out.println(socket.getInetAddress().getHostAddress() + "异常退出了...");
		} finally {
			try {
				ois.close();
				bw.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
  • 客户端代码
public class UserClient{
	public static void main(String[] args) throws Exception {
		
		Socket socket = new Socket(InetAddress.getLocalHost(), 8989);
		System.out.println("已成功连接" + socket.getInetAddress().getHostAddress())
		System.out.println("---------欢迎--------");
		InputStream is = socket.getInputStream();
		BUfferedReader br = new BufferedReader(new InputStreamReader(is));
		OutputStream os = socket.getOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(is);
		Scanner sc = new Scanner(System.in);
		while (true) {
			System.out.println("---1.注册	2.登录	0.退出---");
			int choice = sc.nextInt();
			
			String username = null;
            String password = null;
            int age = 0;
            String sex = null;
            String address = null;
            User user = null;
            String replay = null;
			
			switch () {
				case 1:
					System.out.println("请输入用户名:");
                    username = sc.next();
                    System.out.println("请输入密码:");
                    password = sc.next();
                    System.out.println("请输入年龄:");
                    age = sc.nextInt();
                    System.out.println("请输入性别:");
                    sex = sc.next();
                    System.out.println("请输入地址:");
                    address = sc.next();

					user = new User(1, username, password, age, sex, address);
					oos.writeObject(user);
					replay = bw.readLine();
					System.out.println("服务器回复:" + replay);
					break;
				case 2:
					System.out.println("请输入用户名:");
                    username = sc.next();
                    System.out.println("请输入密码:");
                    password = sc.next();

					user = new User(2, username, password, age, sex, address);
					oos.writeObject(user);
					replay = bw.readLine();
					System.out.println("服务器回复:" + replay);
					break;
				case 0:
					user = new User(0, username, password, age, sex, address);
					replay = bw.readLine();
					System.out.println("服务器回复" + replay);
					break;
				default:
					System.out.println("输入错误!请重新输入:");
					break;
			}
		}
	}
}

基于UDP的网络编程(User Datagram Protocol)

UDP相关的类
DatagramSocket
  • 用来发送和接收数据报包的套接字
  • JavaAPI中介绍:
    此类表示用来发送和接收数据报包的套接字。
    数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
    在 DatagramSocket 上总是启用 UDP 广播发送。为了接收广播包,应该将 DatagramSocket 绑定到通配符地址。在某些实现中,将 DatagramSocket 绑定到一个更加具体的地址时广播包也可以被接收。
  • 构造方法
方法描述
DatagramSocket()构造数据报套接字并将其绑定到本地主机上任何可用的端口。
protected DatagramSocket(DatagramSocketImpl impl)创建带有指定 DatagramSocketImpl 的未绑定数据报套接字。
DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr)创建数据报套接字,将其绑定到指定的本地地址。
DatagramSocket(SocketAddress bindaddr)创建数据报套接字,将其绑定到指定的本地套接字地址。
  • 方法
方法声明方法描述
void bind(SocketAddress addr)将此 DatagramSocket 绑定到特定的地址和端口。
void close()关闭此数据报套接字。
void connect(InetAddress address, int port)将套接字连接到此套接字的远程地址。
void connect(SocketAddress addr)将此套接字连接到远程套接字地址(IP 地址 + 端口号)。
void disconnect()断开套接字的连接。
boolean getBroadcast()检测是否启用了 SO_BROADCAST。
DatagramChannel getChannel()返回与此数据报套接字关联的唯一 DatagramChannel 对象(如果有)。
InetAddress getInetAddress()返回此套接字连接的地址。
InetAddress getLocalAddress()获取套接字绑定的本地地址。
int getLocalPort()返回此套接字绑定的本地主机上的端口号。
SocketAddress getLocalSocketAddress()返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
int getPort()返回此套接字的端口。
int getReceiveBufferSize()获取此 DatagramSocket 的 SO_RCVBUF 选项的值,该值是平台在 DatagramSocket 上输入时使用的缓冲区大小。
SocketAddress getRemoteSocketAddress()返回此套接字连接的端点的地址,如果未连接则返回 null。
boolean getReuseAddress()检测是否启用了 SO_REUSEADDR。
int getSendBufferSize()获取此 DatagramSocket 的 SO_SNDBUF 选项的值,该值是平台在 DatagramSocket 上输出时使用的缓冲区大小。
int getSoTimeout()获取 SO_TIMEOUT 的设置。
int getTrafficClass()为从此 DatagramSocket 上发送的包获取 IP 数据报头中的流量类别或服务类型。
boolean isBound()返回套接字的绑定状态。
boolean isClosed()返回是否关闭了套接字。
boolean isConnected()返回套接字的连接状态。
void receive(DatagramPacket p)从此套接字接收数据报包。
void send(DatagramPacket p)从此套接字发送数据报包。
void setBroadcast(boolean on)启用/禁用 SO_BROADCAST。
static void setDatagramSocketImplFactory(DatagramSocketImplFactory fac)为应用程序设置数据报套接字实现工厂。
void setReceiveBufferSize(int size)将此 DatagramSocket 的 SO_RCVBUF 选项设置为指定的值。
void setReuseAddress(boolean on)启用/禁用 SO_REUSEADDR 套接字选项。
void setSendBufferSize(int size)将此 DatagramSocket 的 SO_SNDBUF 选项设置为指定的值。
void setSoTimeout(int timeout)启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。
void setTrafficClass(int tc)为从此 DatagramSocket 上发送的数据报在 IP 数据报头中设置流量类别 (traffic class) 或服务类型八位组 (type-of-service octet)。
DatagramPacket
  • 表示数据报包
  • JavaAPI中介绍:
    此类表示数据报包。
    数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
  • 构造方法:
方法描述
DatagramPacket(byte[] buf, int length)构造 DatagramPacket,用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int offset, int length)构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int length, SocketAddress address)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
  • 方法:
方法声明方法描述
InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
byte[] getData()返回数据缓冲区。
int getLength()返回将要发送或接收到的数据的长度。
int getOffset()返回将要发送或接收到的数据的偏移量。
int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
SocketAddress getSocketAddress()获取要将此包发送到的或发出此数据报的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。
void setAddress(InetAddress iaddr)设置要将此数据报发往的那台机器的 IP 地址。
void setData(byte[] buf)为此包设置数据缓冲区。
void setData(byte[] buf, int offset, int length)为此包设置数据缓冲区。
void setLength(int length)为此包设置长度。
void setPort(int iport)设置要将此数据报发往的远程主机上的端口号。
void setSocketAddress(SocketAddress address)设置要将此数据报发往的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。
开发步骤
案例一 使用UDP发送数据给接收者
  • 接收方代码
/**
 * UDP接收方
 * 步骤:
 * (1)创建DatagramSocket,指定端口号
 * (2)创建DatagramPacket,接收报包
 * (3)接收数据
 * (4)处理
 * (5)关闭
 */
public class Receiver {
	public static void main(String[] args) {
		//	1.创建一个DatagramSocket对象,用于接收数据
		DatagramSocket ds = new DatagramSocket(8888);
		//	2.创建一个DatagramPacket对象,用于接收数据报包
		byte[] buf = new byte[63*1024];
		DatagramPacket dp = new DatagramPacket(buf, buf.length);
		//	3.接收
		ds.receive(dp);
		//	4.处理数据
		String str = new String(dp.getData(), 0, dp.getLength()) + " from " + dp.getAddress().getHostAddress() + ":" + dp.getPort();
		System.out.println(str);
		//	5.关闭
		ds.close();
	}
}
  • 发送方代码
/**
 * UDP传输发的步骤
 * (1)创建DatagramSocket,不需要指定端口号,使用随机端口号(相当于快递点)
 * (2)创建DatagramPacket,发送数据报包,指定对方的地址和端口号
 * (3)发送
 * (4)关闭
 */
public class Sender {
    public static void main(String[] args) throws Exception {
        //  1.创建一个DatagramSocket对象
        DatagramSocket ds = new DatagramSocket(3000);
        String str = "hello world"; //  要发送的数据
        byte[] buf = str.getBytes();    //  将定义的字符串转为字节数组
        //  2.创建一个要发送的数据包,数据包包括发送的数据,数据长度,接收端的IP地址以及端口号
        DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 8954);
        System.out.println("发送信息");
        //  3.发送
        ds.send(dp);    //  发送数据
        //  4.关闭
        ds.close(); //  释放资源
    }
}
案例二 使用UDP实现广播聊天
  • 接收方代码
public class ChatReceiver {
    public static void main(String[] args) {
        DatagramSocket ds = null;
        try {
            //  1.创建DatagramSocket
            ds = new DatagramSocket(8888);
            //  2.创建DatagramPacket
            byte[] buf = new byte[63 * 1024];
            DatagramPacket dp = new DatagramPacket(buf, buf.length);
            System.out.println("已启动...");
            while (true) {
                //  3.接收
                ds.receive(dp);
                //  4.处理
                String str = new String(dp.getData(), 0, dp.getLength());
                System.out.println(dp.getAddress().getHostAddress() + "说:" + str);
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("出现异常");
        } finally {
            //  5.关闭
            ds.close();
        }
    }
}
  • 发送方代码
public class ChatSender {
    public static void main(String[] args) throws Exception {
        //  1.创建DatagramSocket
        DatagramSocket ds = new DatagramSocket();
        //  2.创建DatagramPacket
        Scanner sc = new Scanner(System.in);
        while (true) {
            String str = sc.next();
            //  受限广播地址  255.255.255.255
            //  指定广播地址  10.9.31.255
            DatagramPacket dp = new DatagramPacket(str.getBytes(), str.getBytes().length, InetAddress.getByName("10.9.31.255"), 8888);
            ds.send(dp);
            if(str.equals("886")){
                break;
            }
        }
        //3关闭
        ds.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值