Java语法进阶14-网络编程

网络编程

软件结构

C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。html

B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。程序员

网络通讯协议

网络通讯协议:位于同一个网络中的计算机在进行链接和通讯时须要遵照必定的规则,它对数据的传输格式、传输速率、传输步骤等作了统一规定编程

TCP/IP协议:它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通讯的协议,每一层都呼叫它的下一层所提供的协议来完成本身的需求浏览器

  • 应用层:网络服务与最终用户的一个接口。协议有:HTTP、FTP、SMTP、DNS、TELNET、HTTPS、POP3等等。安全

  • 表示层:数据的表示、安全、压缩。格式有:JPEG、ASCll、DECOIC、加密格式等。服务器

  • 会话层:创建、管理、终止会话。对应主机进程,指本地主机与远程主机正在进行的会话网络

  • 传输层:定义传输数据的协议端口号,以及流控和差错校验。协议有:TCP、UDP。socket

  • 网络层:进行逻辑地址寻址,实现不一样网络之间的路径选择。协议有:ICMP、IGMP、IP(IPV4 IPV6)、ARP、RARP。tcp

  • 数据链路层:创建逻辑链接、进行硬件地址寻址、差错校验等功能。将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。函数

  • 物理层:创建、维护、断开物理链接。

IP(internet protocal)又称为互联网协议。IP的责任就是把数据从源传送到目的地。它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的从新组装功能,以适应不一样网络对包大小的要求。常常与IP协议放在一块儿的还有TCP(Transmission Control Protocol)协议

TCP与UDP协议

UDP:用户数据报协议(User Datagram Protocol)。

  • 非面向链接的,不可靠的:发送端不会确认接收端是否存在,就会发出数据,一样接收端在收到数据时,也不会向发送端反馈是否收到数据。
  • 大小限制的:数据被限制在64kb之内,超出这个范围就不能发送了。
  • 数据报(Datagram):网络传输的基本单位

TCP:传输控制协议 (Transmission Control Protocol)。

  • 面向链接的,可靠的:在发送端和接收端创建逻辑链接,而后再传输数据,是一种面向链接的、可靠的、基于字节流的传输层的通讯协议,能够连续传输大量的数据。TCP协议保证了数据包在传送中准确无误,TCP协议使用重发机制,须要收到另外一个通讯实体的确认信息
  • 三次握手

  • 四次挥手

网络编程三要素

一、协议:如上

二、IP地址

IP地址用来给一个网络中的计算机设备作惟一的编号,

IPv4:32位整数,8位一组最多能够表示42亿个

IPv6:采用128位地址长度,每16个字节一组,分红8组十六进制数

公网地址( 万维网使用)和 私有地址( 局域网使用)。192.168.开头的就是私有址址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用

特殊的IP地址:

  • 本地回环地址(hostAddress):127.0.0.1

  • 主机名(hostName):localhost

域名:域名服务器(DNS)负责将域名转化成IP地址,方便记忆。

三、端口号

端口号能够找到惟一标识设备中的进程(应用程序)0~65535,动态/ 私有端口:49152~65535

若是端口号被另一个服务或应用所占用,会致使当前程序启动失败。

利用协议+IP地址+端口号 三元组合,就能够标识网络中的进程了,那么进程间的通讯就能够利用这个标识与其它进程进行交互。

InetAddress类

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

socket 可理解为一个介于应用层与协议层之间的一个抽象层,它屏蔽了各个协议的通讯细节,使得程序员无需关注协议自己,直接使用socket提供的接口来进行互联的不一样主机间的进程的通讯。

就是提供了tcp/ip协议的抽象,对外提供了一套接口,经过这个接口就能够统1、方便的使用tcp/ip协议的功能了

通讯的两端都要有Socket(也能够叫“套接字”),是两台机器间通讯的端点。网络通讯其实就是Socket间的通讯。也是负责和网卡驱动程序沟通的对象。socket工做流程

socket是两个主机通讯的关键,先理解IO流的工做流程有助于理解网络间的通讯,socket数据的发送与接收也可简单的理解为:

客户端将要发送的数据经过send()发送给客户端的tcp/udp协议的缓冲区,由客户端协议发送给服务端的tcp/udp协议,服务端的receive()会读取服务端的协议缓冲区接收到的数据,

