java TCP/UDP、Socket、URL网络编程详解

本文涉及的产品
.cn 域名,1个 12个月
系统运维管理,不限时长
简介: java TCP/UDP、Socket、URL网络编程详解

网络通信协议


网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合


如何实现网络中的主机互相通信,有一定的规则(即:网络通信协议。有两套参考模型)


OSI七层参考模型:开放式系统互联参考模型,模型过于理想化,未能在因特网上进行广泛推广


  • 应用层、表示层、会话层;传输层、网络层、数据链路层、物理层。


TCP/IP五层参考模型(或TCP/IP协议簇):事实上的国际标准。有人将其分为四层,有人分为五层。


  • 4层:应用层、传输层、网络层、网络接口层
  • 5层:应用层、传输层、网络层、数据链路层、物理层


任何和互联网有关的操作都离不开TCP/IP协议

1.png传输层协议中有两个非常重要的协议:


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


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


通信双方地址


端口号

端口号(port ID):本质是由16位二进制组成,范围:0 - 65535,0作为保留端口,所以端口号的取值范围为1 - 65535。1 - 1023是知名端口号。


端口号的作用:区分和标识不同的应用,标识正在计算机上运行的进程(程序)。不同的进程有不同的端口号。


端口分类:


  • 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23,443 端口分配给HTTPS服务)


  • 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)


  • 动态/私有端口:49152~65535


⼀般情况下,如果⼀个程序需要使⽤知名端⼝的需要有root权限


Windows查看端口CMD命令:⽤“netstat”查看端⼝状态


IP地址


IP地址是指互联网协议地址(英语:Internet Protocol Address,又译为网际协议地址),是IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。


IP地址的作⽤:


⽤来在⽹络中标记⼀台电脑的⼀串数字,⽐如114. 114. 114. 114;在网络中的惟⼀性


IP地址的分类:


按照版本分类:早期的IP地址主要指的是IPv4,随着网络的发展IPv4的数量已经不能满足日新月异的网络发展,所以现在IP地址分为两大类:IPv4、IPv6


  • IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿,2011年初已 经用尽,以点分十进制表示,如192.168.0.1


  • IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示, 数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984


按照使用范围:

IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168. 开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用。

国际规定有⼀部分IP地址是⽤于我们的局域⽹使⽤,也就是属于私⽹IP,不在公⽹中使⽤的,它们的范围是:


10.0.0.0~10.255.255.255


172.16.0.0~172.31.255.255


192.168.0.0~192.168.255.255


InetAddress类


Internet上的主机有两种方式表示地址:


域名(hostName):www.baidu.com

IP 地址(hostAddress):127.0.0.1

InetAddress类主要表示IP地址,两个子类:Inet4Address、Inet6Address。


InetAddress 类 对 象 含 有 一 个 Internet 主 机 地 址 的 域 名 和 IP 地 址 :www.baidu.com 和127.0.0.1


InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取 InetAddress实例

public static InetAddress getLocalHost()
public static InetAddress getByName(String host)

InetAddress提供了如下几个常用的方法

public String getHostAddress()//返回 IP 地址字符串(以文本表现形式)
public String getHostName()//获取此 IP 地址的主机名
public boolean isReachable(int timeout)//测试是否可以达到该地址

demo

public class InetAddressDemo {
    public static void main(String[] args) throws Exception{
        InetAddress inet1 = InetAddress.getByName("192.168.0.14");
        System.out.println(inet1); // /192.168.10.14
        InetAddress inet2 = InetAddress.getByName("www.baidu.com");
        System.out.println(inet2); // www.baidu.com/180.101.50.188
        InetAddress inet3 = InetAddress.getByName("127.0.0.1");
        System.out.println(inet3); // /127.0.0.1
        InetAddress localHost = InetAddress.getLocalHost();//查询本机IP地址
        System.out.println(localHost);// LAPTOP-TLQ4KBTA/192.168.83.1
        System.out.println(localHost.getHostName());// LAPTOP-TLQ4KBTA
        System.out.println(localHost.getAddress());
        InetAddress address = InetAddress.getByName("www.baidu.com"); // 创建一个给定的主机的实例
//         address = InetAddress.getLocalHost(); // 获取本地主机的实例
        System.out.println(address.getHostAddress()); // 180.101.50.188
        System.out.println(address.getHostName()); // www.baidu.com
        System.out.println(address.isReachable(4000)); // true
    }
}

