Android网络 | Socket(Eclipse--Java

作者: 凌川江雪 | 来源:发表于2019-12-05 22:18 被阅读0次
    • 在现实网络传输应用中,
      通常使用TCPIPUDP这3种协议实现数据传输

      传输数据的过程中,
      需要通过一个双向的通信连接实现数据的交互

      在这个传输过程中,
      通常将这个双向链路的一端称为Socket
      一个Socket通常由一个IP地址和一个端口号来确定。

      在整个数据传输过程中Socket的作用是巨大的。
      在Java编程应用中,Socket是Java网络编程的核心。

    Socket基础

    • 网络编程中有两个主要的问题
      一个是如何准确地定位网络上一台或多台主机,
      另一个就是找到主机后如何可靠高效地进行数据传输

      TCP/IP协议
      IP层主要负责网络主机的定位数据传输路由
      IP地址可以唯一地确定Internet上的一台主机
      TCP层
      提供面向应用的可靠(TCP)
      非可靠(UDP)数据传输机制
      这是网络编程主要对象
      一般不需要关心IP 层如何处理数据的。

      目前较为流行的网络编程模型客户机/服务器(C/S)结构
      通信双方,一方作为服务器
      等待(另一方作为的)客户提出请求并予以响应
      客户则在需要服务时向服务器提出申请

      服务器一般作为守护进程 始终运行,
      监听网络端口,
      一旦有客户请求,就会启动一个服务进程来响应该客户,
      同时自己继续监听服务端口
      使后来的客户也能及时得到服务

    TCP/IP协议基础

    • TCP/IPTransmission Control Protocol/Internet Protocol的简写,
      中译名为传输控制协议/因特网协议
      又名网络通信协议
      是Internet最基本的协议Internet国际互联网络基础

      网络层IP协议
      传输层TCP协议组成。

      TCP/IP定义了电子设备如何连入因特网
      以及数据如何在它们之间传输的标准

      TCP/IP协议采用了4层层级结构
      每一层都呼叫它的下一层所提供的协议来完成自己的需求

      也就是说,
      TCP负责发现传输的问题,
      一旦发现问题便发出信号要求重新传输
      直到所有数据安全正确地传输到目的地
      IP的功能是给因特网的每一台电脑规定一个地址

      TCP/IP协议不是TCPIP这两个协议的合称
      而是指因特网整个TCP/IP协议簇

      协议分层模型方面来讲,TCP/IP4个层次组成,
      分别是网络接口层网络层传输层应用层

    • 其实TCP/IP协议并不完全符合OSI(Open System Interconnect)7层参考模型
      OSI是传统的开放式系统互连参考模型
      是一种通信协议7层抽象参考模型
      其中每一层执行某一特定任务
      该模型的目的
      使各种硬件相同的层次相互通信

      7层
      物理层、数据链路层(网络接口层)
      网络层(网络层)
      传送层(传输层)
      会话层、表示层和应用层(应用层)
      TCP/IP协议采用了4层的层级结构,
      每一层都呼叫它的下一层所提供的网络来完成自己的需求

      由于ARPANET的设计者注重的是网络互联
      允许通信子网(网络接口层)采用已有的或是将来有的各种协议
      所以这个层次中没有提供专门的协议

      实际上,
      TCP/IP协议可以通过网络接口层连接到任何网络上,
      例如X.25交换网IEEE802局域网

    UDP协议

    • UDPUser Datagram Protocol的简称,
      是一种无连接的协议,
      每个数据报都是一个独立的信息
      包括完整源地址目的地址
      它在网络上以任何可能的路径传往目的地
      因此能否到达目的地
      到达目的地的时间以及内容的正确性都是不能被保证的。

      现实网络数据传输过程中
      大多数功能是由TCP协议UDP协议实现。

    • (1)TCP协议
      面向连接的协议,
      Socket之间进行数据传输之前必然要建立连接
      所以在TCP中需要连接时间

      TCP传输数据大小限制
      一旦连接建立起来,
      双方的Socket就可以按统一的格式传输大的数据
      TCP是一个可靠的协议
      它确保接收方完全正确地获取发送方所发送的全部数据

    • (2)UDP协议
      每个数据报中都给出了完整地址信息
      因此无需要建立发送方接收方连接

      UDP传输数据时是有大小限制的,
      每个被传输的数据报必须限定在64KB之内。

      UDP是一个不可靠的协议,
      发送方所发送的数据报并不一定以相同的次序到达接收方

    TCP、UDP选择的决定因素
    • (1)TCP在网络通信上有极强生命力
      例如远程连接(Telnet)文件传输(FTP)
      都需要不定长度数据可靠地传输
      但是可靠的传输是要付出代价的,
      数据内容正确性的检验必然占用计算机的处理时间网络的带宽
      因此TCP传输效率不如UDP高

    • (2)UDP 操作简单,而且仅需要较少的监护
      因此通常用于局域网高可靠性分散系统Client/Server 应用程序
      例如视频会议系统
      并不要求音频视频数据 绝对的正确
      只要保证连贯性就可以了,
      这种情况下显然使用UDP更合理一些,
      因为TCPUDP都能达到这个保证连贯性的门槛,
      但是TCP却要多占用更多的计算机资源
      杀鸡焉用牛刀呢,
      所有这种情况不用TCP,用UDP

    基于Socket的Java网络编程

    • 网络上的两个程序通过一个双向通信连接实现数据的交换
      这个双向链路一端称为一个Socket

    • Socket通常用来实现客户方服务方的连接。

    • SocketTCP/IP协议的一个十分流行的编程方式,
      一个Socket一个IP地址一个端口号 唯一确定
      但是,
      Socket所支持的协议种类也不光TCP/IP一种,
      因此两者之间是没有必然联系的。
      Java环境下,
      Socket编程主要是指基于TCP/IP协议网络编程

    1.Socket通信的过程
    • ServerListen(监听)某个端口是否有连接请求
      Client端Server 端发出Connect(连接)请求
      Server端Client端发回Accept(接收)消息
      一个连接就建立起来了。

    • Server端Client端都可以通过SendWrite等方法与对方通信

    • Java网络编程应用中,
      对于一个功能齐全的Socket来说,
      其工作过程包含如下所示的基本步骤。
      (1)创建ServerSocketSocket
      (2)打开连接到Socket输入/输出流
      (3)按照一定的协议对Socket进行读/写操作
      (4)关闭IO流Socket

    2.创建Socket
    • Java网络编程应用中,
      java.net中提供了两个类SocketServerSocket
      分别用来表示双向连接客户端服务端
      这是两个封装得非常好的类,
      其中包含了如下所示的构造方法

    • Socket(InetAddress address, int port);

    • Socket(InetAddress address, int port, boolean stream);

    • Socket(String host, int prot);

    • Socket(String host, int prot, boolean stream);

    • Socket(SocketImpl impl);

    • Socket(String host, int port, InetAddress localAddr, int localPort);

    • Socket(InetAddress address, int port, InetAddress localAddr, int localPort);

    • ServerSocket(int port);

    • ServerSocket(int port, int backlog);

    • ServerSocket(int port, int backlog, InetAddress bindAddr)

    • 在上述构造方法中,
      参数addresshostport分别是
      双向连接另一方IP地址主机名端口号
      stream指明Socket流Socket还是数据报Socket
      localPort表示本地主机端口号
      localAddrbindAddr本地机器的地址ServerSocket主机地址),
      implSocket父类
      既可以用来创建ServerSocket又可以用来创建Socket

      例如:

        Socket client = new Socket("127.0.0.1", 80);
        ServerSocket server = new ServerSocket(80);
    
    • 注意:
      必须小心地选择端口
      每一个端口提供一种特定的服务
      只有给出正确的端口,才能获得相应的服务

      0~1023端口号系统所保留
      例如
      HTTP服务的端口号为80,
      Telnet服务的端口号为21,
      FTP服务的端口号为23
      所以我们在选择端口号时,最好选择一个大于1023以防止发生冲突
      另外,
      创建Socket时如果发生错误,将产生IOException
      程序中必须对之做出处理
      所以在创建SocketServerSocket时必须捕获抛出异常

    TCP编程详解

    • TCP/IP通信协议是一种可靠网络协议
      能够在通信的两端各建立一个Socket
      从而在通信的两端之间形成网络虚拟链路

      一旦建立了虚拟的网络链路
      两端的程序就可以通过虚拟链路进行通信

      Java语言对TCP网络通信提供了良好的封装
      通过Socket对象代表两端通信端口
      并通过Socket产生的IO流进行网络通信

      这里先笔记Java应用中TCP编程的基本知识,
      为后面的Android编程打下基础。
    使用ServerSocket

    在Java程序中,

    • 使用
      类ServerSocket 接受其他通信实体连接请求
      对象ServerSocket的功能是监听来自客户端的Socket连接
      如果没有连接则会一直处于等待状态

    • 在类ServerSocket中包含了如下监听客户端连接请求的方法:
      Socket accept():如果接收到一个客户端Socket连接请求
      该方法将返回一个与客户端Socket对应的Socket
      否则该方法将一直处于等待状态,线程也被阻塞

    • 为了创建ServerSocket对象
      ServerSocket类为我们提供了如下构造器

      • ServerSocket(int port)
        用指定的端口port创建一个ServerSocket
        端口应该是有一个有效端口整数值0~65535

      • ServerSocket(int port,int backlog)
        增加一个用来改变连接队列长度参数backlog

      • ServerSocket(int port,int backlog,InetAddress localAddr)
        机器(服务器、本机等)存在多个IP地址的情况下,
        允许通过localAddr这个参数
        来指定将ServerSocket绑定到指定的IP地址

    • 当使用ServerSocket后,
      需要使用ServerSocket中的方法close()关闭该ServerSocket

    • 在通常情况下,
      因为服务器不会只接受一个客户端请求
      而是会不断地接受来自客户端所有请求
      所以可以通过循环不断调用ServerSocket中的方法accept()

    • 例如下面的代码。

        //创建一个ServerSocket,用于监听客户端Socket的连接请求
        ServerSocket ss = new ServerSocket(30000);
        //采用循环不断接受来自客户端的请求
        while (true)
        {
        //每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
        Socket s = ss.accept();
        //下面就可以使用Socket进行通信了
        ...
        }
    
    • 在上述代码中,
      创建的ServerSocket没有指定IP地址
      ServerSocket绑定本机默认IP地址

      在代码中使用30000作为该ServerSocket端口号
      通常推荐使用10000以上的端口
      主要是为了避免与其他应用程序通用端口 冲突
    使用Socket
    • 在客户端可以使用Socket构造器
      实现``和指定服务器连接
      Socket中可以使用如下两个构造器:

      • Socket(InetAddress/String remoteAddress, int port)
        创建连接到指定远程主机远程端口的Socket
        该构造器没有指定本地地址本地端口
        本地IP地址端口使用默认值

      • Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort)
        创建连接到指定远程主机远程端口Socket
        并指定本地IP地址本地端口号
        适用于本地主机多个IP地址的情形。

    • 在使用上述构造器指定远程主机时,
      既可使用InetAddress来指定,也可以使用String对象指定,
      Java中通常使用String对象指定远程IP,例如192.168.2.23
      当本地主机只有一个IP地址时,建议使用第一个方法,简单方便。
      例如下面的代码:

        //创建连接到本机、30000端口的Socket
        Socket s = new Socket("127.0.0.1" , 30000);
    
    • 当程序执行上述代码后会连接到指定服务器
      服务器端ServerSocket的方法accept()向下执行,
      于是服务器端客户端就产生一对互相连接的Socket
      上述代码连接到“远程主机”的IP地址是127.0.0.1
      IP地址总是代表本机的IP地址
      这里例程的服务器端客户端都是在本机运行,
      所以Socket连接到远程主机IP地址使用127.0.0.1。

    • 客户端服务器端产生对应的Socket之后,
      程序无须再区分服务器端和客户端,
      而是通过各自的Socket进行通信。

    • Socket中提供如下两个方法获取输入流输出流

      • InputStream getInputStream()
        返回该Socket对象 对应的输入流
        让程序通过该输入流从Socket中取出数据。

      • OutputStream getOutputStream()
        返回该 Socket对象 对应的输出流
        让程序通过该输出流Socket输出数据

    • TCP协议的服务器端例程:

    public class Server
    {
        public static void main(String[] args)
            throws IOException
        {
            //创建一个ServerSocket,用于监听客户端Socket的连接请求
            ServerSocket myss = new ServerSocket(30001);
            //采用循环不断接受来自客户端的请求
            while (true)
            {
              //每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
              Socket s = myss.accept();
              //将Socket对应的输出流包装成PrintStream
              PrintStream ps = new PrintStream(s.getOutputStream());
              //进行普通IO操作
              ps.println("凌川江雪!");
              ps.println("望川霄云!");
              ps.println("万年太久,只争朝夕!");
              ps.println("人间正道是沧桑!");
              ps.println("穷善其身,达济天下!");
              
              //关闭输出流,关闭Socket
              ps.close();
              s.close();
            }
        }
    }
    
    • 上述代码建立了ServerSocket监听
      并且使用Socket获取了输出流
      执行后不会显示任何信息。
    • 对应的TCP协议的客户端例程:
    public class Client {
    
        public static void main(String[] args)  throws IOException
        {
            Socket socket = new Socket("127.0.0.1" , 30001);
            //将Socket对应的输入流包装成BufferedReader
            BufferedReader br = new BufferedReader(
              new InputStreamReader(socket.getInputStream()));
            
            //进行普通IO操作         
            StringBuilder response = new StringBuilder();
            String line;
            
            //一行一行地读取并加进stringbuilder
            while((line = br.readLine()) != null){
                response.append(line + "\n");
            }
            
            System.out.println("来自服务器的数据:" + "\n" + response.toString());  
            
            //关闭输入流、Socket
            br.close();
            socket.close();
        }
    
    }
    
    • 上述代码使用Socket建立了与指定IP、指定端口的连接,
      并使用Socket获取输入流读取数据,
      之后处理一下数据然后打印在工作台。
      运行服务端Class运行客户端Class,运行结果:

    • 由此可见,
      一旦使用ServerSocketSocket建立网络连接之后,
      程序通过网络通信普通IO并没有太大的区别。
      如果先运行上面程序中的Server 类,
      将看到服务器一直处于等待状态
      因为服务器使用了死循环来接受来自客户端的请求;
      再运行Client类,
      将可看到程序输出“来自服务器的数据:...!”,
      这表明客户端和服务器端通信成功




    TCP中的多线程

    • 刚刚实操的例程中,
      ServerClient只是进行了简单的通信操作,
      当服务器接收到客户端连接之后,服务器向客户端输出一个字符串,
      客户端也只是读取服务器的字符串退出了。

    • 在实际应用中,
      客户端可能需要和服务器端保持长时间通信
      服务器需要不断读取客户端数据
      向客户端写入数据,
      客户端也需要不断读取服务器数据,
      向服务器写入数据。

    • 当使用readLine()方法读取数据时,
      如果在该方法成功返回之前线程阻塞,则程序无法继续执行
      所以服务器很有必要为每个Socket 单独启动一条线程
      每条线程 负责与一个客户端进行通信

    • 另外,
      因为客户端读取服务器数据线程同样会被阻塞
      所以系统应该单独 启动一条线程
      该组线程专门负责读取服务器数据

    • 假设要开发一个聊天室程序
      服务器端应该包含多条线程
      其中每个Socket对应一条线程

      线程负责
      读取 Socket 对应输入流数据
      (从客户端发送过来的数据),

      并将读到的数据
      每个Socket输出流发送一遍
      (将一个客户端 发送的数据 “广播”其他客户端 );

    • 因此需要在服务器端使用List保存所有的Socket
      具体实现时,
      服务器提供了如下两个类
      创建ServerSocket监听主类
      处理每个Socket通信线程类

    1/4 接下来介绍具体实现流程,首先看下面的IServerClass:

    public class IServer
    {
        //定义保存所有Socket的ArrayList
        public static ArrayList<Socket> socketList = new ArrayList<Socket>();
    
        public static void main(String[] args) 
            throws IOException
        {
            ServerSocket ss = new ServerSocket(30000);
            while(true)
            {
                //此行代码会阻塞,将一直等待别人的连接
                Socket s = ss.accept();
                socketList.add(s);
                //每当客户端连接后启动一条ServerThread线程为该客户端服务
                new Thread(new Serverxian(s)).start();
            }
        }
    }
    

    IServer类中,
    服务器端(ServerSocket )只负责接受客户端Socket连接请求
    每当客户端Socket连接到该ServerSocket之后,
    程序将客户端对应的Socket(客户Socket的对面一端)加入 socketList集合保存
    并为该Socket启动一条线程Serverxian),
    线程负责处理 该Socket所有 的 通信任务
    小结:
    IServer类完成的业务是:
    1.接收客户端Socket
    2.保存对应返回的Socket
    3.启动处理线程

    2/4 接着看服务器端线程类文件:

    package liao.server;
    import java.io.*;
    import java.net.*;
    import java.util.*;
    
    //负责处理每个线程通信的线程类
    public class Serverxian implements Runnable 
    {
        //定义当前线程所处理的Socket
        Socket s = null;
        
        //该线程所处理的Socket所对应的输入流读取器
        BufferedReader br = null;
        
        public Serverxian(Socket s)
            throws IOException
        {
            this.s = s;
            //初始化该Socket对应的输入流
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        }
        
        public void run()
        {
            try
            {
                String content = null;
                
                //采用循环不断从Socket中读取客户端发送过来的数据
                while ((content = readFromClient()) != null)
                {
                    
                    //遍历socketList中的每个Socket,
                    //将读到的内容向每个Socket发送一次
                    for (Socket s : IServer.socketList)
                    {
                         //将Socket对应的输出流包装成PrintStream
                        PrintStream ps = new PrintStream(s.getOutputStream());
                        ps.println(content);
                    }
                }
            }
            catch (IOException e)
            {
                //e.printStackTrace();
            }
        }
        
        //定义读取客户端数据的方法
        private String readFromClient()
        {
            try
            {
                return br.readLine();   
            }
            //如果捕捉到异常,表明该Socket对应的客户端已经关闭
            catch (IOException e)
            {
                //删除该Socket。
                IServer.socketList.remove(s);
            }
            return null;
        }
    }
    

    Serverxian类(服务器端线程类)中,
    注意是线程类,继承Runnable,重写run方法
    会不断读取客户端数据,

    在获取时使用方法readFromClient()来读取客户端数据。
    如果读取数据过程中捕获到 IOException异常,
    则说明此Socket对应的客户端Socket出现了问题,
    程序就会将此Socket从socketList中删除。

    当服务器线程读到客户端数据之后会遍历整个socketList集合,
    并将该数据向socketList集合中的每个Socket发送一次,
    该服务器线程将把从Socket中读到的数据
    向socketList中的每个Socket转发一次。

    • 上述代码能够不断获取Socket输入流中的内容,
      当获取Socket输入流中的内容后,
      直接将这些内容打印在控制台

      先运行上面程序中的类IServer
      该类运行后作为本应用的服务器,不会看到任何输出。接着可以运行多个 IClient——相当于启动多个聊天室客户端登录该服务器,此时在任何一个客户端通过键盘输入一些内容后单击“回车”键,将可看到所有客户端(包括自己)都会在控制台收到刚刚输入的内容,这就简单实现了一个聊天室的功能。

    • 运行结果如下动图所示:
      (这个链接是
      在Eclipse上,同时运行多个java程序,
      用不同的console显示运行信息的方法)

      图中展现的是:已经启动服务端,
      同时启动两个客户端,
      来回切换客户端进行“聊天”,
      客户端由于服务端的socket传输,
      可以相互收到彼此的信息;

    相关文章

      网友评论

        本文标题:Android网络 | Socket(Eclipse--Java

        本文链接:https://www.haomeiwen.com/subject/tgfemctx.html