在大学期间做过基于远程线程注入技术的木马,所以对套接字、UDP协议、TCP协议诸如此类网络通讯相关的内容早已熟知,不过那时候编写使用的语言是C++,如今学习java再次涉及到这部分知识,也算另一种温习吧。
UDP和TCP这两个协议是通用协议,全球大部分网络通讯都采用这两者来实现。UDP协议是无连接、不可靠的协议。其优点在于不需要建立连接,速度快,缺点则是数据包大小限制在64K以内,且容易丢包。TCP协议是有连接、可靠的协议。它有着必须建立连接且需三次握手完成的特点。TCP协议相较于UDP协议来说更可靠,适合大数据传输,不足在于效率稍低。Socket即套接字,是网络通讯采用的方式,它起到连接两端的作用。
先熟悉几个类。IP地址类:InetAddress,此类没有构造函数。UDP通讯的类:DatagramSocket、DatagramPacket。TCP通讯的类:Socket、ServerSocket。
下面是以UDP协议实现的聊天程序,因为是简单演示因此两端都在一台终端上。
import java.net.*;
import java.io.*;
class Send implements Runnable{
private DatagramSocket ds;
public Send(DatagramSocket ds){
this.ds = ds;
}
public void run(){
try{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line=bufr.readLine())!=null){
if("886".equals(line))
break;
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10002);
ds.send(dp);
}
}
catch(Exception e){
throw new RuntimeException("发送端失败");
}
}
}
class Rece implements Runnable{
private DatagramSocket ds;
public Rece(DatagramSocket ds){
this.ds = ds;
}
public void run(){
try{
while(true){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+":"+data);
}
}
catch(Exception e){
throw new RuntimeException("接收端失败");
}
}
}
class ChatDemo{
public static void main(String[] args) throws Exception {
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receSocket = new DatagramSocket(10002);
new Thread(new Send(sendSocket)).start();
new Thread(new Rece(receSocket)).start();
}
}
上面的这个演示程序基本涉及了UDP协议下实现网络通讯所需要用到的方法,为了提高效率还是用到了IO流中的缓冲区。DatagramSocket中的send和receive方法分别用来从套接字发送和接收数据包,需要注意的是receive方法是阻塞式的。DatagramPacket中,getAddress方法用于返回IP地址,不过其返回类型是InetAddress,因此如果要单纯返回IP地址还需要再套用InetAddress的getHostAddress方法;getData方法用于返回数据缓冲区;getLength方法则是获取数据包长度。
再来了解TCP协议下的通讯传输。鉴于其有明确的客户端和服务端,先明确这两端的执行步骤。客户端:1,创建Socket服务,并指定要连接的主机和端口。2,获取Socket输出流,将数据写到该流并发送给服务端。3,获取Socket输入流,将服务端反馈的数据获取并进行相关操作。4,关闭客户端资源。服务端:1,创建ServerSocket服务,并监听一个端口。2,通过accept获取Socket对象,没有连接则等待。3,通过Socket对象的读取流读取发送来的数据。4,获取输出流并反馈信息给客户端。5,关闭获取的客户端套接字,关闭服务端(可选)。
通过下面这个上传文件小程序来理解。
import java.net.*;
import java.io.*;
class TextClient{
public static void main(String[] args) throws Exception {
Socket s = new Socket("192.168.1.254",10006);
BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java"));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line=bufr.readLine())!=null){
out.println(line);
}
s.shutdownOutput();//关闭客户端的输出流。相当于给流中加入一个结束标记-1;
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = bufIn.readLine();
System.out.println(str);
bufr.close();
s.close();
}
}
class TextServer{
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(10006);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(new FileWriter("server.txt"),true);
String line = null;
while((line=bufIn.readLine())!=null){
out.println(line);
}
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
pw.println("上传成功");
out.close();
s.close();
ss.close();
}
}
上面的代码需要说明下的是,为了方便就将上传的文件强行确定文件类型和名字存放为server.txt,在实际完成上传功能的编程中是不会这样处理的。之所以用PrintWriter替代了BufferedWriter,是看中了其既可以操作字符流也可以操作字节流并且自动刷新的特性,方便简化代码。用shutdownOutput关闭客户端输出流是为了使服务端接收到客户端传输数据结束的标识,避免出现服务端持续等待,而客户端也无法获得服务端反馈的情况。