网络编程
一、通信
- 两个节点实现相互间的数据传输。
-
线程间的通信,比较简单,是指同一进程中的不同线程,线程间共享内存空间,且由同一jvm来进行管理。
-
java线程间的通讯有三种方式,其中之一就是通过流来实现通讯。
package com.zhong.test_7; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; public class PipeTest { public static void main(String[] args) { PipedInputStream input = new PipedInputStream(); PipedOutputStream output = new PipedOutputStream(); //流连接操作 try { input.connect(output); } catch (IOException e) { e.printStackTrace(); } //启动线程 new Thread(new Producer(output)).start(); new Thread(new Consumer(input)).start(); } static class Consumer implements Runnable {//消费者 private PipedInputStream input; public Consumer(PipedInputStream input) { this.input = input; } @Override public void run() { while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } try { int count = input.available();//得到输入流中等待读取的数据的数量 if (count > 0) { System.out.println("待取用的数据数量:" + count); System.out.println("读出的数据:" + input.read()); } } catch (IOException e) { e.printStackTrace(); } } } } static class Producer implements Runnable {//生产者 private PipedOutputStream output; public Producer(PipedOutputStream output) { this.output = output; } @Override public void run() { while (true) { for (int i = 0; i < 5; i++) { try { output.write(i); } catch (IOException e) { e.printStackTrace(); } } try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
-
-
进程间的通信,比较复杂,两个进程可以处于不同的主机,操作系统可能不同,它们的内存是独立的。
二、网络编程
- 在不同的进程间实现通信。
- 依据通信的方式把现代的软件分成两大部分:
- C/S 模式,客户/服务器,每个客户都是一个进程,服务器也是一个进程,C表示在客户机上安装的应用程序。(界面可以很复杂,但是客户机需要安装软件),给软件的使用带来了一定的不便。
- B/S 模式,浏览器/服务器模型,客户机都是通过浏览器与服务器实现通讯,客户机不用安装应用程序。(界面不会太复杂,通讯的效率不高,不需要安装软件,使用很方便)
- java 应用程序的通信模型:
- 采用 C/S 的通讯模型。客户的请求与服务器的响应有先后次序,不能颠倒,这样一去一回是一个完整的过程。此过程可以无限的延续下去。
三、通信协议
- 进程之间通信必须基于某种通信协议来完成。
- 通信协议是通信双方都应该遵守的规则,它规定了通信双方在数据的格式、速度、先后顺序等方面的一些要求。
- 通信双方所执行的功能及执行顺序可以用基于OSI标准的七层通讯模型来描述,它概括了几乎所有的通信结构。
- 从七层模型中抽取出四层,称为通信的四层模型,基本上可以概括 java 程序的通讯结构。
-
TCP/IP 协议
- TCP 是传输控制协议,工作在传输层;IP 是网际协议,工作在网络层。
- TCP是互联网通信的基础协议,它出现的原因就是为互联网的通信提供一种标准。
- TCP协议的特点:
- 它是基于连接的,通信节点之间首选先要建立稳固的连接。
- 它是可靠的,可以不中断,完整的,连续的把数据传递给对方。
-
UDP 协议
- 数据报通信协议,也称为直连协议。
- 特点:通信双方不用建立连接,把发送方和接收方的地址都放在数据包中,把数据包向网络上发送,可能会出现丢包。它是不可靠的传输协议。
总结:以上两个协议都属于底层协议,通信中使用最多的是 TCP 协议。
-
通讯端口
- 同一主机中的不同进程和不同主机间的进程都可以实现通讯,一台主机上有多个进程,为了区分不同的进程,需要给通讯的进程分配一个编号,此编号就是端口号。所以,需要进行通讯的进程都必须要有一个唯一的端口号。端口号由两个字节表示,范围是 0-65535,0-1023 这个范围被一些公共应用程序所占据,所以给程序分配的端口号要大于1023。如果发生冲突将不能正常通讯。
-
实现TCP协议通讯的三要素
- 通讯协议,IP地址,端口号。缺一不可。
四、基于 tcp/ip 协议的通信编程
- 需要一个服务器的进程,同时也至少需要一个客户端的进程。
- java 的 net 包中提供了两个重要的 api,服务器端需要使用 ServerSocket(服务端套接字),客户端需要使用 Socket(套接字)。
- 通信的过程就是数据相互传输的过程,所以服务器和客户端都需要通过网络进行数据的读写,因此基本套接字可以获取用来进行读写操作的流对象。
- 如果服务器需要与多个客户端来实现即时通讯,在服务器端需要创建多个线程,每个线程各自负责一个客户端的通信任务。
五、实现单个客户端与服务器的通信
-
public class ServerSocket 类
- 作用:接收客户端的请求,并创建服务器端与客户端的连接,然后返回一个套接字对象(Socket),服务器使用此套接字对象来与客户端进行数据的读写操作。
-
public class Socket 类
- 作用:创建客户端的套接字,客户端需要创建一个 Socket 对象,此对象就是客户端套接字,客户端套接字会与服务器端的套接字形成了通信的两个端点。
-
基本例子:
package com.zhong.test_7; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class ServerSocketDemo { public static void main(String[] args) throws IOException, InterruptedException { //创建服务器套接字 ServerSocket ssk = new ServerSocket(8989); System.out.println("服务器在8989端口进行监听……"); //利用服务器套接字来监听客户端的请求,程序阻塞,等待请求到达 Socket socket = ssk.accept(); System.out.println("本地的ip及端口:" + socket.getLocalAddress() + " " + socket.getLocalPort()); System.out.println("服务器的ip及端口:" + socket.getInetAddress() + " " + socket.getPort()); //接收到客户端的请求,创建socket套接字对象,获取输入流对象,等待接收客户的数据 InputStream is = socket.getInputStream(); System.out.println("已接收到客户端的连接请求,并建立了连接"); System.out.println("已拿到输入流,等待接收客户端的信息"); //准备读取客户端的数据 int len; byte[] b = new byte[1024]; //解除阻塞,处理客户的数据 len = is.read(b);//程序在此阻塞,等待数据的到达 System.out.println("客户端说:" + new String(b, 0, len)); //服务器向客户端回话 OutputStream os = socket.getOutputStream(); os.write("你好!".getBytes()); System.out.println("发送完毕"); socket.close(); ssk.close(); } }
package com.zhong.test_7; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class SocketDemo { public static void main(String[] args) throws IOException, InterruptedException { //创建客户端套接字 Socket sk = new Socket("127.0.0.1", 8989); System.out.println("本地的ip及端口:" + sk.getLocalAddress() + " " + sk.getLocalPort()); System.out.println("服务器的ip及端口:" + sk.getInetAddress() + " " + sk.getPort()); //sk创建成功,则两端的连接成功建立 //得到输出流对象,向服务器发送信息 OutputStream os = sk.getOutputStream(); System.out.println("已建立与服务器的连接,向服务器发信息。"); os.write("hello word!".getBytes());//不会阻塞 InputStream is = sk.getInputStream(); byte[] b = new byte[1024]; int len = is.read(b);//此处会阻塞等待信息到达 System.out.println("客户端正在等待服务器的信息:"); System.out.println("服务器说:" + new String(b, 0, len)); System.out.println(new String(b, 0, len)); sk.close(); } }
-
文件上传
package com.zhong.test_7; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class FileServer { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("E:\\IDEA project\\src\\com\\zhong\\test_7\\new.txt"); ServerSocket ssk = new ServerSocket(2520); Socket sk = ssk.accept(); InputStream is = sk.getInputStream(); byte[] b = new byte[1024]; int len; System.out.println("等待接收文件…………"); while ((len = is.read(b)) != -1) { fos.write(b, 0, len); fos.flush(); } System.out.println("文件接收完毕"); fos.close(); sk.close(); } }
package com.zhong.test_7; import java.io.FileInputStream; import java.io.IOException; import java.net.Socket; public class FileClient { public static void main(String[] args) throws IOException { Socket sk = new Socket("localhost", 2520); FileInputStream fis = new FileInputStream("F:\\我的文件\\常用系统命令及快捷键.txt"); int len; byte[] b = new byte[1024]; System.out.println("开始发送文件…………"); while ((len = fis.read(b)) != -1) { sk.getOutputStream().write(b, 0, len); } System.out.println("文件上传完毕"); fis.close(); sk.close(); } }
-
互相不间断的发送信息
package com.zhong.test_7; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class SessionServer { public static void main(String[] args) throws IOException { ServerSocket ssk = new ServerSocket(8989); System.out.println("服务器启动……"); Socket sk = ssk.accept(); System.out.println("已有客户连接"); BufferedReader br = null; PrintWriter pw = null; String word; Scanner sc = new Scanner(System.in); while (true) { br = new BufferedReader(new InputStreamReader(sk.getInputStream())); System.out.println("服务器在等待消息……"); word = br.readLine(); System.out.println("客户说:" + word); if (word.equals("再见")) break; pw = new PrintWriter(sk.getOutputStream(), true); System.out.print("服务器说:"); word = sc.next();//阻塞等待用户输入 pw.println(word); pw.flush(); if (word.equals("再见")) break; } pw.close(); br.close(); sk.close(); ssk.close(); } }
package com.zhong.test_7; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; public class SessionClient { public static void main(String[] args) throws IOException { Socket sk = new Socket("127.0.0.1", 8989); Scanner sc = new Scanner(System.in); PrintWriter pw = null; BufferedReader br = null; String word; while (true) { pw = new PrintWriter(sk.getOutputStream(), true); System.out.print("客户说:"); word = sc.next(); pw.println(word); pw.flush(); if (word.equals("再见")) break; System.out.println("客户在等待消息……"); br = new BufferedReader(new InputStreamReader(sk.getInputStream())); word = br.readLine(); System.out.println("服务器说:" + word); if (word.equals("再见")) break; } pw.close(); br.close(); sk.close(); } }
-
QQ聊天(私聊和群聊)
package com.zhong.test_8; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; public class QQServer { static private ArrayList<User> userRegister = new ArrayList<>(); public static void main(String[] args) {//主线程,管理线程 ServerSocket ssk = null; try { ssk = new ServerSocket(3434); while (true) { System.out.println("等待客户端的连接请求……"); Socket sk = ssk.accept();//sk负责与此次连接的客户端进行通信 BufferedReader br = new BufferedReader(new InputStreamReader(sk.getInputStream())); String userName = br.readLine(); User user = new User(userName, sk); userRegister.add(user);//把sk套接字放到集合中 new Thread(new UserThread(user)).start(); } } catch (Exception e) { System.out.println("有客户断开连接。"); } } static private boolean isClose(Socket sk) {//判断是否断开连接 try { sk.sendUrgentData(0xff); return false; } catch (Exception e) { return true; } } //服务端为各用户提供通信服务的用户线程 //其任务一是接收对应客户端的信息(读),把其他客户端传来的信息发送给本客户端(写) static class UserThread implements Runnable { private User user; private ObjectInputStream readStream; public UserThread(User user) throws IOException { this.user = user;//本线程接收套接字对象 readStream = new ObjectInputStream(user.getSk().getInputStream());//创建本线程的输入流对象 } @Override public void run() { while (true) { if (isClose(user.getSk())) { System.out.println("用户" + user.getUserName() + " 已断开!"); userRegister.remove(user); return; } else { System.out.println(Thread.currentThread().getName() + "等待接收信息"); try { Message message = (Message) readStream.readObject();//会阻塞,等待信息的到达 System.out.println(message); boolean isExist = false; boolean all = false; //把信息发送给接收方(对应客户端),先拿到对方的Socket,再写回客户端 for (User user : userRegister) { if (message.getReceive().equals(this.user.getUserName()) && !message.getReceive().equals("all")) { String date = new SimpleDateFormat("yyyy年/MM月/dd日 HH:mm:ss").format(Calendar.getInstance().getTime()); Message msg = new Message("系统", user.getUserName(), "不可以自言自语", date); ObjectOutputStream oos = new ObjectOutputStream(user.getSk().getOutputStream()); oos.writeObject(msg); isExist = true; System.out.println("系统给 " + user + " 发了一条一级警告"); // oos.close(); break;//如果是本线程的套接字,则不发送信息 } if (message.getReceive().equals("all")) {//群发 ObjectOutputStream writeStream = new ObjectOutputStream(user.getSk().getOutputStream());//得到对方的套接字对象 if (!all) { message.setDate(message.getDate() + " 全体消息"); all = true; } writeStream.writeObject(message); System.out.println(user + "发送信息到了客户端"); isExist = true; // writeStream.close(); } if (message.getReceive().equals(user.getUserName())) {//单独发 ObjectOutputStream writeStream = new ObjectOutputStream(user.getSk().getOutputStream());//得到对方的套接字对象 writeStream.writeObject(message); System.out.println(user + "发送信息到了客户端"); isExist = true; // writeStream.close(); break; } } if (!isExist) { String date = new SimpleDateFormat("yyyy年/MM月/dd日 HH:mm:ss").format(Calendar.getInstance().getTime()); Message msg = new Message("系统", user.getUserName(), "对不起,查无此人或此人已下线!", date); ObjectOutputStream oos = new ObjectOutputStream(user.getSk().getOutputStream()); oos.writeObject(msg); System.out.println("系统给 " + user + " 发了一条二级警告"); // oos.close(); } } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } } } }
package com.zhong.test_8; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Scanner; public class QQClient { public static void main(String[] args) throws IOException, ClassNotFoundException {//主线程负责接收信息 Socket sk = new Socket("127.0.0.1", 3434);//创建客户端套接字,向服务器发首次连接请求 Scanner sc = new Scanner(System.in); PrintWriter pw = new PrintWriter(sk.getOutputStream(), true); System.out.print("请输入您的名字:"); String userName = sc.next(); pw.println(userName); pw.flush(); User user = new User(userName, sk); ObjectInputStream ois; new Thread(new UserWrite(user)).start();//启动写信息的子线程 while (true) {//主线程中负责接收服务器发来的信息,该信息来源于其他的客户端 ois = new ObjectInputStream(sk.getInputStream());//得到输入流对象 Message msg = (Message) ois.readObject();//阻塞,等待信息到达 System.out.println(msg); System.out.print(user.getUserName() + "(你):");//输出输入提示语 } } static class UserWrite implements Runnable { private User user; private ObjectOutputStream oos; public UserWrite(User user) throws IOException { this.user = user; oos = new ObjectOutputStream(user.getSk().getOutputStream());//创建发信息的输出流对象 } @Override public void run() { Scanner sc = new Scanner(System.in);//接收用户的输入 System.out.print("接收人(all表示全体):"); String receive = sc.next(); while (true) {//不断的书写信息并发送 System.out.print(user.getUserName() + "(你):");//输出输入提示语 String word = sc.next();//接收控制台的输入,阻塞 String date = new SimpleDateFormat("yyyy年/MM月/dd日 HH:mm:ss").format(Calendar.getInstance().getTime()); //把输入内容封装到信息对象中 Message msg = new Message(user.getUserName(), receive, word, date); try { if (!receive.equals("all")) System.out.println(msg.getSender() + "(" + msg.getDate() + "):" + msg.getWord()); oos.writeObject(msg);//发送信息 oos.flush(); } catch (IOException e) { e.printStackTrace(); } } } } }