Java网络编程html
一、Socket编程java
Socket(套接字):封装着如端口号,ip地址,计算机名等信息的类。经过Socket咱们能够和远程计算机通讯。spring
网络通讯模型编程
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(); } }
二、多线程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) { } } }
三、线程池
上面这种频繁的建立线程和销毁线程是很是消耗资源和性能的。
能够建立一些空的线程,将它们保存起来,当有任务须要并发执行时,咱们取出一个空的线程来运行这个任务,当任务运行完毕后,在将线程收回,等待下次分配任务。
这样自始自终咱们都只使用了开始建立的那些线程,并能够重复利用来节省性能开销。
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) { }
四、双端队列
内部由两个队列实现,他们交替进行存取工做。这样至少能够保证2个线程同时进行存取操做。可是对于两个线程同时进行存或者取,仍是要求同步的。
可是比单队列实现线程安全仍是要快的。
BlockingDeque双端队列
实现:
1)ArrayBlockingDeque该类实现的构造方法要求咱们传入一个整数,表明当前队列的长度。因此这个是一个固定大小的双端队列,存取原则为FIFO先入先出的原则。
当元素调用offer存入了队列时,若队列已满,那么能够设置延时等待,当超过了延时等待会返回存入失败。
2)LinkedBlockingDeque变长双端队列。长度不定,随着元素数量而增长,最大能够达到Integer.MAX_VALUE,重载构造方法能够传入一个整数,使之变为一个定长的队列。
3)PriorityBlockingDeque 这个和LinkedBlockingDeque类似,只不过是进去了天然排序后获取。
4)SynchronousQueue特殊的双端队列 存取步骤有要求,必须存一次取一次,交替进行。
例:
服务端
/** * 建立一个静态集合保护全部客户端的数输入流 * 注意:由于这个集合被多个线程使用,所哟一集合要是安全的。 */ static Vector<PrintWriter> allOut = new Vector<PrintWriter>(); /** 消息队列 */ static BlockingDeque<String> msgQueue = new LinkedBlockingDeque<String>(); 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这里有一篇文章讲的比较细,把地址记录下来,供查询,感谢做者。