258 - 基于TCP的网络编程

功能:模拟网站的登录,客户端录入账号密码,然后服务器端进行验证。

功能分解1:单向通信

功能:客户端发送一句话到服务器

客户端:

package test2_TCP;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestClien {
    public static void main(String[] args) throws IOException {
        //1、创建套接字:指定服务器的ip和端口号
        Socket s = new Socket("192.168.67.124",8888);
        //2、对于程序员来说,向外发送数据 的直观感受就是 --》利用输出流
        OutputStream os = s.getOutputStream();
        //然后写一句话String类型:
        //os.write("abc"); //发现,没有要求入String类型的write()
        //怎么办?—— (往外写数据(字符串))换一个流:
        //在它外面包一个流:DataOutputStream
        //即:
        // 利用这个OutputStream就可以向外发送数据了,但是没有直接发送String的方法
        // 所以我们又在OutputStream外面套了一个处理流:DataOutputStream
        DataOutputStream dos = new DataOutputStream(os);//包在os上
        dos.writeUTF("你好!");

        //写好后:
        //3、关闭流 + 关闭网络资源
        dos.close();
        os.close();// 只关上面的dos就可以了(关闭流)

        s.close(); //关闭网络资源
    }
}

服务器:

package test2_TCP;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestServer {
    public static void main(String[] args) throws IOException {
        //1、创建套接字:指定服务器的端口号(服务器知道自己的IP,故不用指定了)
        ServerSocket ss = new ServerSocket(8888);

        //2、等着客户端发来的信息
        //ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
        //accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
        //接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
        Socket s = ss.accept();

        //3、感受到的操作流(输入流)
        InputStream is = s.getInputStream();
        //客户端发送数据的时候使用了OutputStream处理流,服务端接收数据的时候也要配合使用
        DataInputStream dis = new DataInputStream(is);

        //4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
        String str = dis.readUTF();
        System.out.println("客户端发来的数据为:" + str);

        //5、关闭流 + 关闭网络资源
        dis.close();
        is.close();
        s.close();

        ss.close();
    }
}

测试:

(1)先开启客户端还是先开启服务器?? 先开服务器,再开启客户端

   侧面验证:先开客户端,出错:

功能分解2:双向通信

代码示例:在上面代码的基础上

服务器端:

package test2_TCP;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestServer {
    public static void main(String[] args) throws IOException {
        //1、创建套接字:指定服务器的端口号(服务器知道自己的IP,故不用指定了)
        ServerSocket ss = new ServerSocket(8888);

        //2、等着客户端发来的信息
        //ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
        //accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
        //接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
        Socket s = ss.accept();

        //3、感受到的操作流(输入流)
        InputStream is = s.getInputStream();
        //客户端发送数据的时候使用了OutputStream处理流,服务端接收数据的时候也要配合使用
        DataInputStream dis = new DataInputStream(is);

        //4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
        String str = dis.readUTF();
        System.out.println("客户端发来的数据为:" + str);

        //双向通信:
        //(再)向客户端输出一句话:---》操作流---》输出流
        OutputStream os = s.getOutputStream();
        //同样的,再套一个处理流:DataOutputStream
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeUTF("你好,我是服务器,我接收到你的请求了!");
        //注:下面要关闭对应的流dos、os
        //同理,然后是客户端的接收 —— 见客户端

        //5、关闭流 + 关闭网络资源
        //双向通信时多出来的输出流:(对应也要关闭)
        dos.close();
        os.close();

        dis.close();
        is.close();
        s.close();

        ss.close();
    }
}

客户端:

package test2_TCP;

import java.io.*;
import java.net.Socket;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestClien {
    public static void main(String[] args) throws IOException {
        //1、创建套接字:指定服务器的ip和端口号
        Socket s = new Socket("192.168.67.124",8888);
        //2、对于程序员来说,向外发送数据 的直观感受就是 --》利用输出流
        OutputStream os = s.getOutputStream();
        //然后写一句话String类型:
        //os.write("abc"); //发现,没有要求入String类型的write()
        //怎么办?—— (往外写数据(字符串))换一个流:
        //在它外面包一个流:DataOutputStream
        //即:
        // 利用这个OutputStream就可以向外发送数据了,但是没有直接发送String的方法
        // 所以我们又在OutputStream外面套了一个处理流:DataOutputStream
        DataOutputStream dos = new DataOutputStream(os);//包在os上
        dos.writeUTF("你好!");

        //双向通信:
        //接收服务端的回话 : —— 同样利用输入流
        InputStream is = s.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        String str = dis.readUTF();
        System.out.println("服务器端对我说:" + str);
        //注:对应的流在下面要关闭

        //写好后:
        //3、关闭流 + 关闭网络资源

        //双向通信-补加:流的关闭
        dis.close();
        is.close();

        dos.close();
        os.close();// 只关上面的dos就可以了(关闭流)

        s.close(); //关闭网络资源
    }
}

