Java语法进阶14-网络编程
网络通信协议:位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,它对数据的传输格式、传输速率、传输步骤等做了统一规定
TCP/IP协议:它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,每一层都呼叫它的下一层所提供的协议来完成自己的需求
-
-
表示层:数据的表示、安全、压缩。格式有:JPEG、ASCll、DECOIC、加密格式等。
-
会话层:建立、管理、终止会话。对应主机进程,指本地主机与远程主机正在进行的会话
-
传输层:定义传输数据的协议端口号,以及流控和差错校验。协议有:TCP、UDP。
-
网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。协议有:ICMP、IGMP、IP(IPV4 IPV6)、ARP、RARP。
-
数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能。将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
-
物理层:建立、维护、断开物理连接。
IP(internet protocal)又称为互联网协议。IP的责任就是把数据从源传送到目的地。它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求。经常与IP协议放在一起的还有TCP(Transmission Control Protocol)协议
- 四次挥手
IP地址用来给一个网络中的计算机设备做唯一的编号,
IPv4:32位整数,8位一组最多可以表示42亿个
IPv6:采用128位地址长度,每16个字节一组,分成8组十六进制数
公网地址( 万维网使用)和 私有地址( 局域网使用)。192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
特殊的IP地址:
-
-
主机名(hostName):
localhost
域名:域名服务器(DNS)负责将域名转化成IP地址,方便记忆。
如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
利用协议
+IP地址
+端口号
三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
InetAddress类主要表示IP地址,两个子类:Inet4Address、Inet6Address。
lInetAddress 类没有提供公共的构造器,而是提供 了 如下几个 静态方法来获取InetAddress 实例
-
public static InetAddress getLocalHost() 【返回本地主机】
-
public static InetAddress getByName(String host) 【在给定主机名的情况下确定主机的 IP 地址】
-
public static InetAddress getByAddress(byte[] addr) 【在给定原始 IP 地址的情况下,返回
InetAddress
对象】 - 例:byte[] addr = {(byte)192,(byte)168,24,56}; 其内部用一个int存储
InetAddress 提供了如下几个常用的方法
-
public String getHostAddress() : 【返回 IP 地址字符串(以文本表现形式)】
-
public String getHostName() : 【获取此 IP 地址的主机名】
- public String getCanonicalHostName(): 【获取此 IP 地址的完全限定域名】
- boolean isReachable(int timeout) 【测试是否可以达到该地址。】
socket是两个主机通信的关键,先理解IO流的工作流程有助于理解网络间的通信,socket数据的发送与接收也可简单的理解为:
客户端将要发送的数据通过send()发送给客户端的tcp/udp协议的缓冲区,由客户端协议发送给服务端的tcp/udp协议,服务端的receive()会读取服务端的协议缓冲区接收到的数据,
如需返回数据再经服务端的send()发送给服务端的协议缓冲区,服务端的协议再发送给客户端的协议,客户端的receive()会读取客户端协议缓冲区中的数据如此循环,直到close()
-
-
Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。流套接字
- DatagramSocket:此类表示用来发送和接收UDP数据报包的套接字。数据报套接字
-
-
调用 accept() :监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
-
调用 该Socket 类对象的 getOutputStream() 和 getInputStream () :获取输出流和输入流,开始网络数据的发送和接收。
-
关闭Socket 对象:客户端访问结束,关闭通信套接字。
-
-
打开连接到 Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输
-
按照一定的协议对 Socket 进行读/ 写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线路。
-
关闭 Socket:断开客户端到服务器的连接,释放线路
ServerSocket类的构造方法:
-
ServerSocket(int port) : 【创建绑定到特定端口的服务器套接字】
ServerSocket类的常用方法:
-
- InetAddress getInetAddress() 【返回此服务器套接字的本地地址。】
- int getLocalPort() 【返回此套接字在其上侦听的端口。】
- void close(): 【关闭此套接字。】
Socket类的常用构造方法:
-
public Socket(InetAddress address,int port): 【创建一个流套接字并将其连接到指定 IP 地址的指定端口号】
-
Socket类的常用方法:
-
public InputStream getInputStream(): 【返回此套接字的输入流,可以用于接收消息】
-
public OutputStream getOutputStream(): 【返回此套接字的输出流,可以用于发送消息】
-
public InetAddress getInetAddress(): 【返回此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null】
-
public InetAddress getLocalAddress(): 【获取套接字绑定的本地地址】
-
public int getPort(): 【返回此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0】
-
public int getLocalPort(): 【返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回 -1】
-
public void close(): 【关闭套接字(即无法重新连接或重新绑定) 同时也将会关闭该套接字的 InputStream 和 OutputStream】
-
public void shutdownInput():
如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。关闭输入流
-
public void shutdownOutput():
禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。关闭输出流
- boolean isInputShutdown() : 【返回是否关闭套接字连接的半读状态 (read-half)】
- boolean isOutputShutdown() : 【返回是否关闭套接字连接的半写状态 (write-half)】
注意:先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用Socket的close()方法。在通信结束后,仍然要调用Scoket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口号等。
-
public DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
-
public DatagramSocket(int port,InetAddress laddr)
创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
-
public void send(DatagramPacket p)
从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
-
public void receive(DatagramPacket p)
从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
- public void close() 【关闭此数据报套接字。】
DatagramPacket类的常用方法:
-
public DatagramPacket(byte[] buf,int length)
构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。
-
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。
-
public int getLength() 【返回将要发送或接收到的数据的长度】
多点广播
Datagram只允许数据报发送给指定的目标地址,而MulticastSocket可以将数据报以广播方式发送到数量不等的多个客户端。
IP协议为多点广播提供了这批特殊的IP地址,这些IP地址的范围是224.0.0.0至239.255.255.255。
MulticastSocket常用的方法:
-
MulticastSocket(int port) :
创建多播套接字并将其绑定到特定端口。创建一个MulticastSocket对象后,还需要将该MulticastSocket加入到指定的多点广播地址,如果结束也需要脱离多点广播地址。
-
void joinGroup(InetAddress mcastaddr) :【加入多播组。】
-
void leaveGroup(InetAddress mcastaddr) :【离开多播组。】
-
void setLoopbackMode(boolean disable) :【启用/禁用多播数据报的本地回送。true 表示禁用LoopbackMode。】
public class Client { public static void main(String[] args) throws UnknownHostException, IOException { // 1、连接服务器 Socket socket = new Socket("127.0.0.1", 9999); // 2、开启两个线程,一个收消息,一个发消息 SendThread st = new SendThread(socket); ReceiveThread rt = new ReceiveThread(socket);
st.start(); rt.start(); // 等发送线程停下来再往下走 try { st.join(); } catch (InterruptedException e) { e.printStackTrace(); } // 等接收线程停下来,再往下走,断开连接 try { rt.join(); } catch (InterruptedException e) { e.printStackTrace(); } socket.close(); } static class SendThread extends Thread { private Socket socket; public SendThread(Socket socket) { super(); this.socket = socket; } public void run() { try { // 键盘输入 Scanner input = new Scanner(System.in); OutputStream out = socket.getOutputStream(); PrintStream ps = new PrintStream(out); while (true) { // 从键盘输入 System.out.print("请输入要发送的消息:"); String content = input.nextLine(); // 给服务器发送 ps.println(content); // 如果bye,就结束发送 if ("bye".equals(content)) { break; } } input.close(); } catch (IOException e) { e.printStackTrace(); } } } static class ReceiveThread extends Thread { private Socket socket; public ReceiveThread(Socket socket) { super(); this.socket = socket; } public void run() { try { InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); while (true) { String line = br.readLine(); if("bye".equals(line)){ break; } System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } }
public class Server { private static ArrayList<Socket> online = new ArrayList<Socket>(); public static void main(String[] args) throws IOException { //1、开启服务器 ServerSocket server = new ServerSocket(9999); while(true){ //2、接收客户端的连接 Socket socket = server.accept(); //把这个客户端加入到online中 online.add(socket); //每一个客户端独立的线程 MessageHandler mh = new MessageHandler(socket); mh.start(); } } private static class MessageHandler extends Thread{ private Socket socket; private String ip; public MessageHandler(Socket socket) { super(); this.socket = socket; this.ip = socket.getInetAddress().getHostAddress(); } public void run(){ //这个客户端的一连接成功,线程一启动,就可以告诉其他人我上线了 sendToOthers(ip+"上线了"); //(1)接收当前的客户端发送的消息 try { InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); String content; while((content = br.readLine()) !=null){ //收到一句,转发一句 sendToOthers(ip+"说:" + content); if("bye".equals(content)){ //给自己发一句bye OutputStream out = socket.getOutputStream(); PrintStream ps = new PrintStream(out); ps.println("bye"); break; } } sendToOthers(ip+"下线了"); } catch (IOException e) { sendToOthers(ip+"掉线了"); } } //因为转发的代码也很长,独立为一个方法 public void sendToOthers(String str){ //遍历所有online的客户端 Iterator<Socket> iterator = online.iterator(); while(iterator.hasNext()){ Socket on = iterator.next(); if(!on.equals(socket)){//只给其他客户端转发 try { OutputStream out = on.getOutputStream(); PrintStream ps = new PrintStream(out); ps.println(str); } catch (IOException e) { //说明on这个客户端要么下线了,要么掉线了 iterator.remove(); } } } } } }