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 VectorallOut = 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这里有一篇文章讲的比较细,把地址记录下来,供查询,感谢作者。