注意:关闭防火墙

功能分解3:对象流传送

  —— 登陆的验证

封装的User类:

package test3_TCP_2;

import java.io.Serializable;

/**
 * @Auther: zhoulz
 * @Description: test3_TCP_2
 * @version: 1.0
 */
public class User implements Serializable {
    //Alt + Enter  —— 快捷生成下面的 serialVersionUID
    private static final long serialVersionUID = -4001204060434082179L;

    private String name;
    private String pwd;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public User(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }
}

客户端:

package test3_TCP_2;

import javax.xml.transform.Source;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestClien {
    public static void main(String[] args) throws IOException {
        //1、创建套接字:指定服务器的ip和端口号
        Socket s = new Socket("192.168.67.124",8888);

        //录入用户的账号和密码:
        Scanner sc = new Scanner(System.in);
        System.out.println("请录入您的账号:");
        String name = sc.next();
        System.out.println("请录入您的密码:");
        String pwd = sc.next();
        //将账号和密码封装为一个User的对象
        User user = new User(name,pwd);

        //2、对于程序员来说,向外发送数据 的直观感受就是 --》利用输出流
        OutputStream os = s.getOutputStream();
        //DataOutputStream dos = new DataOutputStream(os);//包在os上
        //此时,再往外面写的时候,可以在外面包一个流(这个流就不能是上面的DataOutputStream了)
        //而是:
        ObjectOutputStream oos =new ObjectOutputStream(os);
        //直接有往外写对象的方法:
        oos.writeObject(user);

       /* dos.writeUTF(name);
        dos.writeUTF(pwd);
        //分别写,可以,但是如果很多的话,在这么写就不方便了。
        //此时,可以把上面的(账号和密码),封装成一个对象
        //然后通过对象流向外写一个对象就行了(无论对象里有多少个属性)
        //即:抽取出一个类——User*/

        //双向通信:
        //接收服务端的回话 : —— 同样利用输入流
        InputStream is = s.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        /*String str = dis.readUTF();
        System.out.println("服务器端对我说:" + str);
        //注:对应的流在下面要关闭*/

        //客户端接收验证结果:
        boolean b = dis.readBoolean();
        if (b){
            System.out.println("恭喜,登陆成功!");
        }else {
            System.out.println("对不起,登陆失败");
        }

        //写好后:
        //3、关闭流 + 关闭网络资源

        //双向通信-补加:流的关闭
        dis.close();
        is.close();

        //dos.close();
        oos.close();
        os.close();// 只关上面的dos就可以了(关闭流)

        s.close(); //关闭网络资源
    }
}

服务器端:

package test3_TCP_2;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestServer {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1、创建套接字:指定服务器的端口号(服务器知道自己的IP,故不用指定了)
        ServerSocket ss = new ServerSocket(8888);

        //2、等着客户端发来的信息
        //ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
        //accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
        //接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
        Socket s = ss.accept();

        //3、感受到的操作流(输入流)
        InputStream is = s.getInputStream();
        /*//客户端发送数据的时候使用了OutputStream处理流,服务端接收数据的时候也要配合使用
        DataInputStream dis = new DataInputStream(is);*/
        //上面客户端发送的是String类型的数据时,下面发送了一个对象:
        //客户端发送的是一个对象了,则接收用:
        ObjectInputStream ois = new ObjectInputStream(is);

        //4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
        /*String str = dis.readUTF();
        System.out.println("客户端发来的数据为:" + str);*/

        //读取的为一个对象:(则需要将其装换为对象对应的类型,然后去接收)
        User user = (User)(ois.readObject());

        //还有,
        //对对象进行验证:
        boolean flag = false;
        if (user.getName().equals("娜娜") && user.getPwd().equals("112233")){
            flag = true;
        }
        //然后,下面将验证的结果返回给客户端:

        /*//双向通信:
        //(再)向客户端输出一句话:---》操作流---》输出流
        OutputStream os = s.getOutputStream();
        //同样的,再套一个处理流:DataOutputStream
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeUTF("你好,我是服务器,我接收到你的请求了!");
        //注:下面要关闭对应的流dos、os
        //同理,然后是客户端的接收 —— 见客户端*/

        //双向通信:
        //(再)向客户端输出结果:---》操作流---》输出流
        OutputStream os = s.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        //这里,可以再用DataOutputStream,因为其里面有一个可以写布尔类型的方法
        dos.writeBoolean(flag);

        //5、关闭流 + 关闭网络资源
        //双向通信时多出来的输出流:(对应也要关闭)
        dos.close();
        os.close();

        //dis.close();
        ois.close();
        is.close();

        s.close();
        ss.close();
    }
}

