JAVA网络编程
参考<<疯狂JAVA编程>>中的第17章 网络编程
InetAddress简介
InetAddress是Java对IP地址的封装,在java.net中有许多类都使用到了InetAddress,包括ServerSocket,Socket,DatagramSocket等等
import java.net.*; /** * 演示InetAddress类的基本使用 */ public class InetAddressDemo { public static void main(String[] args) { try{ //使用域名创建对象 InetAddress inet1 = InetAddress.getByName("www.163.com"); System.out.println(inet1);// www.163.com/220.181.28.50 //使用IP创建对象 InetAddress inet2 = InetAddress.getByName("127.0.0.1"); System.out.println(inet2);// /127.0.0.1 //获得本机地址对象 InetAddress inet3 = InetAddress.getLocalHost(); System.out.println(inet3);// chen/192.168.1.100 //获得对象中存储的域名 String host = inet3.getHostName(); System.out.println("域名:" + host);// 域名:chen //获得对象中存储的IP String ip = inet3.getHostAddress();// IP:192.168.1.100 System.out.println("IP:" + ip); }catch(Exception e){} } }
URLDecoder和URLEncoder简介
URLDecoder和URLEncoder主要完成application/x-www-form-urlencoded字符串和普通字符串之间的相关转化
import java.net.*; public class URLDecoderTest{ public static void main(String[] args) throws Exception{ //将application/x-www-form-urlencoded字符串 //转换成普通字符串 String keyWord = URLDecoder.decode("%E6%9D%8E%E5%88%9A+j2ee", "UTF-8"); System.out.println(keyWord);//李刚 j2ee //将普通字符串转换成 //application/x-www-form-urlencoded字符串 String urlStr = URLEncoder.encode("ROR敏捷开发最佳指南" , "GBK"); System.out.println(urlStr);//ROR%C3%F4%BD%DD%BF%AA%B7%A2%D7%EE%BC%D1%D6%B8%C4%CF } }
URL和URLConnection介绍(实现多线程下载,以及断点下载)
URL的openConnection()方法返回一个URLConnection对象,该对象表示应用程序和URL之间的通信链接。程序可以通过URLConnection对象向该URL发送请求,读取URL引用的资源。
import java.io.*; import java.net.*; //定义下载从start到end的内容的线程 class DownThread extends Thread { //定义字节数组(取水的竹筒)的长度 private final int BUFF_LEN = 32; //定义下载的起始点 private long start; //定义下载的结束点 private long end; //下载资源对应的输入流 private InputStream is; //将下载到的字节输出到raf中 private RandomAccessFile raf ; //构造器,传入输入流,输出流和下载起始点、结束点 public DownThread(long start , long end , InputStream is , RandomAccessFile raf) { //输出该线程负责下载的字节位置 System.out.println(start + "---->" + end); this.start = start; this.end = end; this.is = is; this.raf = raf; } public void run() { try { is.skip(start); raf.seek(start); //定义读取输入流内容的的缓存数组(竹筒) byte[] buff = new byte[BUFF_LEN]; //本线程负责下载资源的大小 long contentLen = end - start; //定义最多需要读取几次就可以完成本线程的下载 long times = contentLen / BUFF_LEN + 4; //实际读取的字节数 int hasRead = 0; for (int i = 0; i < times ; i++) { hasRead = is.read(buff); //如果读取的字节数小于0,则退出循环! if (hasRead < 0) { break; } raf.write(buff , 0 , hasRead); } } catch (Exception ex) { ex.printStackTrace(); } //使用finally块来关闭当前线程的输入流、输出流 finally { try { if (is != null) { is.close(); } if (raf != null) { raf.close(); } } catch (Exception ex) { ex.printStackTrace(); } } } } public class MutilDown { public static void main(String[] args) { final int DOWN_THREAD_NUM = 4; final String OUT_FILE_NAME = "down.jpg"; InputStream[] isArr = new InputStream[DOWN_THREAD_NUM]; RandomAccessFile[] outArr = new RandomAccessFile[DOWN_THREAD_NUM]; try { //创建一个URL对象 URL url = new URL("http://images.china-pub.com/" + "ebook35001-40000/35850/shupi.jpg"); //以此URL对象打开第一个输入流 isArr[0] = url.openStream(); long fileLen = getFileLength(url); System.out.println("网络资源的大小" + fileLen); //以输出文件名创建第一个RandomAccessFile输出流 outArr[0] = new RandomAccessFile(OUT_FILE_NAME , "rw"); //创建一个与下载资源相同大小的空文件 for (int i = 0 ; i < fileLen ; i++ ) { outArr[0].write(0); } //每线程应该下载的字节数 long numPerThred = fileLen / DOWN_THREAD_NUM; //整个下载资源整除后剩下的余数 long left = fileLen % DOWN_THREAD_NUM; for (int i = 0 ; i < DOWN_THREAD_NUM; i++) { //为每个线程打开一个输入流、一个RandomAccessFile对象, //让每个线程分别负责下载资源的不同部分。 if (i != 0) { //以URL打开多个输入流 isArr[i] = url.openStream(); //以指定输出文件创建多个RandomAccessFile对象 outArr[i] = new RandomAccessFile(OUT_FILE_NAME , "rw"); } //分别启动多个线程来下载网络资源 if (i == DOWN_THREAD_NUM - 1 ) { //最后一个线程下载指定numPerThred+left个字节 new DownThread(i * numPerThred , (i + 1) * numPerThred + left , isArr[i] , outArr[i]).start(); } else { //每个线程负责下载一定的numPerThred个字节 new DownThread(i * numPerThred , (i + 1) * numPerThred , isArr[i] , outArr[i]).start(); } } } catch (Exception ex) { ex.printStackTrace(); } } //定义获取指定网络资源的长度的方法 public static long getFileLength(URL url) throws Exception { long length = 0; //打开该URL对应的URLConnection。 URLConnection con = url.openConnection(); //获取连接URL资源的长度 long size = con.getContentLength(); length = size; return length; } }
关于Get和Post请求
只是发送Get方式请求,使用connect方法建立和远程资源之间的实际连接就OK了。
如果需要发送POST方式的请求,需要获取URLConnection实例对应的输出流来发送请求参数。
import java.io.*; import java.net.*; import java.util.*; public class TestGetPost{ /** * 向指定URL发送GET方法的请求 * @param url 发送请求的URL * @param param 请求参数,请求参数应该是name1=value1&name2=value2的形式。 * @return URL所代表远程资源的响应 */ public static String sendGet(String url , String param) { String result = ""; BufferedReader in = null; try { String urlName = url + "?" + param; URL realUrl = new URL(urlName); //打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); //设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); //建立实际的连接 conn.connect(); //获取所有响应头字段 Map<String,List<String>> map = conn.getHeaderFields(); //遍历所有的响应头字段 for (String key : map.keySet()){ System.out.println(key + "--->" + map.get(key)); } //定义BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine())!= null){ result += "\n" + line; } } catch(Exception e){ System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } finally{//使用finally块来关闭输入流 try{ if (in != null){ in.close(); } } catch (IOException ex){ ex.printStackTrace(); } } return result; } /** * 向指定URL发送POST方法的请求 * @param url 发送请求的URL * @param param 请求参数,请求参数应该是name1=value1&name2=value2的形式。 * @return URL所代表远程资源的响应 */ public static String sendPost(String url,String param){ PrintWriter out = null; BufferedReader in = null; String result = ""; try{ URL realUrl = new URL(url); //打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); //设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); //发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); //获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); //发送请求参数 out.print(param); //flush输出流的缓冲 out.flush(); //定义BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine())!= null){ result += "\n" + line; } } catch(Exception e){ System.out.println("发送POST请求出现异常!" + e); e.printStackTrace(); } //使用finally块来关闭输出流、输入流 finally{ try{ if (out != null){ out.close(); } if (in != null){ in.close(); } } catch (IOException ex){ ex.printStackTrace(); } } return result; } //提供主方法,测试发送GET请求和POST请求 public static void main(String args[]){ //发送GET请求 String s = TestGetPost.sendGet("http://localhost:8888/abc/login.jsp",null); System.out.println(s); //发送POST请求 String s1 = TestGetPost.sendPost("http://localhost:8888/abc/a.jsp","user=李刚&pass=abc"); System.out.println(s1); } }
TCP协议网络编程
(ServerSocket,Socket),要建立连接.
在Java语言中,对于TCP方式的网络编程提供了良好的支持,在实际实现时,以java.net.Socket类代表客户端连接,以java.net.ServerSocket类代表服务器端连接。
java主要关注的是在传输层 的应用,而对于底层的传输,可以不必关心它。而在传输层,TCP,UDP是两种传输数据流的方式。
由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些
例子程序(实现类似QQ的通讯程序,包括群聊和私聊)
服务端源码:
public interface YeekuProtocol{ //定义协议字符串的长度 int PROTOCOL_LEN = 2; //下面是一些协议字符串,服务器和客户端交换的信息 //都应该在前、后添加这种特殊字符串。 String MSG_ROUND = "§γ"; String USER_ROUND = "∏∑"; String LOGIN_SUCCESS = "1"; String NAME_REP = "-1"; String PRIVATE_ROUND = "★【"; String SPLIT_SIGN = "※"; } //扩展HashMap类,MyMap类要求value也不可重复 public class YeekuMap<K,V> extends HashMap<K,V>{ //根据value来删除指定项 public void removeByValue(Object value) { for (Object key : keySet()){ if (get(key) == value){ remove(key); break; } } } //获取所有value组成的Set集合 public Set<V> valueSet() { Set<V> result = new HashSet<V>(); //遍历所有key组成的集合 for (K key : keySet()){ //将每个key对应的value添加到result集合中 result.add(get(key)); } return result; } //根据value查找key。 public K getKeyByValue(V val) { //遍历所有key组成的集合 for (K key : keySet()){ //如果指定key对应的value与被搜索的value相同 //则返回对应的key if (get(key).equals(val) && get(key) == val){ return key; } } return null; } //重写HashMap的put方法,该方法不允许value重复 public V put(K key,V value){ //遍历所有value组成的集合 for (V val : valueSet() ){ //如果指定value与试图放入集合的value相同 //则抛出一个RuntimeException异常 if (val.equals(value) && val.hashCode() == value.hashCode()){ throw new RuntimeException ("MyMap实例中不允许有重复value!"); } } return super.put(key , value); } } public class Server { private static final int SERVER_PORT = 30000; //使用MyMap对象来保存每个客户名字和对应输出流之间的对应关系。 public static YeekuMap<String , PrintStream> clients =new YeekuMap<String , PrintStream>(); public void init(){ ServerSocket ss = null; try{ //建立监听的ServerSocket ss = new ServerSocket(SERVER_PORT); //采用死循环来不断接受来自客户端的请求 while(true){ Socket socket = ss.accept();//阻塞式 new ServerThread(socket).start(); } } catch (IOException ex){ System.out.println("服务器启动失败,是否端口" + SERVER_PORT + "已被占用?"); } finally{ try{ if (ss != null){ ss.close(); } } catch (IOException ex){ ex.printStackTrace(); } System.exit(1); } } public static void main(String[] args){ Server server = new Server(); server.init(); } } public class ServerThread extends Thread{ private Socket socket; BufferedReader br = null; PrintStream ps = null; //定义一个构造器,用于接收一个Socket来创建ServerThread线程 public ServerThread(Socket socket){ this.socket = socket; } public void run(){ try{ //获取该Socket对应的输入流 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //获取该Socket对应的输出流 ps = new PrintStream(socket.getOutputStream()); String line = null; while((line = br.readLine())!= null){ //如果读到的行以MyProtocol.USER_ROUND开始,并以其结束, //可以确定读到的是用户登陆的用户名 if (line.startsWith(YeekuProtocol.USER_ROUND) && line.endsWith(YeekuProtocol.USER_ROUND)){ //得到真实消息 String userName = getRealMsg(line); //如果用户名重复 if (Server.clients.containsKey(userName)){ System.out.println("重复"); ps.println(YeekuProtocol.NAME_REP); } else{ System.out.println("成功"); ps.println(YeekuProtocol.LOGIN_SUCCESS); Server.clients.put(userName , ps);//登录成功,记录scoket对应的输出流 } } //如果读到的行以YeekuProtocol.PRIVATE_ROUND开始,并以其结束, //可以确定是私聊信息,私聊信息只向特定的输出流发送 else if (line.startsWith(YeekuProtocol.PRIVATE_ROUND) && line.endsWith(YeekuProtocol.PRIVATE_ROUND)){ //得到真实消息 String userAndMsg = getRealMsg(line); //以SPLIT_SIGN来分割字符串,前面部分是私聊用户,后面部分是聊天信息 String user = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[0]; String msg = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[1]; //获取私聊用户对应的输出流,并发送私聊信息 Server.clients.get(user).println(Server.clients.getKeyByValue(ps) + "悄悄地对你说:" + msg); } //公聊要向每个Socket发送 else{ //得到真实消息 String msg = getRealMsg(line); //遍历clients中的每个输出流 for (PrintStream clientPs : Server.clients.valueSet()){ clientPs.println(Server.clients.getKeyByValue(ps)+ "说:" + msg); } } } } //捕捉到异常后,表明该Socket对应的客户端已经出现了问题 //所以程序将其对应的输出流从Map中删除 catch (IOException e){ Server.clients.removeByValue(ps); System.out.println(Server.clients.size()); //关闭网络、IO资源 try{ if (br != null){ br.close(); } if (ps != null){ ps.close(); } if (socket != null){ socket.close(); } } catch (IOException ex) { ex.printStackTrace(); } } } //将读到的内容去掉前后的协议字符,恢复成真实数据 public String getRealMsg(String line){ return line.substring(YeekuProtocol.PROTOCOL_LEN, line.length() - YeekuProtocol.PROTOCOL_LEN); } }
客户端源码:
public class Client{ private static final int SERVER_PORT = 30000; private Socket socket; private PrintStream ps; private BufferedReader brServer; private BufferedReader keyIn; public void init(){ try{ //初始化代表键盘的输入流 keyIn = new BufferedReader(new InputStreamReader(System.in)); //连接到服务器 socket = new Socket("127.0.0.1", SERVER_PORT); //获取该Socket对应的输入流和输出流 ps = new PrintStream(socket.getOutputStream()); brServer = new BufferedReader(new InputStreamReader(socket.getInputStream())); String tip = ""; //采用循环不断地弹出对话框要求输入用户名 while(true){ String userName = JOptionPane.showInputDialog(tip + "输入用户名"); //将用户输入的用户名的前后增加协议字符串后发送 ps.println(YeekuProtocol.USER_ROUND + userName+ YeekuProtocol.USER_ROUND); //读取服务器的响应 String result = brServer.readLine(); //如果用户重复,开始下次循环 if (result.equals(YeekuProtocol.NAME_REP)){ tip = "用户名重复!请重新"; continue; } //如果服务器返回登陆成功,结束循环 if (result.equals(YeekuProtocol.LOGIN_SUCCESS)){ break; } } } catch (UnknownHostException ex){ System.out.println("找不到远程服务器,请确定服务器已经启动!"); closeRs(); System.exit(1); } catch (IOException ex){ System.out.println("网络异常!请重新登陆!"); closeRs(); System.exit(1); } //以该Socket对应的输入流启动ClientThread线程 new ClientThread(brServer).start(); } //定义一个读取键盘输出,并向网络发送的方法 private void readAndSend() { try{ //不断读取键盘输入 String line = null; while((line = keyIn.readLine()) != null){ //如果发送的信息中有冒号,且以//开头,则认为想发送私聊信息 if (line.indexOf(":") > 0 && line.startsWith("//")){ line = line.substring(2); //冒号之前的是私聊用户,冒号之后的是聊天信息 ps.println(YeekuProtocol.PRIVATE_ROUND + line.split(":")[0] + YeekuProtocol.SPLIT_SIGN + line.split(":")[1] + YeekuProtocol.PRIVATE_ROUND); } else{ ps.println(YeekuProtocol.MSG_ROUND + line+ YeekuProtocol.MSG_ROUND); } } } catch (IOException ex){ System.out.println("网络通信异常!请重新登陆!"); closeRs(); System.exit(1); } } //关闭Socket、输入流、输出流的方法 private void closeRs(){ try{ if (keyIn != null){ ps.close(); } if (brServer != null){ ps.close(); } if (ps != null){ ps.close(); } if (socket != null){ keyIn.close(); } } catch (IOException ex){ ex.printStackTrace(); } } public static void main(String[] args){ Client client = new Client(); client.init(); client.readAndSend(); } } public class ClientThread extends Thread{ //该客户端线程负责处理的输入流 BufferedReader br = null; //使用一个网络输入流来创建客户端线程 public ClientThread(BufferedReader br){ this.br = br; } public void run(){ try{ String line = null; //不断从输入流中读取数据,并将这些数据打印输出 while((line = br.readLine())!= null){ System.out.println(line); /* 本例仅打印了从服务器端读到的内容。实际上,此处的情况可以更复杂: 如果我们希望客户端能看到聊天室的用户列表,则可以让服务器在 每次有用户登陆、用户退出时,将所有用户列表信息都向客户端发送一遍。 为了区分服务器发送的是聊天信息,还是用户列表,服务器也应该 在要发送的信息前、后都添加一定的协议字符串,客户端此处则根据协议 字符串的不同而进行不同的处理! 更复杂的情况: 如果两端进行游戏,则还有可能发送游戏信息,例如两端进行五子棋游戏, 则还需要发送下棋坐标信息等,服务器同样在这些下棋坐标信息前、后 添加协议字符串后再发送,客户端就可以根据该信息知道对手的下棋坐标。 */ } } catch (IOException ex){ ex.printStackTrace(); } //使用finally块来关闭该线程对应的输入流 finally{ try{ if (br != null){ br.close(); } } catch (IOException ex){ ex.printStackTrace(); } } } }
UDP协议网络编程
DatapramPacket,DatapgramSocket(单播),MulticastSocket(实现多点广播,组播)
通信两端各建立一个socket,但2个socket之间没有虚拟链路,这2个socket只是发送,接收数据报的对象。
DatagramSocket对象基于UDP协议的Socket,使用DatapgramSocket代表DatagramSocket发送,接收的数据报。
使用该种方式无需建立专用的虚拟连接,由于无需建立专用的连接,所以对于服务器的压力要比TCP小很多,所以也是一种常见的网络编程方式。
DatapramPacket的3个重要接口,获取发送者的IP和端口:
InetAddress sendIP = getPacket.getAddress();
int sendPort = getPacket.getPort();
SocketAddress sendAddress = getPacket.getSocketAddress();
// 通过数据报得到发送方的套接字地址(SocketAddress封装了InetAddress和代表端口的整数,也就是说SocketAddress可以同时代表IP地址和端口)
UDP测试例子:
服务端代码:
public class UdpServer{ public static final int PORT = 30000; //定义每个数据报的最大大小为4K private static final int DATA_LEN = 4096; //定义该服务器使用的DatagramSocket private DatagramSocket socket = null; //定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; //以指定字节数组创建准备接受数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff , inBuff.length); //定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket; //定义一个字符串数组,服务器发送该数组的的元素 String[] books = new String[]{ "轻量级J2EE企业应用实战", "基于J2EE的Ajax宝典", "Struts2权威指南", "ROR敏捷开发最佳实践" }; public void init()throws IOException{ try{ //创建DatagramSocket对象 socket = new DatagramSocket(PORT); //采用循环接受数据 for (int i = 0; i < 1000 ; i++ ){ //读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。 socket.receive(inPacket); //判断inPacket.getData()和inBuff是否是同一个数组 System.out.println(inBuff == inPacket.getData()); //将接收到的内容转成字符串后输出 System.out.println(new String(inBuff ,0 , inPacket.getLength())); //从字符串数组中取出一个元素作为发送的数据 byte[] sendData = books[i % 4].getBytes(); //以指定字节数组作为发送数据、以刚接受到的DatagramPacket的 //源SocketAddress作为目标SocketAddress创建DatagramPacket。 outPacket = new DatagramPacket(sendData ,sendData.length , inPacket.getSocketAddress()); //发送数据 socket.send(outPacket); } } finally{ if (socket != null){ socket.close(); } } } public static void main(String[] args) throws IOException{ new UdpServer().init(); } }
客户端代码:
public class UdpClient{ //定义发送数据报的目的地 public static final int DEST_PORT = 30000; public static final String DEST_IP = "127.0.0.1"; //定义每个数据报的最大大小为4K private static final int DATA_LEN = 4096; //定义该客户端使用的DatagramSocket private DatagramSocket socket = null; //定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; //以指定字节数组创建准备接受数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff , inBuff.length); //定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket = null; public void init()throws IOException{ try{ //创建一个客户端DatagramSocket,使用随机端口 socket = new DatagramSocket(); //初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0] , 0 ,InetAddress.getByName(DEST_IP) , DEST_PORT); //创建键盘输入流 Scanner scan = new Scanner(System.in); //不断读取键盘输入 while(scan.hasNextLine()){ //将键盘输入的一行字符串转换字节数组 byte[] buff = scan.nextLine().getBytes(); //设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); //发送数据报 socket.send(outPacket); //读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。 socket.receive(inPacket); System.out.println(new String(inBuff , 0 , inPacket.getLength())); } } finally{ if (socket != null){ socket.close(); } } } public static void main(String[] args) throws IOException{ new UdpClient().init(); } }
MulticastSocket实现多点广播:
DatagramSocket只允许数据报发送给指定的目标地址,而MulticastSocket可以将数据报以广播方式发送到数量不等的多个客户端。
若要使用多点广播时,则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有主机都能收到该数据报。
IP多点广播(或多点发送)实现了将单一信息发送到多个接收者的广播,其思想是设置一组特殊网络地址作为多点广播地址,每一个多点广播地址都被看做一个组,当客户端需要发送、接收广播信息时,加入到该组即可。
广播是网络通信中常用的一种方式,将数据包一次发送给多台机器。广播本身也是UDP通信,只是发送时地址不是具体某一台机器的IP,而是标识一组计算机D类IP地址,凡是加入这个组的机器都可以接收到数据。IP协议为多点广播提供了这批特殊的IP地址,这些IP地址的范围是224.0.0.0至239.255.255.255,(D类IP地址)
joinGroup(InetAddress multicastAddr):将该MulticastSocket加入指定的多点广播地址。
leaveGroup(InetAddress multicastAddr):让该MulticastSocket离开指定的多点广播地址。
这2个方法对于MulticastSocket实现多点广播很重要,比如本地IP地址是10.10.121.73,那么我们必须要先创建一个MulticastSocket对象,然后将这个对象加入指定的多点广播地址,比如230.0.0.0,这样当我们接收到外部发送给230.0.0.0这个广播地址数据时候,因为我们的MulticastSocket对象加入了该广播地址这个组,所以10.10.121.73(就是我们创建MulticastSocket对象默认的本机地址)也可以接收到广播下来的数据了。
在某些系统中,可能有多个网络接口。这可能会对多点广播带来问题,这时候程序需要在一个指定的网络接口上监听,通过调用setInterface可选择MulticastSocket所使用的网络接口;也可以使用getInterface方法查询MulticastSocket监听的网络接口。
如果创建仅用于发送数据报的MulticastSocket对象,则使用默认地址、随机端口即可。
但如果创建接收用的MulticastSocket对象,则该MulticastSocket对象必须具有指定端口,否则发送方无法确定发送数据报的目标端口。
DatagramSocket多一个setTimeToLive(int ttl)方法,该ttl参数设置数据报最多可以跨过多少个网络,
当ttl为0时,指定数据报应停留在本地主机;当ttl的值为1时,指定数据报发送到本地局域网;当ttl的值为32时,意味着只能发送到本站点的网络上;当ttl为64时,意味着数据报应保留在本地区;当ttl的值为128时,意味着数据报应保留在本大洲;当ttl为255时,意味着数据报可发送到所有地方;默认情况下,该ttl的值为1。
//让该类实现Runnable接口,该类的实例可作为线程的target public class MulticastSocketTest implements Runnable{ //使用常量作为本程序的多点广播IP地址 private static final String BROADCAST_IP = "230.0.0.1"; //使用常量作为本程序的多点广播目的的端口 public static final int BROADCAST_PORT = 30000; //定义每个数据报的最大大小为4K private static final int DATA_LEN = 4096; //定义本程序的MulticastSocket实例 private MulticastSocket socket = null; private InetAddress broadcastAddress = null; private Scanner scan = null; //定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; //以指定字节数组创建准备接受数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff , inBuff.length); //定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket = null; public void init()throws IOException{ try{ //创建用于发送、接收数据的MulticastSocket对象 //因为该MulticastSocket对象需要接收,所以有指定端口 socket = new MulticastSocket(BROADCAST_PORT);//同时,使用本机默认的IP地址创建了MulticastSocket对象,比如192.168.1.1 //将该socket加入指定的多点广播地址 broadcastAddress = InetAddress.getByName(BROADCAST_IP); socket.joinGroup(broadcastAddress);//这样本机IP地址192.168.1.1创建的MulticastSocket对象就加入了广播地址230.0.0.1 //设置本MulticastSocket发送的数据报被回送到自身 socket.setLoopbackMode(false); //初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0] , 0 ,broadcastAddress , BROADCAST_PORT); //启动以本实例的run()方法作为线程体的线程 new Thread(this).start(); //创建键盘输入流 scan = new Scanner(System.in); //不断读取键盘输入 while(scan.hasNextLine()){ //将键盘输入的一行字符串转换字节数组 byte[] buff = scan.nextLine().getBytes(); //设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); //发送数据报 socket.send(outPacket); } } finally{ socket.close(); } } public void run(){ try{ while(true){ //读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。 socket.receive(inPacket); //打印输出从socket中读取的内容 System.out.println("聊天信息:" + new String(inBuff , 0 , inPacket.getLength())); } } catch (IOException ex){ ex.printStackTrace(); try{ if (socket != null){ //让该Socket离开该多点IP广播地址 socket.leaveGroup(broadcastAddress); //关闭该Socket对象 socket.close(); } System.exit(1); } catch (IOException e){ e.printStackTrace(); } } } public static void main(String[] args) throws IOException{ new MulticastSocketTest().init(); } }
例子2:
//写了个MulticastSocket的程序,以作备忘:
import java.io.*; import java.net.*; import java.util.*; public class MulticastClient{ public static void main(String[] args) throws IOException{ //创建MulticastSocket,默认本机Ip,端口是4446,需要接收,所以有指定端口 MulticastSocket socket = new MulticastSocket(4446); //将该socket加入指定的多点广播地址230.0.0.1 InetAddress address = InetAddress.getByName("230.0.0.1"); socket.joinGroup(address); DatagramPacket packet; //发送数据包 byte[] buf = "Hello,This is a member of multicast!".getBytes(); packet = new DatagramPacket(buf, buf.length,address,4445);//组播发送数据,发送到指定端口4445 socket.send(packet); //接收数据包并打印 byte[] rev = new byte[512]; packet = new DatagramPacket(rev, rev.length); socket.receive(packet); String received = new String(packet.getData()).trim(); System.out.println("received: " + received); //退出组播组,关闭socket socket.leaveGroup(address); socket.close(); } } --------------------------------------- import java.io.*; import java.net.*; import java.util.*; public class AMulticastClient { public static void main(String[] args) throws IOException{ MulticastSocket socket = new MulticastSocket(4445); //将该socket加入指定的多点广播地址230.0.0.1(和上面的MulticastSocket加入同一个广播地址,属于同一组) InetAddress address = InetAddress.getByName("230.0.0.1"); socket.joinGroup(address); DatagramPacket packet; //接收数据包 byte[] buf = new byte[512]; packet = new DatagramPacket(buf, buf.length); socket.receive(packet); //打印数据包 String received = new String(packet.getData()).trim(); System.out.println("received: " + received); //发送数据包 byte[] sen=received.getBytes(); packet=new DatagramPacket(sen,sen.length,address,4446); socket.send(packet); //退出组播组,关闭socket socket.leaveGroup(address); socket.close(); } }
例子3:
/**
* Description:该类用于网络通信,它包含了MulticastSocket实例和
* DatagramSocket实例,分别实现广播和私聊功能
*/
//聊天交换信息的工具类
public class ComUtil { //使用常量作为本程序的多点广播IP地址 private static final String BROADCAST_IP= "230.0.0.1"; //使用常量作为本程序的多点广播目的的端口 //DatagramSocket所用的的端口为该端口-1。 public static final int BROADCAST_PORT = 30000; //定义每个数据报的最大大小为4K private static final int DATA_LEN = 4096; //定义本程序的MulticastSocket实例 private MulticastSocket socket = null; //定义本程序私聊的Socket实例 private DatagramSocket singleSocket = null; //定义广播的IP地址 private InetAddress broadcastAddress = null; //定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; //以指定字节数组创建准备接受数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff , inBuff.length); //定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket = null; //聊天的主界面 private LanChat lanTalk; //构造器,初始化资源 public ComUtil(LanChat lanTalk)throws IOException , InterruptedException{ this.lanTalk = lanTalk; //创建用于发送、接收数据的MulticastSocket对象 //因为该MulticastSocket对象需要接收,所以有指定端口 socket = new MulticastSocket(BROADCAST_PORT); //创建私聊用的DatagramSocket对象 singleSocket = new DatagramSocket(BROADCAST_PORT + 1); broadcastAddress = InetAddress.getByName(BROADCAST_IP); //将该socket加入指定的多点广播地址 socket.joinGroup(broadcastAddress); //设置本MulticastSocket发送的数据报被回送到自身 socket.setLoopbackMode(false); //初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0] , 0 ,broadcastAddress , BROADCAST_PORT); //启动两个读取网络数据的线程 new ReadBroad().start(); Thread.sleep(1); new ReadSingle().start(); } //广播消息的工具方法 public void broadCast(String msg){ try{ //将msg字符串转换字节数组 byte[] buff = msg.getBytes(); //设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); //发送数据报 socket.send(outPacket); } catch (IOException ex){ ex.printStackTrace(); if (socket != null){ //关闭该Socket对象 socket.close(); } JOptionPane.showMessageDialog(null, "发送信息异常,请确认30000端口空闲,且网络连接正常!" , "网络异常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } //定义向单独用户发送消息的方法 public void sendSingle(String msg , SocketAddress dest){ try{ //将msg字符串转换字节数组 byte[] buff = msg.getBytes();buff , buff.length , dest); singleSocket.send(packet); } catch (IOException ex){ ex.printStackTrace(); if (singleSocket != null){ //关闭该Socket对象 singleSocket.close(); } JOptionPane.showMessageDialog(null, "发送信息异常,请确认30001端口空闲,且网络连接正常!" , "网络异常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } //不断从DatagramSocket中读取数据的线程 class ReadSingle extends Thread{ //定义接收网络数据的字节数组 byte[] singleBuff = new byte[DATA_LEN]; private DatagramPacket singlePacket = new DatagramPacket(singleBuff , singleBuff.length); public void run(){ while (true){ try{ //读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。 singleSocket.receive(singlePacket); //处理读到的信息 lanTalk.processMsg(singlePacket , true); } catch (IOException ex){ ex.printStackTrace(); if (singleSocket != null){ singleSocket.close(); } JOptionPane.showMessageDialog(null, "接收信息异常,请确认30001端口空闲,且网络连接正常!" , "网络异常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } } } //持续读取MulticastSocket的线程 class ReadBroad extends Thread{ public void run(){ while (true){ try{ //读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。 socket.receive(inPacket); //打印输出从socket中读取的内容 String msg = new String(inBuff , 0 , inPacket.getLength()); //读到的内容是在线信息 if (msg.startsWith(YeekuProtocol.PRESENCE) && msg.endsWith(YeekuProtocol.PRESENCE)){ String userMsg = msg.substring(2 , msg.length() - 2); String[] userInfo = userMsg.split(YeekuProtocol.SPLITTER); UserInfo user = new UserInfo(userInfo[1] , userInfo[0] , inPacket.getSocketAddress(), 0); //控制是否需要添加该用户的旗标 boolean addFlag = true; ArrayList<Integer> delList = new ArrayList<Integer>(); //遍历系统中已有的所有用户,该循环必须循环完成 for (int i = 1 ; i < lanTalk.getUserNum() ; i++ ){ UserInfo current = lanTalk.getUser(i); //将所有用户失去联系的次数加1 current.setLost(current.getLost() + 1); //如果该信息由指定用户发送过来 if (current.equals(user)){ current.setLost(0); //设置该用户无需添加 addFlag = false; } if (current.getLost() > 2){ delList.add(i); } } //删除delList中的所有索引对应的用户 for (int i = 0; i < delList.size() ; i++){ lanTalk.removeUser(delList.get(i)); } if (addFlag){ //添加新用户 lanTalk.addUser(user); } } //读到的内容是公聊信息 else{ //处理读到的信息 lanTalk.processMsg(inPacket , false); } } catch (IOException ex){ ex.printStackTrace(); if (socket != null){ //关闭该Socket对象 socket.close(); } JOptionPane.showMessageDialog(null, "接收信息异常,请确认30000端口空闲,且网络连接正常!" , "网络异常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } } } }
关于UrlConnection连接和Socket连接的区别:
只知道其中的原理如下:
抽象一点的说,Socket只是一个供上层调用的抽象接口,隐藏了传输层协议的细节。
urlconnection 基于Http协议,Http协议是应用层协议,对传输层Tcp协议进行了封装,是无状态协议,不需要你去考虑线程、同步、状态管理等,
内部是通过socket进行连接和收发数据的,不过一般在数据传输完成之后需要关闭socket连接。
使用UrlConnection比直接使用Socket要简单的多,不用关心状态和线程管理。
直接使用Socket进行网络通信得考虑线程管理、客户状态监控等,但是不用发送头信息等,更省流量。
分析源码如下 :
url.openConnection()调用的是strmHandler.openConnection(this);
而strmHandler是URLStreamHandler接口的子类的实例。
抽象类 URLStreamHandler 是所有流协议处理程序的通用超类,可以通过不同 protocol 的 URL 实例,产生 java.net.URLConnection 对象。
由于context和handler是null,所以最终根据具体的协议调用URL类中的setupStreamHandler()方法对strmHandler进行初始化。
将调用下面的Handler类的实例的openConnection(URL u)方法。
protected URLConnection openConnection(URL u) throws IOException { return new HttpURLConnectionImpl(u, getDefaultPort()); } protected URLConnection openConnection(URL u, Proxy proxy) throws IOException { if (null == u || null == proxy) { throw new IllegalArgumentException(Messages.getString("luni.1B")); } return new HttpURLConnectionImpl(u, getDefaultPort(), proxy); }
HttpConnection 部分源码如下:
private SSLSocket sslSocket; private InputStream inputStream; private OutputStream outputStream; private InputStream sslInputStream; private OutputStream sslOutputStream; private HttpConfiguration config; public HttpConnection(HttpConfiguration config, int connectTimeout) throws IOException { this.config = config; String hostName = config.getHostName(); int hostPort = config.getHostPort(); Proxy proxy = config.getProxy(); if(proxy == null || proxy.type() == Proxy.Type.HTTP) { socket = new Socket(); } else { socket = new Socket(proxy); } socket.connect(new InetSocketAddress(hostName, hostPort), connectTimeout); }
现在UrlConnection连接和Socket连接的区别应该十分清楚了吧。
参考UrlConnection连接和Socket连接的区别
http://zhoujianghai.iteye.com/blog/1195988
参考:
java socket 属性设置
http://blog.csdn.net/fastthinking/article/details/10930193
Java Socket 几个重要的TCP/IP选项解析(一)
http://elf8848.iteye.com/blog/1739598
http://elf8848.iteye.com/blog/1739604
JAVA Socket超时浅析
http://blog.csdn.net/sureyonder/article/details/5633647
Java网络编程之传输控制协议
http://blog.csdn.net/xiaojianpitt/article/details/2767213
Socket 关于设置Socket连接超时时间
http://cuisuqiang.iteye.com/blog/1725348
SO_KEEPALIVE选项 (LINUX)
http://blog.chinaunix.net/uid-26575352-id-3483808.html
LINUX C网络编程中的心跳机制
http://blog.chinaunix.net/uid-9688646-id-4054244.html
posted on 2013-03-27 15:56 Cynthia&Sky 阅读(743) 评论(0) 编辑 收藏 举报