Java中提供了socket套接字机制以及UDP、TCP两种通信协议的编程类。利用它们可以实现基于UDP和TCP的端到端的网络通信。为应用程序提供网络通信功能。
学计算机网络时,大致学过Berkeley套接字的机制,拥有一系列的操作原语,从建立连接(SOCKET)到释放(CLOSE),socket的最大好处就在于它能够让网络通信变得如打电话一样简单,只需要知道标示就可以建立通信,而不用去关心具体的实现过程。socket就是利用IP地址和Port端口号来建立连接。但是与电话系统不同的是,socket是基于客户端/服务器模型设计的,因此它为客户端和发送端提供了不同的系统调用,这一点在java中体现在不同的构造函数来创建socket对象~下面简单的介绍下java中的socket通信。
UDP协议类:
简要的例子说明下:
发送端的实现:
import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; public class UDPSend { /** * 描述 : <描述函数实现的功能>. <br> *<p> * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub try { DatagramSocket ds = new DatagramSocket(); String str = "我是YJH!"; DatagramPacket dp = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getByName("172.26.28.58"), 3500); ds.send(dp); ds.close(); } catch(Exception e) { throw e; } } }
接收端的实现:
import java.io.IOException; import java.net.DatagramSocket; import java.net.DatagramPacket; public class UDPRecv { /** * 描述 : <描述函数实现的功能>. <br> *<p> * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub try { final DatagramSocket ds = new DatagramSocket(3500); new Thread(new Runnable() { public void run() { byte[] buf = new byte[1024]; DatagramPacket dp = new DatagramPacket(buf, 1024); while(true) { try {//for IOException ds.receive(dp); String strRecv = new String(dp.getData(), 0, dp.getLength()); System.out.println(strRecv); String strRecv1 = new String("本消息来自:" + dp.getAddress().getHostName() + "的 " + dp.getPort()); System.out.println(strRecv1); if(strRecv.equalsIgnoreCase("quit")) break; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } ds.close(); } }).start(); } catch(Exception e) { throw e; } } }
UDP主要运用DatagramSocket类和DatagramPacket类 ,在发送端DatagramSocket不需要指定端口号,只需在DatagramPacket数据包里指定ip地址和端口号就可以了,注意在这里使用的InetAddress的getByName(String ip),而不是接收端的getHostName(),后者需要InetAddress对象中包含ip地址信息。
这里发送端比较简单,在接收端为了实现能够多次接受不同客户端发送的DatagramPacket我接受的数据包类dp放在了不得线程里,并使用while(true)让它能够多次接受。当然别忘了在DatagramSocket对象的墙面加上final,因为在匿名类中使用了它,具体原因与java的垃圾回收机制有关,加上final就可以保证它不会被因为重新指向其他对象而使得内部类中对象这里是(ds)失去了引用,并延长了ds的生命周期。
TCP协议类:
客户端的实现:
import java.net.*; import java.io.*; public class TcpClient { /** * 描述 : <描述函数实现的功能>. <br> *<p> * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { //建立连接,创建流对象 Socket s = new Socket(InetAddress.getByName("172.26.28.58"), 8001); InputStream ips = s.getInputStream(); OutputStream ops = s.getOutputStream(); BufferedReader bf = new BufferedReader(new InputStreamReader(ips)); BufferedReader bkey = new BufferedReader(new InputStreamReader(System.in)); DataOutputStream dos = new DataOutputStream(ops); //交换数据 while(true) { String strWord = bkey.readLine(); dos.writeBytes(strWord + System.getProperty("line.separator")); if(strWord.equalsIgnoreCase("quit")) break; else { //bf.readLine()的位置一定要在dos.write()之后,否则会处于阻塞状态! System.out.println("Return Information: " + bf.readLine()); } } //释放流对象 bf.close(); bkey.close(); dos.close(); s.close(); } catch(Exception e) { e.printStackTrace(); } } }
补充:
BufferedReader类:
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
在通常情况下,Reader所做的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。因此,使用BufferedReader包装所有其read()操作可能开销很大的Reader类(如InputStreamReader和FileReader)。
例如:
BufferedReader bf = new BufferedReader(newInputStreamReader(ips));
BufferedReader bf = new BufferedReader(newFileReader(“foo.in”));
该类有几个重要方法:
read();读取单个字符;
read(char[] cbuf, int offset, int len);将字符读入数组cbuf的某一部分;
readLine();读取一文本行,(\n、\r终止);
InputStreamReader类:
该类是字节流通向字符流的桥梁,它使用指定的charset读取字节并将其解码为字符。它使用的字符集可以由名称指定或是显示给定,或者可以接受平台默认的字符集。(new InputStreamReader(InputStream ips, Charset cset));
方法:
read();
read(char[] buf, int offset, int len);
ready();判断此流是否已经准备好用于读取。如果其输入缓冲区不为空,或者可从底层字节流读取字节,则 InputStreamReader 已做好被读取准备。
FileReader类:
继承inputStreamReader的方法;
DataOutputStream类:
该类是继承与FilterOutputStream的,直接来说相当于重写了OutputStream的方法,更方便与使用了。
服务器端的实现:
在服务器端,同样将数据交换放在独立的线程中,用两个类来清楚地展现出来:
调用类(服务器类):
import java.net.*; public class TCPServer02 { /** * 描述 : <描述函数实现的功能>. <br> *<p> * @param args */ static int i = 0; public static void main(String[] args) { // TODO Auto-generated method stub try { ServerSocket ss = new ServerSocket(8001); while(true) { Socket s = ss.accept();//如果没有接受到连接请求,程序将阻塞在此 Servicer servicer = new Servicer(s); new Thread(servicer).start(); System.out.println("打开第 " + (++i) + " 个连接!"); } } catch(Exception e) { e.printStackTrace(); } } }被调用类(数据交换类):
import java.net.*; import java.io.*; import java.util.*; import java.text.*; public class Servicer implements Runnable { Socket s; Servicer(Socket s) { this.s = s; } public void run() { try { InputStream ips = s.getInputStream(); OutputStream ops = s.getOutputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(ips)); DataOutputStream ds = new DataOutputStream(ops); while(true) { String strWord = br.readLine();//没有接受到将阻塞 System.out.println("客户端发送的字符串为:" + strWord + "\n" + "时间:" + now()); if(strWord.equalsIgnoreCase("quit")) { System.out.println("释放第 " + TCPServer02.i + "个连接"); break; } String strReturn = "Already received!"; ds.writeBytes(strReturn + System.getProperty("line.separator")); } br.close(); ds.close(); s.close(); } catch(Exception e) { e.printStackTrace(); } } private String now() { DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.format(new Date()); } }与客户端不同的是这里的Socket是依附于ServerSocket对像的(服务器端只有一个,在这里没有键盘输入功能,这也是一对多服务器的本质决定的),其他的说明和客户端基本相同,不再重复。