zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    HTTP协议简介
    Web开发中B/S架构和C/S架构的区别
    软件测试作业三
    Java8 时间处理
    Java EE
    Java 中的 I/O 抽象
    Python 高级 I/O 多路复用
    SQL 与关系代数
    Python 协程与事件循环
    Java SE 5.0
  • 原文地址:https://www.cnblogs.com/lijunamneg/p/2984785.html
Copyright © 2011-2022 走看看