UDP知识点:
- 使用接口为 DatagramSocket 和 DatagramPacket
- socket 就像码头,packet 就像集装箱
-
想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定接收端IP地址和端口号。
- udp是 DatagramSocket 和 DatagramSocket 之间进行通信,称为发送端与接收端。
- 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知识点:
- 建立字节流对象OutputStream和InputStream进行数据传输,一个连接通路需要Socket的两个流对象支持,不需要自己创建,直接从套接字socket中获取。
- 服务器ServerSocket没有内置流功能, 它需要通过accept获取当前连接的客户端socket,从socket中取得IO流,进行数据传输。
- 套接字socket只要通过构造器创建成功,就会和服务器进行连接,连接失败的话就抛异常。
- 字节流要传字符串,需要用 "某字符串".getBytes() 获取字节数组。
- 客户端socket调用shutdown()方法,表示已经发送数据完毕,相当于到达read()方法返回-1的末尾。但该方法并不关闭流,只是将对应的InputStream或OutputStream禁用。
- 在程序示例中, 如果不调用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,毫秒数,随机数等组合命名。