JAVA网络编程
1.1 UDP
在网络数据的传输过程中,需要用到网络协议,java网络编程主要是基于TCP协议与UDP协议。其中UDP协议是一种无连接的传输协议,其在处理数据包时不提供数据包分组、组装和不能对数据包进行排序,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包而言UDP的额外开销很小。吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。虽然UDP是一个不可靠的协议,但它是分发信息的一个理想协议。例如,在屏幕上报告股票市场、显示航空信息等等。UDP也用在路由信息协议RIP(Routing Information Protocol)中修改路由表。在这些应用场合下,如果有一个消息丢失,在几秒之后另一个新的消息就会替换它。UDP广泛用在多媒体应用中。
1.2 TCP
TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:
(1)基于流的方式;
(2)面向连接;
(3)可靠通信方式;
(4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;
(5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。
为满足TCP协议的这些特点,TCP协议做了如下的规定: [10]
①数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
②到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
③超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;
④滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
⑤失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
⑥重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
⑦数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。
1.3 UDP与TCP的比较
UDP和TCP协议的主要区别是两者在如何实现信息的可靠传递方面不同。TCP协议中包含了专门的传递保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送方只有在接收到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止。与TCP不同,UDP协议并不提供数据传送的保证机制。如果在从发送方到接收方的传递过程中出现数据包的丢失,协议本身并不能做出任何检测或提示。因此,通常人们把UDP协议称为不可靠的传输协议。
TCP 是面向连接的传输控制协议,而UDP 提供了无连接的数据报服务;TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;UDP 在传输数据前不建立连接,不对数据报进行检查与修改,无须等待对方的应答,所以会出现分组丢失、重复、乱序,应用程序需要负责传输可靠性方面的所有工作;UDP 具有较好的实时性,工作效率较 TCP 协议高;UDP 段结构比 TCP 的段结构简单,因此网络开销也小。TCP 协议可以保证接收端毫无差错地接收到发送端发出的字节流,为应用程序提供可靠的通信服务。对可靠性要求高的通信系统往往使用 TCP 传输数据。
2.1基于UDP的java网络编程
client端发送文件,代码如下:
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class client {
public static void main(String[] args) throws Exception {
//数据缓冲字节数组
byte[] bt =new byte[1024];
//文件名称
String fileName ="Protoform trailer.mp4";
//文件路径
String filePath = "E:/file/rail_apps/";
//创建文件输入流
File file = new File(filePath+fileName);
InputStream is =new FileInputStream(file);
int len;
DatagramPacket datagramPacket;
DatagramSocket socket =new DatagramSocket();
InetAddress ip=InetAddress.getByName("127.0.0.1");
//传输文件名称
datagramPacket=new DatagramPacket(fileName.getBytes(),0,fileName.length(),ip,9999);
socket.send(datagramPacket);
//传输文件数据,当len为-1时表示所能读取到的数据为空,终止传输
while ((len=is.read(bt))!=-1){
datagramPacket=new DatagramPacket(bt,0,len,ip,9999);
socket.send(datagramPacket);
}
//发送特殊包,设置数据包长度为0,便于在服务端接收判断文件发送完毕
socket.send(new DatagramPacket(fileName.getBytes(),0,ip,9999));
}
}
server端接收文件,代码如下
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.LinkedList;
public class _07Test {
public static void main(String[] args) throws Exception {
//数据缓冲字节数组
byte[] bt =new byte[1024];
DatagramPacket datagramPacket =new DatagramPacket(bt,0,bt.length);
OutputStream outputStream = new FileOutputStream("d:/测试.mp4");
LinkedList<DatagramPacket> list =new LinkedList();
int len = datagramPacket.getLength();
DatagramSocket socket =new DatagramSocket(9999);
socket.receive(datagramPacket);
list.add(datagramPacket);
HandleThread handleThread=new HandleThread(list,outputStream);
handleThread.run();
while (true){
socket.receive(datagramPacket);
if(datagramPacket.getLength()==0){
break;
}
list.add(datagramPacket);
}
}
}
class HandleThread extends Thread {
private LinkedList<DatagramPacket> linkedList;
private OutputStream outputStream;
public HandleThread(LinkedList linkedList,OutputStream outputStream){
this.linkedList=linkedList;
this.outputStream=outputStream;
}
@Override
public void run() {
DatagramPacket datagramPacket;
while(true){
datagramPacket=linkedList.poll();
if(datagramPacket==null){
break;
}
System.out.println(new String(datagramPacket.getData()));
byte[] bt=datagramPacket.getData();
int len=datagramPacket.getLength();
try {
outputStream.write(bt,0,len);
} catch (IOException e) {
e.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.2基于BIO的TCP传输
客户端代码如下:
// 客户端
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10000);
//是本地的流,用来读取本地文件的.
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("socketmodule\\ClientDir\\1.jpg"));
//写到服务器 --- 网络中的流
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
int b;
while((b = bis.read())!=-1){
bos.write(b);//通过网络写到服务器中
}
bos.flush();
//给服务器一个结束标记,告诉服务器文件已经传输完毕
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) !=null){
System.out.println(line);
}
bis.close();
socket.close();
}
}
服务端代码如下
// 服务器
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
//网络中的流,从客户端读取数据的
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//本地的IO流,把数据写到本地中,实现永久化存储
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("socketmodule\\ServerDir\\copy.jpg"));
int b;
while((b = bis.read()) !=-1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
bos.close();
accept.close();
ss.close();
}
}