美文网首页
Java 基础 49 TCP协议

Java 基础 49 TCP协议

作者: 小熊先生很不开心 | 来源:发表于2018-03-06 08:10 被阅读29次

    1.1 TCP 协议简介

      TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。

      区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。

      而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。

      在JDK中提供了两个类用于实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端。

      通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。

    1.2 ServerSocket

      通过前面的学习知道,在开发TCP程序时,首先需要创建服务器端程序。JDK的java.net包中提供了一个ServerSocket类,该类的实例对象可以实现一个服务器段的程序。通过查阅API文档可知,ServerSocket类提供了多种构造方法,接下来就对ServerSocket的构造方法进行逐一地讲解。

    ServerSocket(int port) 
              创建绑定到特定端口的服务器套接字。
    

      使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)。

      接下来学习一下ServerSocket的常用方法,如表所示。

     Socket accept() 
              侦听并接受到此套接字的连接。 
        
    
     InetAddress getInetAddress() 
              返回此服务器套接字的本地地址。 
    

      ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept()方法才会返回一个Scoket对象用于和客户端实现通信,程序才能继续向下执行。

    1.3 Socket

      讲解了ServerSocket对象可以实现服务端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此JDK提供了一个Socket类,用于实现TCP客户端程序。

      通过查阅API文档可知Socket类同样提供了多种构造方法,接下来就对Socket的常用构造方法进行详细讲解。

    Socket(String host, int port) 
              创建一个流套接字并将其连接到指定主机上的指定端口号。
    

      使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。

    Socket(InetAddress address, int port) 
              创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
    
    

      该方法在使用上与第二个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。

      在以上Socket的构造方法中,最常用的是第一个构造方法。

    接下来学习一下Socket的常用方法,如表所示。

    方法声明 功能描述
    int getPort() 该方法返回一个int类型对象,该对象是Socket对象与服务器端连接的端口号
    InetAddress getLocalAddress() 该方法用于获取Socket对象绑定的本地IP地址,并将IP地址封装成InetAddress类型的对象返回
    void close() 该方法用于关闭Socket连接,结束本次通信。在关闭socket之前,应将与socket相关的所有的输入/输出流全部关闭,这是因为一个良好的程序应该在执行完毕时释放所有的资源
    InputStream getInputStream() 该方法返回一个InputStream类型的输入流对象,如果该对象是由服务器端的Socket返回,就用于读取客户端发送的数据,反之,用于读取服务器端发送的数据
    OutputStream getOutputStream() 该方法返回一个OutputStream类型的输出流对象,如果该对象是由服务器端的Socket返回,就用于向客户端发送数据,反之,用于向服务器端发送数据

      在Socket类的常用方法中,getInputStream()和getOutStream()方法分别用于获取输入流和输出流。当客户端和服务端建立连接后,数据是以IO流的形式进行交互的,从而实现通信。

      接下来通过一张图来描述服务器端和客户端的数据传输,如下图所示。

    数据交互

    1.4 TCP协议实现

    1.4.1案例代码

    package com.itheima_04;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    
    /*
     * 使用TCP协议发送数据
            创建发送端Socket对象(创建连接)
            获取输出流对象
            发送数据
            释放资源
            
        Socket(InetAddress address, int port) 
        Exception in thread "main" java.net.ConnectException: Connection refused: connect
    
     */
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
            //创建发送端Socket对象(创建连接)
            Socket s = new Socket(InetAddress.getByName("itheima"),10086);
            //获取输出流对象
            OutputStream os = s.getOutputStream();
            //发送数据
            String str = "hello tcp,im comming!!!";
            os.write(str.getBytes());
            //释放资源
            //os.close();
            s.close();
        }
    }
    
    
    package com.itheima_04;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /*
     * 使用TCP协议接收数据
            创建接收端Socket对象
            监听(阻塞)
            获取输入流对象
            获取数据
            输出数据
            释放资源
            
        ServerSocket:接收端,服务端Socket
        ServerSocket(int port) 
        Socket accept() 
     
     */
    public class ServerDemo {
        public static void main(String[] args) throws IOException  {
            //创建接收端Socket对象
            ServerSocket ss = new ServerSocket(10086);
            //监听(阻塞)
            Socket s = ss.accept();
            //获取输入流对象
            InputStream is = s.getInputStream();
            //获取数据
            byte[] bys = new byte[1024];
            int len;//用于存储读到的字节个数
            len = is.read(bys);
            //输出数据
            InetAddress address = s.getInetAddress();
            System.out.println("client ---> " + address.getHostName());
            System.out.println(new String(bys,0,len));
            //释放资源
            s.close();
            //ss.close();
        }
    }
    

    1.5 TCP相关案例

    使用TCP协议发送数据,服务端将接收到的数据转换成大写返回给客户端

    package com.itheima_05;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    
    /*
        需求:使用TCP协议发送数据,并将接收到的数据转换成大写返回
        
        客户端发出数据
        服务端接收数据
        服务端转换数据
        服务端发出数据
        客户端接收数据
        
     */
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
            //创建客户端Socket对象
            Socket s = new Socket(InetAddress.getByName("itheima"),10010);
            //获取输出流对象
            OutputStream os = s.getOutputStream();
            //发出数据
            os.write("tcp,im comming again!!!".getBytes());
    
            
            //获取输入流对象
            InputStream is = s.getInputStream();
            byte[] bys = new byte[1024];
            int len;//用于存储读取到的字节个数
            //接收数据
            len = is.read(bys);
            //输出数据
            System.out.println(new String(bys,0,len));
            
            //释放资源
            s.close();
            
        }
    }
    
    
    
    
    package com.itheima_05;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            //创建服务端Socket对象
            ServerSocket ss = new ServerSocket(10010);
            //监听
            Socket s = ss.accept();
            //获取输入流对象
            InputStream is = s.getInputStream();
            //获取数据
            byte[] bys = new byte[1024];
            int len;//用于存储读取到的字节个数
            len = is.read(bys);
            String str = new String(bys,0,len);
            //输出数据
            System.out.println(str);
            //转换数据
            String upperStr = str.toUpperCase();
            //获取输出流对象
            OutputStream os = s.getOutputStream();
            //返回数据(发出数据)
            os.write(upperStr.getBytes());
            
            //释放资源
            s.close();
            //ss.close();//服务端一般不关闭
        }
    }
    
    

    1.5.1 案例代码

    • 客户端:
      • 提示用户输入用户名和密码,将用户输入的用户名和密码发送给服务端
      • 接收服务端验证完用户名和密码的结果
    • 服务端:
      • 接收客户端发送过来的用户名和密码
      • 如果用户名不是itheima或者 密码不是123456,就向客户端写入”登录失败”
        否则向客户端写入登录成功
    package com.itheima_06;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.Socket;
    
    /*
     * 模拟用户登录
     */
    public class ClientTest {
        public static void main(String[] args) throws  IOException  {
            //创建客户端Socket对象
            //Socket s = new Socket(InetAddress.getByName("itheima"),8888);
            Socket s = new Socket("itheima",8888);
            
            //获取用户名和密码
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("请输入用户名:");
            String username = br.readLine();
            System.out.println("请输入密码:");
            String password = br.readLine();
            
            
            //获取输出流对象
            //BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            PrintWriter out = new PrintWriter(s.getOutputStream(),true);
            //写出数据
            out.println(username);
            out.println(password);
            
            //获取输入流对象
            BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream()));
            //获取服务器返回的数据
            String result = serverBr.readLine();
            System.out.println(result);
            //释放资源
            s.close();
        }
    }
    
    
    package com.itheima_06;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class ServerTest {
        public static void main(String[] args) throws IOException {
            //创建服务器端Socket对象
            ServerSocket ss = new ServerSocket(8888);
            //监听
            Socket s = ss.accept();
            //获取输入流对象
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            //获取用户名和密码
            String username = br.readLine();
            String password = br.readLine();
            //判断用户名和密码是否正确
            boolean flag = false;
            if("itheima".equals(username) && "123456".equals(password)) {
                flag = true;
            }
            //获取输出流对象
            PrintWriter out = new PrintWriter(s.getOutputStream(),true);
            
            //返回判断信息
            if(flag) {
                out.println("登陆成功");
            }
            else {
                out.println("登陆失败");
            }
            //释放资源
            s.close();
            //ss.close();//服务器一般不关闭
        }
    }
    

    1.5.2 案例代码

    将用户名和密码封装到一个User类中,提供对应的构造方法和getter/setter方法
    新建一个UserDB类里面定义一个集合,在集合中添加以下User对象

    • new User("zhangsan","123456");

    • new User("lisi","654321");

    • new User("itheima","itheima");

    • new User("admin","password");

    • 客户端:

      • 提示用户输入用户名和密码,将用户输入的用户名和密码发送给服务端
      • 接收服务端验证完用户名和密码的结果
    • 服务端:

      • 服务端将客户端发送过来的用户名密码封装成User对象
      • 集合中如果包括这个User对象,想客户端写入” 登录成功”
        否则向客户端写入”登录失败”
    package com.itheima_07;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.Socket;
    
    /*
     * 模拟用户登录改写(面向对象版本)
     */
    public class ClientTest {
        public static void main(String[] args) throws  IOException  {
            //创建客户端Socket对象
            Socket s = new Socket("itheima",8888);
            
            //获取用户名和密码
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("请输入用户名:");
            String username = br.readLine();
            System.out.println("请输入密码:");
            String password = br.readLine();
            
            
            //获取输出流对象
            PrintWriter out = new PrintWriter(s.getOutputStream(),true);
            //写出数据
            out.println(username);
            out.println(password);
            
            //获取输入流对象
            BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream()));
            //获取服务器返回的数据
            String result = serverBr.readLine();
            System.out.println(result);
            //释放资源
            s.close();
        }
    }
    
    package com.itheima_07;
    
    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.List;
    
    public class ServerTest {
        public static void main(String[] args) throws IOException {
            //创建服务器端Socket对象
            ServerSocket ss = new ServerSocket(8888);
            //监听
            Socket s = ss.accept();
            //获取输入流对象
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            //获取用户名和密码
            String username = br.readLine();
            String password = br.readLine();
            //判断用户名和密码是否正确
            boolean flag = false;
        
            /*if("itheima".equals(username) && "123456".equals(password)) {
                flag = true;
            }*/
            
            List<User> users = UserDB.getUsers();
            User user = new User(username,password);
            if(users.contains(user)) {
                //匹配成功
                flag = true;
            }
            
            
            //获取输出流对象
            PrintWriter out = new PrintWriter(s.getOutputStream(),true);
            
            //返回判断信息
            if(flag) {
                out.println("登陆成功");
            }
            else {
                out.println("登陆失败");
            }
            //释放资源
            s.close();
            //ss.close();//服务器一般不关闭
        }
    }
    

    相关文章

      网友评论

          本文标题:Java 基础 49 TCP协议

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