目录
网络编程,也就是主机通过不同的进程,以编程的方式实现网络通信
JDK中提供了InetAddress类,用于封装IP地址
- public class demo1 {
- public static void main(String[] args) throws UnknownHostException {
- //获取给定主机名的IP地址
- System.out.println(InetAddress.getByName("localhost"));
- //获取本机IP地址
- System.out.println(InetAddress.getLocalHost());
- //获取本机IP地址的主机名
- InetAddress inetAddress=InetAddress.getLocalHost();
- System.out.println(inetAddress.getHostName());
- }
- }
系统提供的用于网络通信的技术,是基于ICP/IP协议网络通信的基本操作单元
Socket套接字可以看作是应用层与传输层之间的桥梁,传输层协议有两种TCP和UDP,故而Socket套接字也有两种
TCP:面向连接 可靠传输 面向字节流 全双工
UDP:不面向连接 不可靠传输 面向数据报 全双工
UDP通信是不需要建立连接的,就类似于货运公司在两个码头之间发送货物,包装货物就需要集装箱,故而JDK提供了DatagramPacket类,该类的实例就类似于包装箱;提供了DatagramSocket类,该类的作用类似于码头,使用这个类的实例化对象就可以发送和接收DatagramPacket数据报,通信过程如下所示
用于创建发送端和接收端对象(发送端和接收端都是进程,需要绑定端口号)
构造方法的使用:
1、DatagramSocket():
创建发送端的DatagramSocket对象;创建对象时不指定端口号,此时,系统会分配一个没有被其他网络程序使用的端口号
2、DatagramSocket(int port)
创建发送端和接收端的DatagramSocket对象;创建对象时指定端口号
3、DatagramSocket(int port, InetAddress laddr)
创建DatagramSocket对象时指定端口号和相关的IP地址,适用于多个网卡的情况,可以明确规定数据通过哪一个网卡发送数据和通过哪一个网卡接受数据
recieve:接受数据报
send:发送数据报
close:释放资源
getSocketAddress()得到IP+端口号
发送端要知道发送端IP地址和端口号,接收端只需要接收数据即可
用于封装UDP通信中的数据,在创建发送端和接收端的DatagramPacket对象时,使用的构造方法不同;接收端的构造方法只需要接收一个字节数组来存储接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还要指定发送端IP地址和端口号
构造方法:
1、DatagramPacket(byte[] buf, int length)
创建接收端DatagramPacket对象;创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号
2、DatagramPacket(byte[] buf, int offset, int length)
创建DatagramPacket对象时,在1的基础上增加参数offset,指定数据开始存放的地址
3、DatagramPacket(byte[] buf, int length, InetAddress address, int port)
创建发送端DatagramPacket对象;创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,还指定了数据包的IP地址(address)和端口号(port)
创建发送端程序和接收端程序;通信时只有接收端程序一直运行,才可以避免发送端数据没被接收
接收端程序
-
- public class Recieve {
- //1、创建接收端的DatagramSocket对象
- private DatagramSocket socket = null;
-
- public Recieve() throws SocketException {
- this.socket = new DatagramSocket(8900);
- }
-
- //2、定义一个DatagramPacket数据报对象,用于封装接收的数据
- private byte[] elem = new byte[1024];
- private DatagramPacket packet = new DatagramPacket(elem, elem.length);
-
- //3、接收数据
- public void recieve() throws IOException {
- while (true) {
- //1、等待接收数据报,没接收到之前处于阻塞状态
- socket.receive(packet);
- //2、将packet数据报封装的数据转化为字符串
- String ret = new String(packet.getData(), 0, packet.getLength());
- System.out.println(packet.getAddress() + ":" + packet.getPort() + ": 发送消息" + ret);
- }
- }
-
- public static void main(String[] args) throws IOException {
- Recieve recieve = new Recieve();
- recieve.recieve();
- }
- }
创建了UDP接收端程序;创建DatagramSocket对象时指定端口号8900,这样才可以通过这个端口号和发送端产生连接;当接收到信息时,DatagramPacket数据报对象会封装数据
运行接收端程序,阻塞在socket.receive()语句
发送端程序
发送端程序运行之前,接收端一直处于运行状态
- public class Send {
- Scanner scanner = new Scanner(System.in);
- //1、创建发送端的DatagramSocket对象
- private DatagramSocket socket = null;
-
- public Send() throws SocketException, UnknownHostException {
- this.socket = new DatagramSocket();
- }
-
- //2、发送消息
- public void send() throws IOException {
- System.out.println("输入发送消息的内容");
- String ret = scanner.nextLine();
- //1、定义一个DatagramPacket数据报对象,用于封装接收的数据和发送端的IP地址及端口号
- byte[] elem = ret.getBytes();
- DatagramPacket packet = new DatagramPacket(elem, elem.length, InetAddress.getByName("localhost"), 8900);
- //2、发送消息
- socket.send(packet);
- //3、释放资源
- socket.close();
- }
- public static void main(String[] args) throws IOException {
- Send send=new Send();
- send.send();
- }
- }
创建了UDP发送端程序;创建DatagramPacket对象时,需要给定接收的数据和发送端的IP地址及端口号,这个端口号要和接收端DatagramSocket对象的端口号一致,这样才可以保证数据传送成功
可能抛出异常:
当前使用的端口号已经被其他程序占有了,因为一个端口号上只可以运行一个程序
可以使用命令行窗口输入“netstat -anb”命令,检查当前端口的占有情况
实现两个用户的聊天,一个用户发送消息,另一个用户可以接收到消息
1、创建聊天界面,要输入两个用户的端口号;一个用户既可以接收消息,又可以发送消息
- //1、聊天界面 聊天进入的程序入口
- public class createroom {
-
- public static void main(String[] args) throws SocketException {
- //1、输入当前和目的的端口号
- Scanner scanner = new Scanner(System.in);
- System.out.println("输入当前聊天程序的端口号");
- int cur = scanner.nextInt();
- System.out.println("输入接收方的端口号");
- int dest = scanner.nextInt();
- System.out.println("聊天程序已经启动");
- DatagramSocket socket = new DatagramSocket(cur);//接收端和发送端
- //服务器一直运行 等待接收
- new receiveroom(socket).start();
- //发送端程序
- new sendroom(socket, dest).start();
- }
- }
输入当前端口号,聊天对象端口号:我们最后启动两个控制台,端口号,聊天对象端口号是相反的
每一个客户端设置一个DatagramSocket对象,既可以发送消息,又可以接收消息;同时,为了保证两个聊天程序客户端可以消息同步传递,使用多线程启动发送程序和接收程序
接收端
- //接收端
- public class receiveroom extends Thread {
- static DatagramSocket socket = null;
-
- public receiveroom(DatagramSocket s) {
- socket = s;
- }
-
- public void run(){
- while (true) {
- //1、接收数据
- byte[] elem = new byte[1024];
- DatagramPacket packet = new DatagramPacket(elem, elem.length);
- //2、显示接收数据
- try {
- socket.receive(packet);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- String ret = new String(packet.getData(), 0, packet.getLength());
- System.out.println("收到"+packet.getAddress() + ":" + packet.getPort() + ": 发送的消息: " + ret);
- }
- }
- }
发送端
-
- //发送消息界面
- public class sendroom extends Thread{
- static DatagramSocket sendSocket = null;
- static int dest = 0;
-
- public sendroom(DatagramSocket socket, int s) {
- dest = s;
- sendSocket = socket;
- }
-
- public void run() {
- Scanner scanner = new Scanner(System.in);
- //1、创建DatagramSocket对象
- while (true) {
- try {
- String ret = scanner.nextLine();
- // 1、创建DatagramPacket对象用于封装数据 IP地址和目的端口
- DatagramPacket packet = new DatagramPacket(ret.getBytes(), ret.getBytes().length, InetAddress.getByName("localhost"), dest);
- //2、发消息
- sendSocket.send(packet);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }
- }
4、结果检验
开启两个控制台:先开启一个后,设置idea
创建简单的文本
修改接收端做出的响应
- public class Recieve {
- //1、创建接收端的DatagramSocket对象
- private DatagramSocket socket = null;
-
- public Recieve() throws SocketException {
- this.socket = new DatagramSocket(8900);
- }
-
- //2、定义一个DatagramPacket数据报对象,用于封装接收的数据
- private byte[] elem = new byte[1024];
- private DatagramPacket packet = new DatagramPacket(elem, elem.length);
-
- //3、接收数据
- public void recieve() throws IOException {
- while (true) {
- //1、等待接收数据报,没接收到之前处于阻塞状态
- socket.receive(packet);
- //2、将packet数据报封装的数据转化为字符串
- String ret = new String(packet.getData(), 0, packet.getLength());
- //3、根据实现要求 进行操作
- String str=process(ret);
- System.out.println(packet.getAddress() + ":" + packet.getPort() + ": 发送消息" + str);
- }
- }
- private String process(String ret) throws IOException {
- BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\30283\\Desktop\\翻译.txt"));//字符输入缓冲流
- String str = null;
- while ((str = reader.readLine()) != null) {//按行读取
- if (str.contains(ret)) {//如果包含
- String[] s = str.split(" ");
- if (ret.equals(s[0])) {//是英文
- return s[1];
- } else if (ret.equals(s[1])) {//中文
- return s[0];
- }
- }
- }
- reader.close();
- return new String("没有找到");
- }
- public static void main(String[] args) throws IOException {
- Recieve recieve = new Recieve();
- recieve.recieve();
- }
- }
TCP和UDP的区别就是TCP是面向连接的;UDP的接收端和发送端之间可以任意的发送数据,而
TCP是强调客户端和服务器的,只有当客户端请求连接后,才可以开始通信;服务器不可以主动连接,只可以被动响应
客户端:发出请求
服务器:做出响应
JDK提供了两个类实现TCP通信;ServerSocket类,用于表示服务器,Socket类表示客户端;通信时,先建立ServerSocket对象,然后等待Socket对象发出连接请求
构造服务端 服务端端口号是需要被客户端使用的,因此一般都会给定端口号
构造方法:
1、ServerSocket()
创建ServerSocket对象时,没有指定端口号,因此不能监听任何端口,不可以直接使用;使用时要调用bind(SocketAddress endpoint)方法将其绑定到指定端口
2、ServerSocket(int port)
创建ServerSocket对象时,指定端口号;如果port=0,那么系统会随意分配一个没有被使用的端口号;
3、ServerSocket(int port, int backlog)
在2的基础上,增加参数backlog,
这个参数用于指定在服务器忙碌时,可以与之保持连接请求的等待客户端的数量,不指定时默认50
4、ServerSocket(int port, int backlog, InetAddress bindAddr)
在3的基础上,增加参数bindAddr,
这个参数用于指定相关的IP地址,适用于这个计算机上有多个网卡和多个IP的情况,使用时可以规定ServerSocket在哪一个网卡或IP地址上等待客户端连接
ServerSocket对象用于创建服务器,监听某一台计算机的这个端口,创建ServerSocket对象之后,调用accept()方法,接收来自客户端的请求;如果没有请求,服务器程序陷入阻塞,直到接收到了请求,返回Socket对象用于通信
Socket类
创建客户端程序
构造方法
1、Socket()
使用这个构造方法创建Socket对象,没有指定IP地址和端口号,意味着只是创建了客户端,没有连接服务器,需要调用connect(SocketAddress endpoint)方法,完成IP地址和端口号的绑定,其中endpoint封装了这两个属性
2、Socket(String host, int port)
使用这个构造方法创建Socket对象,指定IP地址和端口号,只不过host是字符串类型的IP地址
3、Socket(InetAddress address, int port)
使用这个构造方法创建Socket对象,指定IP地址和端口号,只不过address
是InetAddress
类型的IP地址
当客户端和服务端建立连接之后,数据是面向字节流的,通过I/O流的形式交互
服务器
- public class TCPreceive {
- //1、创建客户端服务器
- ServerSocket listen = null;
-
- public TCPreceive() throws IOException {
- this.listen = new ServerSocket(7788);
- }
-
- public void start() throws IOException {
- //2、服务端等待客户端发送连接
- Socket socket = listen.accept();
- System.out.println("服务器读取到了客户端的请求");
- //3、对连接内容进行操作
- try (InputStream inputStream = socket.getInputStream()) {//读
- try (OutputStream outputStream = socket.getOutputStream()) {//写
- Scanner scanner = new Scanner(inputStream);
- while (true) {
- //1、读取客户端发送的信息
- String ret = scanner.nextLine();
- if(ret.isEmpty()){
- System.out.println("断开连接");
- break;
- }
- //2、根据请求,计算响应
- String str = process(ret);
- //3、将响应返回客户端
- PrintWriter writer = new PrintWriter(outputStream);
- writer.println(str);//写入ret
- writer.flush();
- //4、输出响应
- System.out.println("收到" + socket.getInetAddress() + socket.getPort() + "的请求: " + ret + " 响应是: " + str);
- }
- socket.close();
- }
- }
- }
- private String process(String str){
- return str;
- }
-
- public static void main(String[] args) throws IOException {
- TCPreceive tcPreceive=new TCPreceive();
- tcPreceive.start();
- }
- }
客户端
- public class TCPSend {
- //1、设置客户端
- Socket socket = new Socket(InetAddress.getByName("localhost"), 7788);
-
- public TCPSend() throws IOException {
- }
-
- //2、发送请求
- public void start() throws IOException {
- //1、输入请求
- System.out.println("客户端发送请求");
- Scanner scanner = new Scanner(System.in);
- //2、发送请求
- try (InputStream inputStream = socket.getInputStream()) {//读
- try (OutputStream outputStream = socket.getOutputStream()) {//写
- while (true) {
- String ret = scanner.nextLine();
- //发送请求给客户端
- PrintWriter writer = new PrintWriter(outputStream);
- writer.println(ret);
- writer.flush();
- if(ret.isEmpty()){
- System.out.println("用户输入结束");
- break;
- }
- //得到响应
- Scanner scanner1 = new Scanner(inputStream);
- System.out.println("得到响应为: " + scanner1.nextLine());
- }
- socket.close();
- }
- }
- }
- public static void main(String[] args) throws IOException {
- TCPSend tcpSend=new TCPSend();
- tcpSend.start();
- }
-
- }
发现:启动多个客户端,是只有第一个客户端才会和服务器产生连接的;因为只有调用accept方法,才会让客户端和服务器产生连接
在实际情况中,一个服务器程序是可以被多个客户端访问的,这就使用到了多线程编程
- public class TCPreceive {
- //1、创建客户端服务器
- ServerSocket listen = null;
-
- public TCPreceive() throws IOException {
- this.listen = new ServerSocket(7788);
- }
-
- public void start() throws IOException {
- //2、服务端等待客户端发送连接
- //3、对连接内容进行操作
- while (true) {
- //让每一个客户端都可以调用到accept()方法
- Socket socket = listen.accept();
- System.out.println("服务器读取到了客户端的请求");
- //每个客户端的操作 都是独立的
- new Thread(() -> {
- try (InputStream inputStream = socket.getInputStream()) {//读
- try (OutputStream outputStream = socket.getOutputStream()) {//写
- while (true) {
- Scanner scanner = new Scanner(inputStream);
- //1、读取客户端发送的信息
- String ret = scanner.nextLine();
- if (ret.isEmpty()) {
- System.out.println("断开连接");
- break;
- }
- //2、根据请求,计算响应
- String str = process(ret);
- //3、将响应返回客户端
- PrintWriter writer = new PrintWriter(outputStream);
- writer.println(str);//写入ret
- writer.flush();
- //4、输出响应
- System.out.println("收到" + socket.getInetAddress() + socket.getPort() + "的请求: " + ret + " 响应是: " + str);
- }
- socket.close();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }).start();
- }
- }
- private String process(String str){
- return str;
- }
-
- public static void main(String[] args) throws IOException {
- TCPreceive tcPreceive=new TCPreceive();
- tcPreceive.start();
- }
- }
采用多线程的方式创建服务端程序;使用while循环,让每一个客户端都可以调用accept()方法和服务器产生连接,从而为每一个客户端建立一个线程,每一个线程都会和服务器一对一交互
服务器程序
- public class UpdateServer {
- private Scanner scan = new Scanner(System.in);
-
- //1、设置服务器
- private ServerSocket listen = null;
-
- public UpdateServer() throws IOException {
- this.listen = new ServerSocket(1000);
- }
-
- //2、等待客户端发出连接请求
- public void start() throws IOException {
- System.out.println("等待客户端发出连接请求");
- while (true) {
- Socket socket = listen.accept();
- System.out.println("接收到了客户端的连接请求");
- //发出响应 采用多线程
- new Thread(() -> {
- try {
- response(socket);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }).start();
- }
- }
-
- private void response(Socket socket) throws IOException {
- File dest = new File("C:\\Users\\30283\\Desktop\\拷贝");//设置最终拷贝目的地
- if (!dest.exists()) {
- dest.mkdir();//创建根目录
- }
- try (InputStream in = socket.getInputStream()) {
- try (OutputStream out = socket.getOutputStream()) {
- Scanner scanner = new Scanner(in,"UTF-8");
- while (true) {
- //1、接收客户端发出的文件路径
- String path = scanner.nextLine();
- if (path.isEmpty()) {
- System.out.println("IP地址: " + socket.getInetAddress() + " 端口号 " + socket.getPort() + "断开连接");
- break;
- }
- //2、遍历这个文件,将内容拷贝到上传路径
- File file = new File(path);//源文件
- boolean ret = copy(file, dest);
- //3、将响应返回客户端
- String r=null;
- if (ret) {
- r = "文件提交完成";
- } else {
- r = "文件提交出现错误,请检查输入路径";
- }
- PrintWriter writer=new PrintWriter(out);
- writer.println(r);
- writer.flush();
- }
- socket.close();
- }
- }
- }
-
- private boolean copy(File file, File dest) throws IOException {
- if (!file.exists()) {//如果路径不存在
- System.out.println("路径不存在");
- return false;
- }
- if (file.isFile()) {//如果是个文件 直接拷贝
- System.out.println(file.getAbsoluteFile()+" : 输入对应文件上传后的名称");
- String name = scan.nextLine();
- File newFile = new File(dest.getAbsoluteFile() + "\\" + name);
- if (!newFile.exists()) {
- newFile.createNewFile();
- }
- FileReader reader = new FileReader(file);
- FileWriter writer = new FileWriter(newFile);
- char[] elem = new char[1024];
- int len = 0;
- while ((len = reader.read(elem)) != -1) {
- writer.write(elem, 0, len);
- writer.flush();
- }
- reader.close();
- writer.close();
- return true;
- }
- if (file.isDirectory()) {//如果是目录
- File[] files = file.listFiles();
- System.out.println(file.getAbsoluteFile()+" :输入对应子目录名称");
- String ret = scan.nextLine();
- File newpath = new File(dest + "\\"+ret);
- if (!newpath.exists()) {
- newpath.mkdir();//创建一个目录
- }
- for (File newfile : files) {
- copy(newfile, newpath);
- }
- }
- return true;
- }
- public static void main(String[] args) throws IOException {
- UpdateServer updateServer=new UpdateServer();
- updateServer.start();
- }
- }
客户端程序
- public class UpdateClient {
- //1、设置客户端
- Socket socket=new Socket(InetAddress.getLocalHost(),1000);
-
- public UpdateClient() throws IOException {
- }
- //2、发出请求
- public void start() throws IOException {
- Scanner scanner = new Scanner(System.in);
-
- try (InputStream in = socket.getInputStream()) {
- try (OutputStream out = socket.getOutputStream()) {
- Scanner scan = new Scanner(in);
- PrintWriter writer = new PrintWriter(out);
- while (true) {
- //1、发出请求
- System.out.println("输入拷贝的文件路径");
- String path = scanner.nextLine();
- writer.println(path);
- writer.flush();
- if(path.isEmpty()) {
- System.out.println(socket.getInetAddress() + " " + socket.getPort() + "停止输入");
- break;
- }
- //2、收到响应
- System.out.println(scan.nextLine());
- }
- socket.close();
- }
- }
- }
-
- public static void main(String[] args) throws IOException {
- UpdateClient updateClient=new UpdateClient();
- updateClient.start();
- }
-
- }