如需返回数据再经服务端的send()发送给服务端的协议缓冲区,服务端的协议再发送给客户端的协议,客户端的receive()会读取客户端协议缓冲区中的数据如此循环,直到close()

send()与receive()函数

  • ServerSocket:此类实现TCP服务器套接字。服务器套接字等待请求经过网络传入。流套接字

  • Socket:此类实现客户端套接字(也能够就叫“套接字”)。套接字是两台机器间通讯的端点。流套接字

  • DatagramSocket:此类表示用来发送和接收UDP数据报包的套接字。数据报套接字

TCP网络编程

一、服务器端

  • 调用 ServerSocket(int port) :建立一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。

  • 调用 accept() :监听链接请求,若是客户端请求链接,则接受链接,返回通讯套接字对象。

  • 调用 该Socket 类对象的 getOutputStream() 和 getInputStream () :获取输出流和输入流,开始网络数据的发送和接收。

  • 关闭Socket 对象:客户端访问结束,关闭通讯套接字。

二、客户端

  • 建立 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则创建客户端到服务器的通讯线路。若链接失败,会出现异常。

  • 打开链接到 Socket 的输入/出流: 使用 getInputStream()方法得到输入流,使用getOutputStream()方法得到输出流,进行数据传输

  • 按照必定的协议对 Socket 进行读/ 写操做:经过输入流读取服务器放入线路的信息(但不能读取本身放入线路的信息),经过输出流将信息写入线路。

  • 关闭 Socket:断开客户端到服务器的链接,释放线路

API

ServerSocket类的构造方法:

  • ServerSocket(int port) :                                         【建立绑定到特定端口的服务器套接字】

ServerSocket类的经常使用方法:

  • Socket accept()                                                             【侦听并接受到此套接字的链接。】 

  • InetAddress getInetAddress()                                       【返回此服务器套接字的本地地址。】
  • int getLocalPort()                                                          【返回此套接字在其上侦听的端口。】 
  • void close():                                                                  【关闭此套接字。】

Socket类的经常使用构造方法

  • public Socket(InetAddress address,int port):              【建立一个流套接字并将其链接到指定 IP 地址的指定端口号】

  • public Socket(String host,int port):                              【建立一个流套接字并将其链接到指定主机上的指定端口号】

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占用的资源,好比占用的本地端口号等。

若是服务器端要“同时”处理多个客户端的请求,所以服务器端须要为每个客户端单独分配一个线程来处理,不然没法实现“同时”。

UDP网络编程

UDP(User Datagram Protocol,用户数据报协议)特色:

在正式通讯前没必要与对方先创建链接,至于对方是否能够接收到这些数据内容,UDP协议没法控制,无链接的好处就是快,省内存空间和流量,没有TCP的确认机制、重传机制,若是由于网络缘由没有传送到对端,UDP也不会给应用层返回错误信息。

UDP协议是面向数据报文的信息传送服务。UDP在发送端没有缓冲区,对于应用层交付下来的报文在添加了首部以后就直接交付于ip层,不会进行合并,也不会进行拆分,而是一次交付一个完整的报文。

UDP协议没有拥塞控制,因此当网络出现的拥塞不会致使主机发送数据的速率下降。虽然UDP的接收端有缓冲区,可是这个缓冲区只负责接收,并不会保证UDP报文的到达顺序是否和发送的顺序一致。

所以UDP适用于一次只传送少许数据、对可靠性要求不高的应用环境,数据报大小限制在64K如下。

API

基于UDP协议的网络编程仍然须要在通讯实例的两端各创建一个Socket,但这两个Socket之间并无虚拟链路,这两个Socket只是发送、接收数据报的对象

DatagramSocket 类的经常使用方法:

  • 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。】

TCP网络编程示例:群聊

客户端未导包

public class Client {
    public static void main(String[] args) throws UnknownHostException, IOException {
        // 一、链接服务器
        Socket socket = new Socket("127.0.0.1", 9999);
        // 二、开启两个线程,一个收消息,一个发消息
        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 {
        //一、开启服务器
        ServerSocket server = new ServerSocket(9999);
        
        while(true){
            //二、接收客户端的链接
            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();
                    }
                }
            }
        }
    }
}
相关文章
相关标签/搜索