zoukankan      html  css  js  c++  java
  • 7.4 (java学习笔记)网络编程之TCP

    一、TCP

      1.1 TCP(Transmission Control Protocol 传输控制协议),是一种面向连接的,安全的传输协议,但效率相比于UDP而言比较低。

      TCP传输时需要确保先建立连接之后,再进行传输这样就保证了传输的可靠性。

      java中将TCP封装成了对应的类。

        ServerSocket:服务端

        Socket:客户端

      1.2TCP连接的建立与取消(三次握手与四次挥手)

        连接(三次握手):

          1.初始状态,服务器处于监听状态,主机的传输控制模块(TCB)像服务器发送连接请求,客户端进入同步已发送状态。

          2.服务器受到客服端发送的连接请求,如果同同意连接则向客户端发送确认,服务器进入同步收到状态。

          3.客户端受到确认后,继续给服务器发送确认报文,客户端进入已连接状态。

          后续服务器收到客服端的确认后也进入已建立连接状态。

         建立连接后,客户端和服务器就可以愉快的发送信息了,信息发送完毕后,就要断开连接。

         断开(四次挥手):

          1.客户端发送释放报文,同时停止发送数据主动关闭TCP连接,进入终止等待状态1。

          2.服务器收到释放报文后发送确认,此时服务器进入关闭等待状态。此时客户端到服务器方向的连接就释放了。

          此时TCP进入半连接状态,服务器到客户端的连接未释放,此时服务器还可以将未发送完的数据向客户端发送。

          3.服务器没有数据向客户端发送之后,就会发出连接释放报文等待客户端确认,服务器进入最终确认状态。

          4.客户端收到服务器发送的释放报文后,向服务器发送一个确认报文,服务器进入连接关闭状态。客户端同时进入时间等待(TIME-WAIT)状态。

            此时连接还没有被释放掉。客户端会等待2MSL的时间,然后进入连接关闭状态。至此连接断开完成。

           

          每一条TCP的连接唯一的被两个通信两端的两个端点表示,也就是是四元组(源IP,源端口,目的IP,目的端口),

          而不是单纯的用一个IP地址和端口区别。

          也就意味着一个TCP可以建立多个连接,比如服务器IP是127.0.0.1,端口是8888;

          例如客户端一:127.0.0.1:3389

            客户端二:127.0.0.1:3390

            客户端三:127.0.0.1:3390

          三个连接对应的四元组

          TCP 127.0.0.1:3889 127.0.0.1:8888 ESTABLISHED

          TCP 127.0.0.1:3890 127.0.0.1:8888 ESTABLISHED

          TCP 127.0.0.1:3891 127.0.0.1:8888 ESTABLISHED

          我们可以发现即使目的地IP和端口相同,但本地的端口不同导致整个四元组不同。

          服务器可以建立多个连接,前提是四元组不同。连接中无法出现两个四元组相同的连接。

           TCP可以连接多个客户端,为其每一个客户端创建一个Socket,Socket不同代表不同连接。

          客户端服务器之间通过Socket通信,服务器加上多线程为每一个Socket分配一个线程就可实现并发处理。

          

          参考:1、计算机网络(第四版) 谢希仁编著。

             2、https://www.cnblogs.com/Andya/p/7272462.html

               3、https://blog.csdn.net/sssnmnmjmf/article/details/68486261

    二、ServerSocket

        ServerSocket(int port)//创建绑定到指定端口的服务器套字节。

        默认绑定的IP地址是本地的IP地址。

        例如我这里是在个人电脑上面运行,绑定的地址就是当前主机的IP地址。

        当前IP地址可按win键+r输入cmd,然后输入ipconfig -all查看以太网适配器的IPv4地址,后面带有首选的。

        

        也可以认为是绑定到127.0.0.1上,因为当前C/S都是在一台电脑上运行都属于本机访问,

        所以本地测试使用的回环地址(127.0.0.1和本机IP(192.168.190.1)都可以。

        

         2.主要方法

        Socket accept()//监听要对当前对象IP上指定端口的连接,如果发现有连接请求则连接它。

        例如客户端发送一个连接请求到当前服务端的对应端口,则建立客户端与服务端的连接。

        这个监听是一个阻塞式的监听,意思就是说如果没有建立连接的话当前进程就不会继续向下运行。

        成功建立连接后,会返回一个Socket对象,而Socket对象中有获取输入输出流的方法,这时就在

        客户端,服务端之间建立输入输出流管道,两者就可以通过这个管道通信。

    三、Socket

         1.构造方法:

        Socket(InetAddress address, int port)

        Socket(String host, int port)
        //创建套字节,并将其绑定到指定的(IP|域名)上的指定端口。

        2.主要方法:

        InputStream getInputStream()//返回当前Socket对象的输入流

        OutputStream getOutputStream()//返回当前Socket对象的输出流

    四、例子

      Server:

    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    public class Server {
        public static void main(String[] args) throws UnknownHostException, IOException {
            String msg = "欢迎连接到Server!";
            ServerSocket server = new ServerSocket(8880);//绑定到本地IP的8880端口
            Socket socket = server.accept();//阻塞式接收,接收成功建立连接管道
            //连接管道的输出流,即对连接对象(客户端)进行输出。
            BufferedWriter bos = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"));
            bos.write(msg);//服务器将指定内容发给客户端
            bos.newLine();
            bos.flush();
        }
    }

    Client:

    import java.io.BufferedReader;
    import java.io.FileDescriptor;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintStream;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    public class Client {
        public static void main(String[] args) throws UnknownHostException, IOException {
            Socket client = new Socket("192.168.190.1",8880);//向指定IP地址的指定端口进行连接
        //    Socket client = new Socket("127.0.0.1",8880);//使用127.0.0.1和使用192.168.190.1都可以完成通信
            //连接成功后,获取连接管道的输入流,即对服务器写入内容进行读取
            BufferedReader isr = new BufferedReader(new InputStreamReader(client.getInputStream(),"UTF-8"));
            String re = isr.readLine();//读取内容
            System.out.println(re);
        }
    }
    运行结果:
    欢迎连接到Server!

    先运行Server会进行阻塞式接收,没有建立连接前后面的语句都不会执行。

    然后运行Client建立连接后,Server向连接管道中写入数据,Client向连接管道中读取数据。

    最后将内容显示到控制台。 

    五、简易聊天室

    下面结合多线程,和网络编程实现一个简易聊天室。

    客户端先将消息发送到服务器,服务接收消息后转发给其他客户端。

    每个客户端是一个线程。

    基本流程:

    1.A客户端读取键盘输入数据,并将其发送到服务器。

    2.服务器与A客户端建立连接后,将A客户端放入一个容器,同时将A客户端发送的消息,转发给容器中除A客户端之外的所有客户端。

     服务器中为每一个Socket分配一个线程,就可以实现并发转发所有聊天消息。

    3.发送给其他客户端后,其他客户端会读取服务器发送的内容并显示到自己控制台。

    Send:读取键盘输入内容并将其发送给服务器

    import java.io.BufferedReader;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.Reader;
    import java.net.Socket;
    
    public class Send implements Runnable {
        private boolean Running = true;
        private DataInputStream dis;//用于读取service返回的消息
        private DataOutputStream dos;//用于向server发送消息
        private BufferedReader br;
        public Send(){
            
        }
        
        public Send(Socket client){
            try {
                dis = new DataInputStream(client.getInputStream());
                dos = new DataOutputStream(client.getOutputStream());
            } catch (IOException e) {
                // TODO Auto-generated catch block
                System.err.println("初始化连接失败!");
                Running = false;
                try {
                    dis.close();
                    dos.close();
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    System.err.println("关闭异常!");
                }
                
            }
            
        }
        //读取键盘输入信息并返回
        private String reciver(){
            String msg=null;
            br = new BufferedReader(new InputStreamReader(System.in));
            try {
                msg = br.readLine();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                System.out.println("读取用户输入异常!");
                Running = false;
                try {
                    br.close();
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
            return msg;
        }
        //将键盘输入信息发送至服务器
        private void send(){
            String msg = reciver();
            try {
                if(msg != null && !msg.equals("")){
                    dos.writeUTF(msg);
                    dos.flush();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                System.err.println("用户发送信息异常!");
                Running = false;
                try {
                    dos.close();
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
        }
        
        public void run(){
            while(Running){
                send();
            }
        }
    }

    reciver:读取服务器发送的数据

    package ChatRoom;
    
    import java.io.DataInputStream;
    import java.io.IOException;
    import java.net.Socket;
    
    public class Reciver implements Runnable{
        private boolean Running = true;
        private DataInputStream dis;
        
        public Reciver(){
            
        }
        //初始化,获取连接
        public Reciver(Socket client){
            try {
                dis = new DataInputStream(client.getInputStream());
            } catch (IOException e) {
                // TODO Auto-generated catch block
                System.err.println("Client-->Server连接失败!");
                Running = false;
                try {
                    dis.close();
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    System.err.println("关闭异常!");
                }
            }
        }
        //读取客户端发送的数据
        private String reciver(){
            String msg=null;
            try {
                msg = dis.readUTF();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                System.err.println("接受客户端消息异常!");
                Running = false;
                try {
                    dis.close();
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    System.err.println("关闭异常!");
                }
            }
            return msg;
        }
        
        public void run(){
            while(Running){
                System.out.println(reciver());
            }
        }
    }

    Client:(192.168.1.1~253) 255.255.255.0

    import java.io.IOException;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    public class Client {
    
        public static void main(String[] args) throws UnknownHostException, IOException {
            // TODO Auto-generated method stub
            Socket client = new Socket("192.168.1.254",8888);//连接服务器
                new Thread(new Send(client)).start();//读取键盘数据并发送给服务器
                new Thread(new Reciver(client)).start();//读取服务器发送回来的消息
        }
    
    }

    Server: 192.168.1.254   255.255.255.0

    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.LinkedList;
    import java.util.List;
    
    public class Server {
        static List<Server.Connect> allUser;
        public static void main(String[] args) throws IOException{
            allUser = new LinkedList<Server.Connect>();//存储客户端的容器
            ServerSocket serverSocket = new ServerSocket(8888);//设置监听端口
            while(true){//不断接受客户端的连接请求
                Socket con = serverSocket.accept();//获取服务器与客户端的Socket
           //   System.out.println(con.getPort());
                Server server = new Server();//实例化一个服务器
                Server.Connect connect = server.new Connect(con);//创建一个客户端到服务器的连接(socket)
                allUser.add(connect);//将已经连接的客户端放入容器,也可以看做将socket放入服务器
                new Thread(connect).start();//每连接一个客户端(socket)就为其开辟一条线程,一个服务器对应多个客户端。
            }
        }
        
        class Connect implements Runnable{//
            private boolean Running = true;//运行标志位
            DataInputStream dis;
            DataOutputStream dos;
            
            public Connect(){
                
            }
            
            public Connect(Socket client){//客户端连接上服务器后的socket
                try {//初始化获取对客户的读写流
                    dis = new DataInputStream(client.getInputStream());
                    dos = new DataOutputStream(client.getOutputStream());
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    System.err.println("Server-->Client连接失败!");
                    try {
                        dos.close();
                        dis.close();
                    } catch (IOException e1) {
                        // TODO Auto-generated catch block
                        System.err.println("关闭异常");
                    }
                }
            }
            
            public String reciver(){//读取客户端发送的消息
                String msg = null; 
                try {
                    msg = dis.readUTF();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    System.err.println("获取客户端信息异常!");
                    Running = false;
                    try {
                        dis.close();
                    } catch (IOException e1) {
                        // TODO Auto-generated catch block
                        System.err.println("关闭异常");
                    }
                }
                return msg;//返回读取的消息
            }
            
            public void send(String msg){//将消息发送到输出流dos对应的客户端
                try {
                    dos.writeUTF(msg);
                    dos.flush();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    System.err.println("发送客户端信息异常!");
                    Running = false;
                    try {
                        dos.close();
                    } catch (IOException e1) {
                        // TODO Auto-generated catch block
                        System.err.println("关闭异常");
                    }
                }
                
            }
            
            public void sendOther(){//将消息发送到其它客户端,例如A客户端发送过来的消息,就发送给除A之外的客户端
                String msg = this.reciver();
                System.out.println(msg);
                for(Connect temp : allUser){//遍历存放客户端的容器
                    if(temp == this)//如果容器中当前对象时是A,就跳过这次循环,不是则将消息发送到对应的客户端。
                        continue;
                    temp.send(msg);//哪一个客户端调用就将消息发给谁,假如这里的temp是B就将调用B中的send,此时发送的输出流是向客户端B写入的。
                }
            }
    
            @Override
            public void run() {//开启多线程后服务器不断接收客户端消息,然后转发
                // TODO Auto-generated method stub
                while(Running){//运行标志位,如果中途出现读写异常则终止。
                    sendOther();
                }
            }    
        }
    }

    如果是一台电脑上测试,则将客户端中连接服务器的地址修改为127.0.0.1或localhost端口任选(大于1024即可)。

    如果是多台电脑测试,例如两台电脑(将两台电脑的网线接口用一根网线连接)。

    将其中一台电脑的IP地址修改为192.169.1.245:255.255.255.0,

    另外一台IP地址只需和其保持同一网段即可例如(192.168.1.1:255.255.255.0)。(最好禁用其余网卡)

    在192.168.1.254上先运行服务器然后运行客户端,在另外一个电脑上运行客户端。

    通过控制台输入可以实现聊天。

    如果想实现同一网段多个电脑间通信需要用到交换机连接多个电脑。

  • 相关阅读:
    企业架构研究总结(38)——TOGAF架构能力框架之架构能力建设和架构治理
    企业架构研究总结(37)——TOGAF企业连续体和工具之架构资源库及架构工具的选择
    企业架构研究总结(36)——TOGAF企业连续体和工具之企业连续体构成及架构划分
    SQL高级查询——50句查询(含答案) ---参考别人的,感觉很好就记录下来留着自己看。
    致不想奋斗的女人们
    16-Angular中的动画
    15-Angular的路由
    14-Angular供应商和自定义服务
    13-$location以及$interpolate等服务
    12-Angular的http与location
  • 原文地址:https://www.cnblogs.com/huang-changfan/p/9948419.html
Copyright © 2011-2022 走看看