TCP和UDP协议基础

UDP知识点:

  1. 使用接口为 DatagramSocket 和 DatagramPacket
  2. socket 就像码头,packet 就像集装箱
  3. 想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定接收端IP地址和端口号。

  4. udp是 DatagramSocket 和 DatagramSocket 之间进行通信,称为发送端与接收端。
  5. tcp是 Socket 和 ServerSocket 之间进行通信,称为客户端与服务器端。

以下为代码示例:

发送端socket无需指定端口号,可以由系统随机分配一个端口即可发送数据。

发送端packet必须指定目的主机的Ip地址和端口号,相当于寄快递时在快递上写明地址。

接收端socket必须指定监听本机的哪个端口号,即要与packet绑定的目标端口号一致。

接收端packet必须包含一个空byte数组,用以存放接收到的内容,此外无需再指定任何ip和端口号。

注意接收端接收到数据包后,拆包时要截取发送内容长度作为new String的长度,否则,String长度就是字节数组的长度1024,浪费资源。

发送端:

package com.ch32.udp;

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

/**
 * @ClassName: SendSocket 
 * @Description: udp的发送端socket 
 * @author Lulu
 * @date 2018年8月19日 下午7:05:46 
 */
public class SendSocket {
    public static void main(String[] args) {
        // 发送端socket和数据包
        DatagramSocket socket = null;
        DatagramPacket packet = null;
        // 定义要发送的数据
        byte[] bytes = "你好".getBytes();
        try {
            // new一个packet包,装入数据,并绑定目的ip和端口号(类似寄快递)
            packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 12306);
            // 发送端无需指定端口号,让系统随机分配一个没被占用的端口号即可
            socket = new DatagramSocket();
            // 发送数据包
            socket.send(packet);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (!socket.isClosed()) {
                socket.close();
            }
        }
    }

}

接收端:

package com.ch32.udp;

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

public class RecieveSocket {
    public static void main(String[] args) {
        // 接收端的socket
        DatagramSocket recSocket = null;
        // 接收端定义一个空字节数组,用于接受数据
        byte[] bytes = new byte[1024];
        // 接收端的包
        DatagramPacket recPacket = new DatagramPacket(bytes, 1024);
        try {
            // 接受端socket需要指定监听的本机端口号(相当于收快递时到收件地址的快递点查看)
            recSocket = new DatagramSocket(12306);
            recSocket.receive(recPacket);
            // byte数组转为字符串,数组长度为1024,太浪费资源,截取发送数据的长度大小
            String rec = new String(recPacket.getData(), 0, recPacket.getLength());
            System.out.println(rec + " from " + recPacket.getAddress().getHostName()
                    + " : " + recPacket.getPort());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (!recSocket.isClosed()) {
                recSocket.close();
            }
        }
    }
}

先运行接收端,receieve()方法会阻塞,等待数据发送过来,再运行发送端,接收端控制台就会打印出数据。

你好 from 127.0.0.1 : 58147

 

TCP知识点:

  1. 建立字节流对象OutputStream和InputStream进行数据传输,一个连接通路需要Socket的两个流对象支持,不需要自己创建,直接从套接字socket中获取。
  2. 服务器ServerSocket没有内置流功能, 它需要通过accept获取当前连接的客户端socket,从socket中取得IO流,进行数据传输。
  3. 套接字socket只要通过构造器创建成功,就会和服务器进行连接,连接失败的话就抛异常。
  4. 字节流要传字符串,需要用 "某字符串".getBytes() 获取字节数组。
  5. 客户端socket调用shutdown()方法,表示已经发送数据完毕,相当于到达read()方法返回-1的末尾。但该方法并不关闭流,只是将对应的InputStream或OutputStream禁用。
  6. 在程序示例中, 如果不调用shutdown()方法,服务器端的read()方法就无法读到返回值为-1的末尾,read()方法会一直阻塞,等待read()结束,客户端也一直等待服务器端read()结束后发送的反馈消息,造成二者互相等待,程序无法终止的现象。

1. TCP客户端与服务器端传输文件示例:

服务器端代码

