Java网络编程

1、Socket编程

Socket(套接字):封装着如端口号,ip地址,计算机名等信息的类。通过Socket我们可以和远程计算机通信。

网络通信模型

C/S Client/Server

客户端通航运行在用户的计算机上,客户端是一个特有的程序,实现特有的功能,连接服务器进行通信,谁发起连接谁是用户。

服务器端通常是等待客户端连接,提供功能服务并与之通信。

B/S

固定了客户端和通信协议和C/S结构。


通信协议:计算机通信的实质就是相互收发字节。那么按照一定的格式收发字节就是通信协议。


/**
* 创建客户端Socket
* Socket客户端类
* 构造的时候就会根据给定的服务端ip地址和服务端的端口号尝试连接
*/
try {
    System.out.println("开始连接");
    Socket socket = new Socket("172.16.3.33", 8088);
    System.out.println("与服务端连接成功!");
    /**
    * 通过socket可以获取一组与服务器通信的输入输出流
    * 我们对其包装就可以方便进行读写信息了。
    * 通过socket拿到的是两个低级流(字节流)
    */
    InputStream in = socket.getInputStream();
    OutputStream out = socket.getOutputStream();
    /**
    * 向服务器发送字符,将字符输出流转换成缓冲字符输出流
    */
    PrintWriter pw = new PrintWriter(out);
    pw.println("你好服务器!");
    pw.flush();
    /**
    * 收取服务器发送的字符串,包装为缓冲字符输入流
    */
    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    //读取服务器发回的信息
                                                                                                                                                                                                                                                                                                       
    String info = reader.readLine();
    System.out.println("服务端:"+info);
} catch (IOException e) {
    //e.printStackTrace();
}
/**
* 服务端
* 服务器端打开socket等待客户端的连接
* 服务器端的Socket名称是ServerSocket
* 创建ServerSocket需要指定服务端口号
*/
public static void main(String[] args) {
    try {
        System.out.println("dddd");
        ServerSocket server = new ServerSocket(8050);
        /**
        * 监听端口
        * 等待客户端连接
        * 等待客户端连接的方法accept()
        * 该方法是一个阻塞方法,知道有客户端连接上该方法才会返回,返回的就是当前客户端的套接字。
        * 从中我们可以知道客户端的ip等信息。
        *
        * accept()方法可以重复调用
        */
        System.out.println("启动完毕,等待连接");
        Socket client = server.accept();
        System.out.println("有一个客户端和我连接了");
        /**
        * 通过socket获取输入流读取客户端的信息
        */
        InputStream in = client.getInputStream();
        OutputStream out = client.getOutputStream();
                                                                                                                                                                                                                                                                                                           
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        System.out.println("客户端:"+reader.readLine());
                                                                                                                                                                                                                                                                                                           
        //向客户端发信息
        PrintWriter pw = new PrintWriter(out);
        pw.println("你好客户端");
        pw.flush();   
    } catch (IOException e) {
        //e.printStackTrace();
    }
}


2、多线程Socket


Server端多线程:

服务器端无限循环接受客户端的访问,每连接都能茶圣一对新的Socket的实例。

为每个客户端连接创建一个独立线程,处理客户请求。


public static void main(String[] args) {
    try {
        ServerSocket server = new ServerSocket(8088);
        System.out.println("启动完毕,等待连接");
                                                                                                                                                                                                                                                                                                           
        while (true) {
        Socket client = server.accept();
        if(client==null)continue;
        System.out.println("与客户端连接成功"+client.getInetAddress().getHostAddress());
        Handler handler = new Handler(client);//交给线程去处理
        Thread t = new Thread(handler);
        t.start();
        }   
    } catch (IOException e) {
    }
}
/**
* 与客户端通信线程
* 负责与某个特定的socket的客户端进行通信
* 每个线程实例负责一个客户端的通信
*/
private static class Handler implements Runnable {
    /**
    * 当前线程要通信的客户端socket
    */
    private Socket client;
    public Handler(Socket client) {
        this.client = client;
    }
    public void run(){
        try {
            /** 通过socket获取客户端信息 */
            InputStream in = this.client.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            /** 死循环读取客户端信息 */
                                                                                                                                                                                                                                                                                                               
            while (true) {
                if (reader.readLine() == null)
                return;
                System.out.println("客户端"+ this.client.getInetAddress().getHostAddress() + ":" + reader.readLine());
            }
        } catch (Exception e) {
        }
    }
}