如果不对 User进行序列化,则会出现如下错误:

结果报错:User 没有序列化

即:IO流 —— 序列化和反序列化问题:

想通过IO流或者网络传送(数据)的话,就必须得进行序列化和反序列化。

功能分解4:加入完整的处理异常方式

 客户端:

package test4_TCP_3_try_catch;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestClien {
    public static void main(String[] args) {
        //1、创建套接字:指定服务器的ip和端口号
        Socket s = null;
        OutputStream os = null;//提出来后,将前面的如OutputStream 删掉
        ObjectOutputStream oos = null;
        InputStream is = null;
        DataInputStream dis = null;

        try {
            s = new Socket("192.168.67.124",8888);

            //下面的代码都放到try中去:

            //录入用户的账号和密码:
            Scanner sc = new Scanner(System.in);
            System.out.println("请录入您的账号:");
            String name = sc.next();
            System.out.println("请录入您的密码:");
            String pwd = sc.next();
            //将账号和密码封装为一个User的对象
            User user = new User(name,pwd);

            //2、对于程序员来说,向外发送数据 的直观感受就是 --》利用输出流
            os = s.getOutputStream();
            //DataOutputStream dos = new DataOutputStream(os);//包在os上
            oos =new ObjectOutputStream(os);
            //直接有往外写对象的方法:
            oos.writeObject(user);

            //双向通信:
            //接收服务端的回话 : —— 同样利用输入流
            is = s.getInputStream();
            dis = new DataInputStream(is);

            //客户端接收验证结果:
            boolean b = dis.readBoolean();
            if (b){
                System.out.println("恭喜,登陆成功!");
            }else {
                System.out.println("对不起,登陆失败");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭流的代码要放到finally中:
            //写好后:
            //3、关闭流 + 关闭网络资源

            //双向通信-补加:流的关闭
            try {
                if (dis != null){
                    dis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (is != null){
                    is.close(); //这几个流的关闭—报错的原因:作用域
                                //作用域都在try中,只在try中有效
                                //依次提出来即可
                                //注意:提出来要记得赋初始值
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            //dos.close();
            try {
                if (oos != null){
                    oos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (os != null){
                    os.close();// 只关上面的dos就可以了(关闭流)
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (s != null){
                    s.close(); //关闭网络资源
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            //最后,流和资源不报错了,但是后面的close()又报错了
            //因为关闭的时候可能会有异常,所以要加上try-catch
            //注意:要分别加 —— 不用一个try-catch是防止第一个流关闭失败导致后面的流都没有能关闭

            //最最后,为了防止空指针异常,分别加上一个if条件判断
        }
    }
}

服务器端:

package test4_TCP_3_try_catch;

import jdk.nashorn.internal.ir.annotations.Ignore;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestServer {
    public static void main(String[] args) {
        //1、创建套接字:指定服务器的端口号(服务器知道自己的IP,故不用指定了)
        ServerSocket ss = null;
        Socket s = null;
        InputStream is = null;
        ObjectInputStream ois = null;
        OutputStream os = null;
        DataOutputStream dos = null;

        try {
            ss = new ServerSocket(8888);
            //2、等着客户端发来的信息
            s = ss.accept();

            //3、感受到的操作流(输入流)
            is = s.getInputStream();
            //客户端发送的是一个对象了,则接收用:
            ois = new ObjectInputStream(is);

            //4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
            /*String str = dis.readUTF();
             System.out.println("客户端发来的数据为:" + str);*/

            //读取的为一个对象:(则需要将其装换为对象对应的类型,然后去接收)
            User user = (User)(ois.readObject());
            //有异常,继续try-catch —— 选第一个

            //还有,
            //对对象进行验证:
            boolean flag = false;
            if (user.getName().equals("娜娜") && user.getPwd().equals("112233")){
                flag = true;
            }
            //然后,下面将验证的结果返回给客户端:

            //双向通信:
            //(再)向客户端输出结果:---》操作流---》输出流
            os = s.getOutputStream();
            dos = new DataOutputStream(os);
            //这里,可以再用DataOutputStream,因为其里面有一个可以写布尔类型的方法
            dos.writeBoolean(flag);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            //5、关闭流 + 关闭网络资源
            //双向通信时多出来的输出流:(对应也要关闭)
            try {
                if (dos != null){
                    dos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (os != null){
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            //dis.close();
            try {
                if (ois != null){
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (is != null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (s != null){
                    s.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (ss != null){
                    ss.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

功能分解5:多线程接收用户请求

遗留问题上面的服务器只针对一个请求服务,之后服务器就关闭了(程序自然结束了)

现在需要解决:服务器必须一直在监听 ,一直开着,等待客户端的请求。

在当前代码中,客户端不用动了(User类也不动)

更改服务器代码:

首先,增加了一个服务器线程类ServerThread

package test5_TCP_4_thread;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Auther: zhoulz
 * @Description: test5_TCP_4_thread
 * @version: 1.0
 */
public class ServerThread extends Thread{//线程:专门处理客户端的请求
    InputStream is = null;
    ObjectInputStream ois = null;
    OutputStream os = null;
    DataOutputStream dos = null;

    Socket s = null;
    //构造器
    public ServerThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        //super.run();

        //把服务器中监听(即第2步开始)的操作复制过来,放到线程中处理

        try {
            //2、等着客户端发来的信息

            //3、感受到的操作流(输入流)
            is = s.getInputStream();  //s 还在报错,所以要想办法把Socket s 传进来
                                      //利用构造器,见上面
            //客户端发送的是一个对象了,则接收用:
            ois = new ObjectInputStream(is);

            //4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
            /*String str = dis.readUTF();
             System.out.println("客户端发来的数据为:" + str);*/

            //读取的为一个对象:(则需要将其装换为对象对应的类型,然后去接收)
            User user = (User)(ois.readObject());
            //有异常,继续try-catch —— 选第一个

            //还有,
            //对对象进行验证:
            boolean flag = false;
            if (user.getName().equals("娜娜") && user.getPwd().equals("112233")){
                flag = true;
            }
            //然后,下面将验证的结果返回给客户端:

            //双向通信:
            //(再)向客户端输出结果:---》操作流---》输出流
            os = s.getOutputStream();
            dos = new DataOutputStream(os);
            //这里,可以再用DataOutputStream,因为其里面有一个可以写布尔类型的方法
            dos.writeBoolean(flag);
        }catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            //5、关闭流 + 关闭网络资源
            //双向通信时多出来的输出流:(对应也要关闭)
            try {
                if (dos != null){
                    dos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (os != null){
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            //dis.close();
            try {
                if (ois != null){
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (is != null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服务器端:

package test5_TCP_4_thread;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Auther: zhoulz
 * @Description: test2_TCP
 * @version: 1.0
 */
public class TestServer {
    public static void main(String[] args) {
        System.out.println("服务器启动了");
        //1、创建套接字:指定服务器的端口号
        ServerSocket ss = null;
        Socket s = null;

        int count = 0; //定义一个计数器,用来计数 客户端的请求

        try {
            ss = new ServerSocket(8888);
            while (true){ // 加入死循环,服务器一直监听客户端是否发送数据
                s = ss.accept(); //监听
                //每次过来的客户端的请求,靠 线程处理:
                //故创建一个线程(传入s),并启动
                new ServerThread(s).start();
                count++;
                //输入请求的客户端的信息:
                System.out.println("当前是第"+count+"个用户访问我们的服务器,对应的用户是:"+s.getInetAddress());
            }

        } catch (IOException e) { //ClassNotFoundException 放在ServerThread中处理了,这里要删除
            e.printStackTrace();
        }/*finally {
            //5、关闭流 + 关闭网络资源
            //双向通信时多出来的输出流:(对应也要关闭)

            //因为服务器要一直监听,所以不用关闭,所以这部分代码不需要了
            try {
                if (s != null){
                    s.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (ss != null){
                    ss.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }*/
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值