Socket 网路编程

利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。


网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。


通信的两端都要有Socket,是两台机器间通信的端点。


网络通信其实就是Socket间的通信。


Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。


一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。


Socket分类


  • 流套接字(stream socket):使用TCP提供可依赖的字节流服务

  • 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务


Socket类的常用构造器

public Socket(InetAddress address,int port)创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
public Socket(String host,int port)创建一个流套接字并将其连接到指定主机上的指定端口号。

Socket类的常用方法

public InputStream getInputStream() 返回此套接字的输入流。可以用于接收网络消息
public OutputStream getOutputStream() 返回此套接字的输出流。可以用于发送网络消息
public InetAddress getInetAddress() 此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
public InetAddress getLocalAddress() 获取套接字绑定的本地地址。 即本端的IP地址
public int getPort() 此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
public int getLocalPort() 返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的端口号。
public void close() 关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。
public void shutdownInput() 如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
public void shutdownOutput() 禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流, 则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。

UDP协议


什么是UDP协议


UDP (用户数据报协议,User Datagram Protocol),是OSI参考模型中传输层的协议


每次传输数据的一部分,不会考虑对方是否接收的到


特点:


  • 无连接:将数据、源、目的封装成数据包,不需要建立连接,不安全


  • 每个数据报的大小限制在64K内


  • 不可靠:发送不管对方是否准备好,接收方收到也不确认,故是不可靠的


  • 可以广播发送


  • 发送数据结束时无需释放资源,开销小,速度快,追求速度的网络协议


适用场景:对数据的完整性要求不高的场景,UDP⼀般⽤于多点通信和实时的数据业务


  • 语⾳⼴播


  • 视频


  • QQ(消息,文件的上传下载使用的TCP)


  • TFTP(简单⽂件传送)


  • SNMP(简单⽹络管理协议)


  • RIP(路由信息协议,如报告股票市场,航空信息)


  • DNS(域名解释)


UDP网络编程

类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序


UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。


DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP 地址和端口号以及接收端的IP地址和端口号。


UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和 接收方的连接。如同发快递包裹一样。我只管发出去,不管收不收。


DatagramSocket 构造方法

public DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被 绑定到通配符地址,IP 地址由内核来选择。
public DatagramSocket(int port,InetAddress laddr)
创建数据报套接字,将其绑定到指定的本地地址。 本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地 址,IP 地址由内核选择。

DatagramSocket 常用方法

public void close()
关闭此数据报套接字。
public void send(DatagramPacket p)
从此套接字发送数据报包。DatagramPacket 包含的信息指示:将 要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
public void receive(DatagramPacket p):从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
public InetAddress getLocalAddress()
获取套接字绑定的本地地址。
public int getLocalPort()
返回此套接字绑定的本地主机上的端口号。
public InetAddress getInetAddress()
返回此套接字连接的地址。如果套接字未连接,则返回 null。
public int getPort()
返回此套接字的端口。如果套接字未连接,则返回 -1。

DatagramPacket常用方法

public DatagramPacket(byte[] buf,int length)
构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。
public InetAddress getAddress()
返回某台机器的 IP 地址,此数据报将要发往该 机器或者是从该机器接收到的。
public int getPort()
返回某台远程主机的端口号,此数据报将要发往该主机或 者是从该主机接收到的。
public byte[] getData()
返回数据缓冲区。接收到的或将要发送的数据从缓冲区 中的偏移量 offset 处开始,持续 length 长度。
public int getLength()
返回将要发送或接收到的数据的长度。

实现步骤

   1. 导入DatagramSocket与DatagramPacket包
   2. 建立发送端,接收端
   3. 建立数据包
   4. 调用数据报套接字的发送、接收方法
   5. 关闭数据报套接字

注意:发送端与接收端是两个独立的运行程序,也可以使用多线程同时实现收发


单向数据发收的UDP程序


发送方

