JAVA网络编程

参考<<疯狂JAVA编程>>中的第17章 网络编程

JAVA网络编程

 

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网络编程)