3、线程池

上面这种频繁的创建线程和销毁线程是非常消耗资源和性能的。

可以创建一些空的线程,将它们保存起来,当有任务需要并发执行时,我们取出一个空的线程来运行这个任务,当任务运行完毕后,在将线程收回,等待下次分配任务。

这样自始自终我们都只使用了开始创建的那些线程,并可以重复利用来节省性能开销。

JDK提供了线程池的管理器ExecutorService

通过Executors类的静态方法创建几个不同实现的线程池。


Executors.newCachedThreadPool();创建一个缓存线程池,当有任务的时候会检查线程池中是否有空线程,若有就使用它,若没有就创建新的线程。若长久没有使用的线程会自动回收。

Executors.newFixedThreadPool(int threads);创建一个可重用的,具有固定线程数的线程池。

Executors.newScheduledExecutor();创建只有一条线程的线程池,它可以在指定延迟后执行线程任务。


Executors.newSingleThreadExecutor();创建一个只有单线程的线程池,相当于Exceutors.newFixedThreadPool方法时传入参数1。里边维护着一个任务队列。


/**
* 创建一个线程池,具有50个
*/
ExecutorService threadPool =Executors.newFixedThreadPool(50);
try {
    ServerSocket server = new ServerSocket(8088);
    System.out.println("启动完毕,等待连接");
    /** 将转发消息线程启动 */
    SendMessageHandler sHandler = new SendMessageHandler();
    Thread sendTh = new Thread(sHandler);
    sendTh.start();
                                                                                                                                                                                                                                                                                                       
    while (true) {
        Socket client = server.accept();
        System.out.println("与客户端连接成功"+client.getInetAddress().getHostAddress());
        Handler handler = new Handler(client);//交给线程去处理
        /**
        * 将需要并发的任务(Runnable)交给线程池
        * 让其分配空线程去运行该任务
        * 若线程都在工作,那么登载,直到有空线程为止。
        */
        threadPool.execute(handler);
        //Thread t = new Thread(handler);
        //t.start();
    }
} catch (IOException e) {
}


4、双端队列

内部由两个队列实现,他们交替进行存取工作。这样至少可以保证2个线程同时进行存取操作。但是对于两个线程同时进行存或者取,还是要求同步的。

但是比单队列实现线程安全还是要快的。


BlockingDeque双端队列

实现:

1)ArrayBlockingDeque该类实现的构造方法要求我们传入一个整数,代表当前队列的长度。所以这个是一个固定大小的双端队列,存取原则为FIFO先入先出的原则。

当元素调用offer存入了队列时,若队列已满,那么可以设置延时等待,当超过了延时等待会返回存入失败。

2)LinkedBlockingDeque变长双端队列。长度不定,随着元素数量而增加,最大可以达到Integer.MAX_VALUE,重载构造方法可以传入一个整数,使之变为一个定长的队列。

3)PriorityBlockingDeque 这个和LinkedBlockingDeque相似,只不过是进去了自然排序后获取。

4)SynchronousQueue特殊的双端队列 存取步骤有要求,必须存一次取一次,交替进行。

例:

服务端