public class UdpServer {
    public static void main(String[] args) {
        try {
            while (true) {
                Scanner scan = new Scanner(System.in);
                System.out.println("请输入你要发送的消息:");
                String msg = scan.nextLine();
                byte[] bytes = msg.getBytes();
                DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getByName("127.0.0.1"), 8000);
                DatagramSocket datagramSocket = new DatagramSocket(9000);
                datagramSocket.send(datagramPacket);
                datagramSocket.close();
            }
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        } catch (SocketException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

接收方

public class UdpClient {
    public static void main(String[] args) {
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
        try {
            DatagramSocket datagramSocket = new DatagramSocket(8000);
            while (true) {
                System.out.println("正在接收数据");
                datagramSocket.receive(datagramPacket);
                String s = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
                System.out.println(s);
            }
        } catch (SocketException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

双向数据发收的UDP程序

主机1:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UdpServer1 {
    public static final String DES_IP = "127.0.0.1";
    public static final Integer DES_POST = 8888;
    private static DatagramSocket udpSocket = null;
    public static void main(String[] args) {
        try {
            udpSocket = new DatagramSocket(8080);
        } catch (SocketException e) {
            e.printStackTrace();
        }
        // 发送数据的线程
        Thread threadSend = new Thread(new Runnable() {
            @Override
            public void run() {
                Scanner scan = new Scanner(System.in);
                while (true) {
                    System.out.println("请输入消息:");
                    String msg = scan.nextLine();
                    byte[] bytes = msg.getBytes();
                    DatagramPacket data = new DatagramPacket(bytes, 0, bytes.length,
                            new InetSocketAddress(DES_IP, DES_POST));
                    try {
                        udpSocket.send(data);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 接收数据的线程
        Thread threadReceive = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    byte[] bytes = new byte[1024];
                    DatagramPacket receiveData = new DatagramPacket(bytes, 0, bytes.length);
                    try {
                        udpSocket.receive(receiveData);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    String data = new String(receiveData.getData(),0,receiveData.getLength());
                    System.out.println("接收到来自:" + receiveData.getAddress().getHostAddress()
                            + ":" + receiveData.getPort() + ",消息为:" + data);
                }
            }
        });
        threadSend.start();
        threadReceive.start();
    }
}

主机2:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UdpServer2 {
    public static final String DES_IP = "127.0.0.1";
    public static final Integer DES_POST = 8080;
    private static DatagramSocket udpSocket = null;
    public static void main(String[] args) {
        try {
            udpSocket = new DatagramSocket(8888);
        } catch (SocketException e) {
            e.printStackTrace();
        }
        // 发送数据的线程
        Thread threadSend = new Thread(new Runnable() {
            @Override
            public void run() {
                Scanner scan = new Scanner(System.in);
                while (true) {
                    System.out.println("请输入消息:");
                    String msg = scan.nextLine();
                    byte[] bytes = msg.getBytes();
                    DatagramPacket data = new DatagramPacket(bytes, 0, bytes.length,
                            new InetSocketAddress(DES_IP, DES_POST));
                    try {
                        udpSocket.send(data);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 接收数据的线程
        Thread threadReceive = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    byte[] bytes = new byte[1024];
                    DatagramPacket receiveData = new DatagramPacket(bytes, 0, bytes.length);
                    try {
                        udpSocket.receive(receiveData);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    String data = new String(receiveData.getData(),0,receiveData.getLength());
                    System.out.println("接收到来自:" + receiveData.getAddress().getHostAddress()
                            + ":" + receiveData.getPort() + ",消息为:" + data);
                }
            }
        });
        threadSend.start();
        threadReceive.start();
    }
}

TCP协议

什么是TCP协议


TCP协议,传输控制协议(Transmission Control Protocol)


TCP通信模型中,在通信开始之前,⼀定要先建⽴相关的连接,才能发送数据,类似于⽣活中,“打电话”。


特点:


  • 面向连接:使用TCP协议前,须先建立TCP连接,形成传输数据通道


  • 传输前,采用“三次握手”方式,点对点通信,是可靠的


  • TCP协议进行通信的两个应用进程:客户端、服务端。


  • 在连接中可进行大数据量的传输,有流控机制


  • 传输完毕,需释放已建立的连接,效率低


TCP和UDP的使用场景:

  • TCP更适合应用在对效率要求不高,但对准确性要求较高的场景;
  • UDP更适合在对效率要求较高,但对准确性要求较低的场景。

三次握手

三次握手机制是TCP协议在传输数据前建立连接的机制

2.png

三次挥手的过程:


客户端:syn请求报文,SYN = 1,发送序号seq = x(随机的),


服务器:ack应答报文 + syn请求报文:ACK = 1,SYN = 1,应答序号acknumber = x+1,发送序号seq = y(随机值);


客户端:ack应答报文,ACK = 1,seq = x+1,acknumber = y+1。


经典面试题:三次握手为什么是三次,不能是两次或者四次吗?


四次挥手

四次挥手机制是TCP协议在传输数据结束断开连接的机制

3.png

四次挥手的过程:


客户端A:发送fin结束报文,FIN = 1,seq = u(带数据量的);


服务器B:发送ack应答报文,ACK = 1,acknumber = u + 1,此时A指向B的连接就断开了。因为此时B可能还有数据要发,所以B给A发的FIN数据包没有和ACK包一起发。


服务器B:当B的数据发完以后,B给A发第三个数据包:FIN = 1,seq = w;


客户端A:发送ack应答报文,ACK = 1,seq = u + 1,acknumber = w + 1,此时B指向A的连接就断开了。


至此四次挥手就结束了。注:过程中的序号是包含数据量的。


客户端Socket


客户端Socket的工作过程包含以下四个基本的步骤:


1.创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。


2.打开连接到 Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输


3.按照一定的协议对 Socket 进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。


4.关闭 Socket:断开客户端到服务器的连接,释放线路


客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连接。


Socket的构造器:

Socket(String host,int port)throws UnknownHostException,IOException
向服务器(域名是 host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。
Socket(InetAddress address,int port)throws IOException
根据InetAddress对象所表示的IP地址以及端口号port发起连接。

示例:

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class SocketClient {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1",6666);
        OutputStream out = s.getOutputStream();
        out.write("你好,荷逸".getBytes());
        s.close();
    }
} 

服务器Socket

服务器程序的工作过程包含以下四个基本的步骤:


1.调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。


2.调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。


3.调用该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出流和输入流,开始网络数据的发送和接收。


4.关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字。


ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象。


所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketAtServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(6666);
        Socket s = ss.accept();
        InputStream in = s.getInputStream();
        byte[] buf = new byte[1024];
        int num = in.read(buf);
        String str = new String(buf, 0, num);
        System.out.println(s.getInetAddress().toString() + ":" + str);
        s.close();
        ss.close();
    }
} 

示例:服务端为中转站,做聊天通讯demo

定义消息模型:

import java.io.Serializable;
public class Message implements Serializable {
    private String to;
    private String from;
    private int type;
    private String info;
    public Message(String to, String from, int type, String info) {
        this.to = to;
        this.from = from;
        this.type = type;
        this.info = info;
    }
    public Message() {
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
    public String getTo() {
        return to;
    }
    public void setTo(String to) {
        this.to = to;
    }
    public String getFrom() {
        return from;
    }
    public void setFrom(String from) {
        this.from = from;
    }
    public String getInfo() {
        return info;
    }
    public void setInfo(String info) {
        this.info = info;
    }
    @Override
    public String toString() {
        return "Message{" +
                "to='" + to + '\'' +
                ", from='" + from + '\'' +
                ", type=" + type +
                ", info='" + info + '\'' +
                '}';
    }
}

消息类型:

public class MessageType {
    public final static int MESSAGE_LOGIN = 0x1;
    public final static int MESSAGE_SEND = 0x2;
}

客户端:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Client {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        ExecutorService e = Executors.newFixedThreadPool(5);
        try {
            Socket socket = new Socket("127.0.0.1", 6666);
            System.out.println("服务器链接成功");
            System.out.println("请输入名称:");
            String name = scanner.nextLine();
            System.out.println("姓名" + name);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            Message message = new Message(null, name, MessageType.MESSAGE_LOGIN, null);
            objectOutputStream.writeObject(message);
            Message m = (Message) objectInputStream.readObject();
            System.out.println(m.getFrom() + m.getInfo());
            ReadInfoThread readInfoThread = new ReadInfoThread(objectInputStream);
            e.execute(readInfoThread);
            boolean flag = true;
            while (flag) {
                Message msg = new Message();
                System.out.println("请输入要接收的人:");
                msg.setTo(scanner.nextLine());
                System.out.println("请输入要发送的消息");
                msg.setInfo(scanner.nextLine());
                msg.setFrom(name);
                msg.setType(MessageType.MESSAGE_SEND);
                objectOutputStream.writeObject(msg);
            }
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
    }
}
class ReadInfoThread implements Runnable {
    private ObjectInputStream objectInputStream;
    private boolean flag = true;
    public ReadInfoThread(ObjectInputStream objectInputStream) {
        this.objectInputStream = objectInputStream;
    }
    public void setFlag(boolean flag) {
        this.flag = true;
    }
    @Override
    public void run() {
        try {
            while (flag) {
                Message message = (Message) objectInputStream.readObject();
                System.out.println("【" + message.getFrom() + "】:" + message.getInfo());
            }
            if (objectInputStream != null) {
                objectInputStream.close();
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

服务端:

public class Server {
    public static void main(String[] args) {
        Hashtable<String, UserThread> hashtable = new Hashtable<>();
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        try {
            ServerSocket serverSocket = new ServerSocket(6666);
            System.out.println("服务器已启动等待链接");
            while (true) {
                Socket socket = serverSocket.accept();
                UserThread userThread = new UserThread(socket, hashtable);
                executorService.execute(userThread);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
class UserThread implements Runnable {
    private String name = "";
    private Socket socket;
    private Hashtable<String, UserThread> hashtable;
    private ObjectInputStream ois;
    private ObjectOutputStream oos;
    private boolean flag = true;
    public UserThread(Socket socket, Hashtable<String, UserThread> hashtable) {
        this.socket = socket;
        this.hashtable = hashtable;
        this.flag = true;
    }
    @Override
    public void run() {
        System.out.println("客户端地址 " + socket.getInetAddress().getHostAddress());
        try {
            oos = new ObjectOutputStream(socket.getOutputStream());
            ois = new ObjectInputStream(socket.getInputStream());
            while (flag) {
                Message message = (Message) ois.readObject();
                int type = message.getType();
                switch (type) {
                    case MessageType.MESSAGE_SEND:
                        UserThread userThread = hashtable.get(message.getTo());
                        System.out.println(userThread);
                        if (userThread == null) {
                            oos.writeObject(message.getTo() + "不在线");
                        }
                        if (userThread == this) {
                            oos.writeObject("不能给自己发消息");
                        }
                        userThread.oos.writeObject(message);
                        break;
                    case MessageType.MESSAGE_LOGIN:
                        name = message.getFrom();
                        hashtable.put(name, this);
                        message.setInfo("登录成功");
                        oos.writeObject(message);
                        break;
                }
            }
            ois.close();
            oos.close();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println(e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

URL

什么是URL


URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。


它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate 这个资源。


通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。


URL的基本结构由5部分组成:

<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表


例如: http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123

参数列表格式:参数名=参数值&参数名=参数值…


URL类

URL的构造方法


为了表示URL,java.net 中实现了类 URL。我们可以通过下面的构造器来初始化一个 URL 对象:


1.public URL (String spec); 通过一个表示URL地址的字符串可以构造一个URL对象。

URL url = new URL ("http://www.baidu.com/");

2.public URL(URL context, String spec); 通过基 URL 和相对 URL 构造一个 URL 对象。

URL downloadUrl = new URL(url, "download.html");

3.public URL(String protocol, String host, String file);

new URL("http", "www.baidu.com", "download. html");

4.public URL(String protocol, String host, int port, String file);

URL gamelan = new URL("http", "www.baidu.com", 80, "download.html");

URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用 try-catch 语句进行捕获


URL类常用方法


一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性。

public String getProtocol( ) 获取该URL的协议名
public String getHost( ) 获取该URL的主机名
public String getPort( ) 获取该URL的端口号
public String getPath( ) 获取该URL的文件路径
public String getFile( ) 获取该URL的文件名
public String getQuery( ) 获取该URL的查询名
URL url = new URL("https://pics1.baidu.com/feed/7acb0a46f21fbe09c16218d19b0eb1388644adb6.png@f_auto?token=fac0558642f57b4d98725dfd0f30f14a");
//public String getProtocol(  )     获取该URL的协议名
System.out.println(url.getProtocol());
//public String getHost(  )           获取该URL的主机名
System.out.println(url.getHost());
//public String getPort(  )            获取该URL的端口号
System.out.println(url.getPort());
//public String getPath(  )           获取该URL的文件路径
System.out.println(url.getPath());
//public String getFile(  )             获取该URL的文件名
System.out.println(url.getFile());
//public String getQuery(   )        获取该URL的查询名
System.out.println(url.getQuery());

针对HTTP协议的URLConnection类

URL的方法 openStream():能从网络上读取数据


若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用URLConnection。


URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个 URL 对象上通过方法 openConnection() 生成对应的 URLConnection 对象。如果连接过程失败,将产生IOException。


URL netchinaren = new URL ("http://www.atguigu.com/index.shtml");
URLConnectonn u = netchinaren.openConnection( );

通过URLConnection对象获取的输入流和输出流,即可以与现有的CGI 程序进行交互。

public Object getContent( ) throws IOException
public int getContentLength( )
public String getContentType( )
public long getDate( )
public long getLastModified( )
public InputStream getInputStream( )throws IOException
public OutputSteram getOutputStream( )throws IOException

URL下载图片

//下载图片
public class UrlDemo {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://pics1.baidu.com/feed/7acb0a46f21fbe09c16218d19b0eb1388644adb6.png@f_auto?token=fac0558642f57b4d98725dfd0f30f14a");
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream());
            BufferedOutputStream bs = new BufferedOutputStream(new FileOutputStream("F:\\1.png"));
            byte[] bytes = new byte[1024];
            int len = -1;
            while ((len = in.read(bytes)) != -1) {
                bs.write(bytes, 0, len);
                bs.flush();
            }
            in.close();
            bs.close();
            urlConnection.disconnect();
            System.out.println("下载成功");
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
1月前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
58 9
|
1月前
|
人工智能 Java 物联网
JAVA网络编程的未来:URL与URLConnection的无限可能,你准备好了吗?
随着技术的发展和互联网的普及,JAVA网络编程迎来新的机遇。本文通过案例分析,探讨URL与URLConnection在智能API调用和实时数据流处理中的关键作用,展望其未来趋势和潜力。
43 7
|
1月前
|
Java 开发者
JAVA高手必备:URL与URLConnection,解锁网络资源的终极秘籍!
在Java网络编程中,URL和URLConnection是两大关键技术,能够帮助开发者轻松处理网络资源。本文通过两个案例,深入解析了如何使用URL和URLConnection从网站抓取数据和发送POST请求上传数据,助力你成为真正的JAVA高手。
54 11
|
1月前
|
安全 Java API
深入探索Java网络编程中的HttpURLConnection:从基础到进阶
本文介绍了Java网络编程中HttpURLConnection的高级特性,包括灵活使用不同HTTP方法、处理重定向、管理Cookie、优化安全性以及处理大文件上传和下载。通过解答五个常见问题,帮助开发者提升网络编程的效率和安全性。
|
1月前
|
JSON 安全 算法
JAVA网络编程中的URL与URLConnection:那些你不知道的秘密!
在Java网络编程中,URL与URLConnection是连接网络资源的两大基石。本文通过问题解答形式,揭示了它们的深层秘密,包括特殊字符处理、请求头设置、响应体读取、支持的HTTP方法及性能优化技巧,帮助你掌握高效、安全的网络编程技能。
56 9
|
1月前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
1月前
|
JSON Java API
JAVA网络编程新纪元:URL与URLConnection的神级运用,你真的会了吗?
本文深入探讨了Java网络编程中URL和URLConnection的高级应用,通过示例代码展示了如何解析URL、发送GET请求并读取响应内容。文章挑战了传统认知,帮助读者更好地理解和运用这两个基础组件,提升网络编程能力。
48 5
|
1月前
|
Java Spring
JAVA获取重定向地址URL的两种方法
【10月更文挑战第17天】本文介绍了两种在Java中获取HTTP响应头中的Location字段的方法:一种是使用HttpURLConnection,另一种是使用Spring的RestTemplate。通过设置连接超时和禁用自动重定向,确保请求按预期执行。此外,还提供了一个自定义的`NoRedirectSimpleClientHttpRequestFactory`类,用于禁用RestTemplate的自动重定向功能。
|
2月前
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程
26 0
|
5月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
60 4