package com.ch32.picture;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServer {
    public static void main(String[] args) {
        Socket client = null;
        InputStream is = null;
        OutputStream os = null;
        BufferedOutputStream bos = null;
        ServerSocket server = null;
        
        try {
            // new serverSocket,并指定监听哪个端口
            server = new ServerSocket(12345);
            // 获取连接当前端口服务器的客户端
            client = server.accept();
            // 获取客户端InputStream
            is = client.getInputStream();
            // new一个BufferedOutputStream,用于向指定地址客户端上传的文件字节
            bos = new BufferedOutputStream(new FileOutputStream("d:\\server\\yuiri.jpg"));
            byte[] bytes = new byte[1024];
            int len;
            while ((len = is.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
            
            // 传输完成,服务器通过客户端OutputStream向客户端发送“上传成功”的信息
            os = client.getOutputStream();
            os.write("上传成功".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
         // 大批关流操作
            try {
                if (client != null && !client.isClosed()) {
                    client.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            // server一般不会关,这里不需要一直开着,所以就关闭了
            try {
                if (server != null && !server.isClosed()) {
                    server.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
        
        }
    }
}

客户端代码

package com.ch32.picture;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @ClassName: Client 
 * @Description: 上传图片客户端
 * @author Lulu
 * @date 2018年8月21日 上午8:45:02 
 */
/*
 * 1. new Socket,连接到服务器
 * 2. 获取socket的InputStream,用于读取数据源的字节
 * 3. 获取socket的OutputStream,用于向服务器传输文件
 * 4. 把图片数据传输到socket输出流中,也就是传输到服务器
 * 5. shutdown,相当于添加结束标记,告知服务器该文件已经传输完成
 */
public class Client {
    public static void main(String[] args) {
        Socket client = null;
        OutputStream os = null;
        InputStream is = null;
        BufferedInputStream bis = null;
        try {
            // 客户端绑定服务器端口号
            client = new Socket("127.0.0.1", 12345);
            // 获取客户端OutputStream
            os = client.getOutputStream();
            // 获取客户端InputStream
            is = client.getInputStream();
            // 从数据源读入文件字节
            bis = new BufferedInputStream(new FileInputStream("d:\\yuiri.jpg"));
            byte[] bytes = new byte[1024];
            int len;
            while((len = bis.read(bytes)) != -1) {
                // 从数据源读入的字节写入os中
                os.write(bytes, 0, len);
            }
            // 文件读取结束,shutdown一下,所有数据都会被传输
            client.shutdownOutput();
            
            // 上传完成,等待接收服务器端“上传成功”的消息
            byte[] info = new byte[1024];
            //把反馈信息存储到info数组中,并记录字节个数
            int length = is.read(info);
            System.out.println(new String(info, 0, length));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 大批关流操作
            try {
                if (client != null && !client.isClosed()) {
                    client.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
        }
    }
}

2. 多线程实现客户端上传图片到服务器端的小例子

说明:这里模拟现实中服务器始终运行,对于连接它的客户端,每新连接一个客户端就新开一个线程与其进行通信,不同的客户端请求都对应一个线程来处理。

客户端代码不变

package com.ch32.uploadThread;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @ClassName: MyClient 
 * @Description: 客户端不需要变,本身开一个客户端就相当于一个新线程 
 * @author Lulu
 * @date 2018年8月21日 下午5:31:55 
 */
public class MyClient {
    public static void main(String[] args) {
        Socket client = null;
        OutputStream os = null;
        InputStream is = null;
        BufferedInputStream bis = null;
        try {
            // 客户端绑定服务器端口号
            client = new Socket("127.0.0.1", 9999);
            // 获取客户端OutputStream
            os = client.getOutputStream();
            // 获取客户端InputStream
            is = client.getInputStream();
            // 从数据源读入文件字节
            bis = new BufferedInputStream(new FileInputStream("d:\\yuiri.jpg"));
            byte[] bytes = new byte[1024];
            int len;
            while((len = bis.read(bytes)) != -1) {
                // 从数据源读入的字节写入os中
                os.write(bytes, 0, len);
            }
            // 文件读取结束,shutdown一下,所有数据都会被传输
            client.shutdownOutput();
            
            // 上传完成,等待接收服务器端“上传成功”的消息
            byte[] info = new byte[1024];
            //把反馈信息存储到info数组中,并记录字节个数
            int length = is.read(info);
            System.out.println(new String(info, 0, length));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 大批关流操作
            try {
                if (client != null && !client.isClosed()) {
                    client.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
            
            try {
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }
        }
    }
}

服务器端代码

package com.ch32.uploadThread;

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

/**
 * @ClassName: MyServer 
 * @Description: 服务器端 
 * @author Lulu
 * @date 2018年8月21日 下午3:55:31 
 */
public class MyServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        Socket client = null;
        
        try {
            // 新建server,绑定9999端口
            server = new ServerSocket(9999);
            // 服务器无限循环,将处理所有连接到它9999端口的客户端请求
            while (true) {
                // 等待获取连接到该服务器9999端口上的客户端
                client = server.accept();
                // 开线程来处理这个客户端请求
                Thread upload = new Thread(new UploadThread(client));
                upload.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (client != null && !client.isClosed()) {
                    client.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // server.close(); // server不需要关闭
    }
}

服务器处理客户端请求的线程程序

package com.ch32.uploadThread;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Random;

/**
 * @ClassName: UploadThread 
 * @Description: 服务器端的上传线程,来一个客户端请求开一个线程单独处理
 * @author Lulu
 * @date 2018年8月21日 下午4:33:04 
 */
public class UploadThread implements Runnable {
    // 持有一个私有final的socket对象,只能通过构造器赋值,赋值后不可以再改变
    private final Socket client;
    
    UploadThread(Socket client) {
        this.client = client;
    }
    
    @Override
    public void run() {
        InputStream is = null;
        OutputStream os = null;
        BufferedOutputStream bos = null;
        try {
            // 获取当前客户端的Ip地址
            String clientIp = client.getInetAddress().getHostName();
            // 为防止文件重名覆盖,存储在服务器端时,随机生成一个文件名
            // myServer+[上传客户端ip]+当前毫秒数+四位随机数
            String fileName = "myServer[" + clientIp + "]" + System.currentTimeMillis() + new Random().nextInt(9999) + ".jpg";
            // 新建bos向指定位置写入客户端上传的文件
            bos = new BufferedOutputStream(new FileOutputStream("d:\\server\\" + fileName));
            // 从客户端获取inputStream
            is = client.getInputStream();
            // 读取客户端上传的文件字节
            byte[] bytes = new byte[1024];
            int len = -1;
            while ((len = is.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
            
            // 上传完成,向客户端发送反馈信息
            os = client.getOutputStream();
            os.write("图片上传成功".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (os != null) {
                        os.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (bos != null) {
                        bos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }

}

为了防止文件重名导致文件被覆盖,上传到服务器上的文件都会重命名,使用ip,毫秒数,随机数等组合命名。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值