/**
* 创建一个静态集合保护所有客户端的数输入流
* 注意:因为这个集合被多个线程使用,所哟一集合要是安全的。
*/
static Vector allOut = new Vector();
/** 消息队列 */
static BlockingDeque msgQueue = new LinkedBlockingDeque();
public static void main(String[] args) {
    /**
    * 创建一个线程池,具有50个
    */
    ExecutorService threadPool =Executors.newFixedThreadPool(50);
    try {
        ServerSocket server = new ServerSocket(8088);
        System.out.println("启动完毕,等待连接");
        /** 将转发消息线程启动 */
        SendMessageHandler sHandler = new SendMessageHandler();
        Thread sendTh = new Thread(sHandler);
        sendTh.start();
                                    
        while (true) {
            Socket client = server.accept();
            System.out.println("与客户端连接成功"+client.getInetAddress().getHostAddress());
            Handler handler = new Handler(client);//交给线程去处理
            /**
            * 将需要并发的任务(Runnable)交给线程池
            * 让其分配空线程去运行该任务
            * 若线程都在工作,那么登载,直到有空线程为止。
            */
            threadPool.execute(handler);
            //Thread t = new Thread(handler);
            //t.start();
        }
    } catch (IOException e) {
    }
}
/**
* 与客户端通信线程
* 负责与某个特定的socket的客户端进行通信
* 每个线程实例负责一个客户端的通信
*/
private static class Handler implements Runnable {
    /**
    * 当前线程要通信的客户端socket
    */
    private Socket client;
                                
    public Handler(Socket client) {
        this.client = client;
    }
    public void run(){
        PrintWriter pw = null;
        try {
            /** 通过socket获取客户端信息 */
            InputStream in = this.client.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            /**
            * 将当前客户端的输出流保存到共享集合
            */
            pw = new PrintWriter(client.getOutputStream());
            allOut.add(pw);
            /** 死循环读取客户端信息 */
            while (true) {
                String str = reader.readLine();
                /**
                * 将信息放入到消息队列
                */
                if("q".equals(str)){
                client.close();
                }
                msgQueue.offer(str);
                /**
                * arg1:存放的元素
                * arg2:延时时间
                * arg3:延时的时间单位
                *
                * 下面方法是向队列中存放元素,设置5秒超时,若超时了还没有放入队列这返回false
                */
                boolean tf = msgQueue.offer(str, 5, TimeUnit.SECONDS);
            }       
        } catch (Exception e) {
            e.printStackTrace();
            /**
            * 抛出异常,我们应该将这个客户端的输出流从共享集合中删除
            * 告诉其他线程不要再发信息了。
            */
            allOut.remove(pw);
        }
   }
}
/**
* 转发消息
* 循环消息队列,将每一个消息通过所有客户端的输入流发送给客户端
* 做到广播的效果。
*/
public static class SendMessageHandler implements Runnable{
    public void run() {
        while (true) {
            /** 循环将消息发送给每一个客户端 每50ms作一次*/
            String str = null;
            while ((str=msgQueue.poll())!=null) {//and msgQueue.poll()!=null
                /** 获取所有客户端的输出流,将字符串输出 */
                for (PrintWriter pw : allOut) {
                    pw.println(str);
                    pw.flush();
                }
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


客户端:

public static void main(String[] args) {
    /**
    * 创建客户端Socket
    * Socket客户端类
    * 构造的时候就会根据给定的服务端ip地址和服务端的端口号尝试连接
    */
    try {
        System.out.println("开始连接");
        Socket socket = new Socket("172.16.3.14", 8088);
        System.out.println("与服务端连接成功!");
              
        //启动消息线程
        Thread readerMsgTh = new Thread(new ReadMessageHandler(socket.getInputStream()));
        readerMsgTh.start();
              
        OutputStream out = socket.getOutputStream();
        PrintWriter pw = new PrintWriter(out);
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            pw.println(reader.readLine());
            pw.flush();
        }
    } catch (IOException e) {
    //e.printStackTrace();
    }
}
    /**
    * 该线程用于从服务器读取信息。
    */
    public static class ReadMessageHandler implements Runnable{
        private InputStream in;//从服务端读取信息
        public ReadMessageHandler(InputStream in) {
            this.in = in;
        }
        public void run() {
            try {
                //将字节输入流转换为缓冲字符输入流
                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                while (true) {
                   System.out.println(reader.readLine());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }



http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html这里有一篇文章讲的比较细,把地址记录下来,供查询,感谢作者。