网络编程

我们之所以可以上网,就是因为我们请求远程服务器上面的信息,然后服务器将相应的信息发送给了我们,在一个局域网之内,大家可以通过飞秋联系并且发送文件,这些都和今天要写的这篇文章有关,那就是网络编程,当然,中间主要介绍的还是Java中的网络编程是如何实现的。

1.传输协议

在介绍网络编程的时候,需要先介绍一下传输协议,传输协议就是通信双方事先约定好彼此以何种方式传输数据,当然,不同的传输协议所传输数据的格式也是不一样的,会根据它们具体的特点而有所不同,这里我们不用了解的那么深入,只要知道一下就好了,这里想说的两种传输协议就是: UDPTCP

UDP 协议对于通信双方是无需连接的,发送方直接将数据发送给接收方,而不用管接收方是否接收到,很显然,这样传输速度快,但是却无法保证数据的完整性,我们用QQ进行视频通话时,就是使用的 UDP 协议,通话时视频数据即使有丢失,只要不是丢失的很多,对我们正常通话都不会有很大影响,因为我们视频时部分视频画面没有实时显示也没有多大关系。

TCP 协议对于通信双方则是要求有连接的,双方必须通过”三次握手”来确认连接已连通,之后才会发送数据,在发送数据的过程中,如果有部分数据的丢失,TCP 也会要求将接收方未收到的数据重新发送,直到所有的数据接受完成,很显然,这种协议可以保证数据的完整性,但很显然效率不是很高。

传输协议:
    UDP:发送数据速度快,但数据完整性不能保证
    TCP:能够保证数据的完整性,但发送数据的速度不快

2.网络通信的三要素

当我们的服务器端和客户端进行数据交互的时候,必须要满足哪些必要条件呢?第一,就是要知道彼此在哪一台主机上面,只有这样才能知道将数据发送到哪里给彼此;第二,知道了应该将数据发送到哪台主机上面之后,但是像我们的电脑,会有QQ、Chrome、迅雷,还有酷狗这么多的软件应用,那怎么知道将数据发送到哪一个应用呢?这里要说明的就是每一个程序应用都会占用一个端口号,想和这个应用进行数据交换,必须要通过这个端口号,所以第二点就是端口号了;第三,那通信双方会采取怎样的方式收发数据呢?如果彼此的对应方式不匹配,那就算接受到了数据也是不会明白其中的含义的啊,所以第三点那就是协议了。

网络通信的三要素:
    1.IP地址
    2.端口号
    3.通信协议

可以将上面的交换数据过程对比为我们生活中的相互发送快递,发送快递,我们必须要知道接受者的地址,那这就是IP地址了,知道了地址之后,想要送上门那就必须要知道门牌号了,这就相当于是端口号,最后一个,就是发送快递时会选择哪种快递商,只有事先明确了会发送哪种快递,才能接受对应的快件啊,这就是协议了。

2.1 Java中表示IP地址

Java中要表示IP地址的话可以使用 InetAddress 这个类,要获得这个类的类对象可以使用 public static InetAddress getByName(String host) 方法,这里既可以传入主机名,也可以传入IP地址。

InetAddress address = InetAddress.getByName("WIN-SNN7GVNNB0F") 通过主机名去得到IP地址
InetAddress address = InetAddress.getByName("127.0.0.1");//ip地址是唯一的

这两种方法都可以获得IP地址,127.0.0.1代表的是本机IP地址。

3.UPD协议实现

下面写一段程序来说明使用 UDP 协议是如何进行数据发送和接受的,需要注意的是,如果想自己运行下面的代码,需要自己根据实际情况修改IP地址和端口号。

发送端:

public class UdpSendDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();

        String info = "study hard";
        byte[] bys = info.getBytes();
        int length = bys.length;
        InetAddress toAddress = InetAddress.getByName("127.0.0.1");
        int port = 9022;
        DatagramPacket dp = new DatagramPacket(bys, length, toAddress, port);

        socket.send(dp);
        socket.close();
    }
}

接收端:

public class UdpReceiveDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket receiveSocket = new DatagramSocket(9022);

        byte[] bys = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bys, bys.length);

        receiveSocket.receive(dp);

        InetAddress sendAddress = dp.getAddress();
        byte[] data = new byte[1024];
        data = dp.getData();
        int length = dp.getLength();

        System.out.println("sender:" + sendAddress);
        System.out.println("data:" + (new String(data, 0, length)));
        receiveSocket.close();

        System.out.println("--------");
        System.out.println(new String(bys, 0, length));
    }
}

运行程序的时候需要先运行接收端等待接受数据,再接受发送端发送数据,不然如果先运行发送端的话,由于 UDP 是无连接的协议,发送端会直接发送数据,而不管接收端收到了没有,那样我们就得不到预想的效果了。

4.TCP协议实现

4.1 使用TCP进行收发数据

先像上面UDP的例子一样,实现一个最简单的功能,实现基本的数据收发。

客户端:

public class TcpClientDemo {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket(InetAddress.getByName("127.0.0.1"),9046);

        OutputStream os = s.getOutputStream();

        String data = "something is coming!";
        byte[] bys = data.getBytes();
        os.write(bys);

        os.close();
        s.close();
    }
}

服务端:

public class TcpServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(9046);
        Socket s = ss.accept();

        InetAddress sendAddress = s.getInetAddress();

        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len;
        len = is.read(bys);

        System.out.println(sendAddress.getHostName());
        System.out.println(sendAddress);
        System.out.println(new String(bys, 0, len));

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

还是需要提醒一句,程序中的IP地址和端口号都要根据自己的实际情况进行修改,否则可能运行不成功,其中端口号的选择范围可以是0到65535,但是前面0到1023范围的端口号属于系统可能已经在使用的端口号,因此我们在使用的时候,最好选择其它的,这样就不会因为冲突而发生错误。

4.2 用TCP协议发送数据并将接受到的数据转换成大写并且返回

客户端:

public class TcpClientDemo2 {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8888);
        OutputStream os = s.getOutputStream();

        String data = "we are here!";
        byte[] bys = data.getBytes();
        os.write(bys);

        InputStream is = s.getInputStream();

        int len = is.read(bys);
        String backStr = new String(bys,0,len);
        System.out.println("backfeed:"+backStr);
        s.close();
    }
}

服务端:

public class TcpServerDemo2 {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(9056);

        Socket s = ss.accept();

        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);

        String str = new String(bys, 0, len);
        System.out.println(str);

        String newStr = str.toUpperCase();
        bys = newStr.getBytes();
        OutputStream os = s.getOutputStream();
        os.write(bys);

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

4.3 模拟用户登录

首先先确定学生类:

public class Student {

    private String username;
    private String password;

    public Student() {
        super();
    }

    public Student(String username, String password) {
        super();
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((password == null) ? 0 : password.hashCode());
        result = prime * result
                + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (password == null) {
            if (other.password != null)
                return false;
        } else if (!password.equals(other.password))
            return false;
        if (username == null) {
            if (other.username != null)
                return false;
        } else if (!username.equals(other.username))
            return false;
        return true;
    }
}

在程序运行的时候先预设几个学生对象,作为以后验证登陆的数据:

public class StudentDB {
    private static List<Student> list = new ArrayList<Student>();

    static{
        list.add(new Student("卡卡西","绝版天堂"));
        list.add(new Student("带土","轻轻风铃"));
        list.add(new Student("琳","暗夜烤鱼"));
        list.add(new Student("水门","黄色闪光"));
    }

    public static List<Student> getUsers(){
        return list;
    }
}

接下来才是客户端与服务端的程序:

客户端:

public class TcpClientDemo3 {
    public static void main(String[] args) throws IOException {
        //建立与服务器的连接
        Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 9021);

        //由用户输入用户名和密码进行登录
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("请输入用户名:");
        String username = br.readLine();
        System.out.print("请输入密码:");
        String password = br.readLine();

        //向服务器写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                s.getOutputStream()));
        bw.write(username);
        bw.newLine();
        bw.flush();
        bw.write(password);
        bw.newLine();
        bw.flush();

        //接受服务器传来的数据
        BufferedReader br2 = new BufferedReader(new InputStreamReader(
                s.getInputStream()));
        String result = br2.readLine();
        System.out.println(result);

        s.close();
    }
}

服务端:

public class TcpServerDemo3 {
    public static void main(String[] args) throws IOException {
        // 监听端口,建立与客户端的连接
        ServerSocket ss = new ServerSocket(9021);

        Socket s = ss.accept();
        // 接受客户端的数据
        BufferedReader br = new BufferedReader(new InputStreamReader(
                s.getInputStream()));
        String username = br.readLine();
        String password = br.readLine();
        // 验证登录
        boolean flag = false;
        // if ("admin".equals(username) && "123".equals(password)) {
        // flag = true;
        // }
        List<Student> users = StudentDB.getUsers();
        Student stu = new Student(username, password);
        if (users.contains(stu)) {
            flag = true;
        }
        String result = null;
        if (flag) {
            result = "登陆成功";
        } else {
            result = "登录失败";
        }

        // 向客户端写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                s.getOutputStream()));
        bw.write(result);
        bw.newLine();
        bw.flush();

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

其实之前都是将用户名和密码写死在了程序中,只有用户名是 admin,密码是 123 的才能登陆成功,但是感觉太单调了,像这样加入几个学生对象更有逻辑一点。

5.几个有趣的小例子

5.1 猜数字小游戏

客户端输入用户猜的数字,并将数字发送给服务端,服务端进行验证并将验证结果返回给客户端,在给定的次数中,如果用户能够猜出数字那就代表用户赢了,没有的话那就输了,如果次数到了程序也就停止运行了。

客户端:

public class Client3 {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 9074);
        while (true) {
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    System.in));
            System.out.print("请输入一个0到100之间的数字:");
            String guess = br.readLine();

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));
            bw.write(guess);
            bw.newLine();
            bw.flush();

            BufferedReader br2 = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            String result = br2.readLine();
            System.out.println(result);
            if(result.equals("GAME OVER") || result.equals("YOU ARE WIN")){
                break;
            }
        }
        s.close();
    }
}

服务端:

public class Server3 {
    public static void main(String[] args) throws IOException {
        Random r = new Random();
        int num = r.nextInt(100) + 1;

        ServerSocket ss = new ServerSocket(9074);
        Socket s = ss.accept();

        int count = 0;
        while (true) {
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            String guess = br.readLine();
            int guessNum = Integer.parseInt(guess);

            String result = null;
            if (count > 5) {
                result = "GAME OVER";
            } else {
                if (guessNum > num) {
                    result = "大了";
                } else if (guessNum < num) {
                    result = "小了";
                } else if (guessNum == num) {
                    result = "YOU ARE WIN";
                }
            }

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));

            if (result.equals("GAME OVER") || result.equals("YOU ARE WIN")) {
                bw.write(result);
                bw.newLine();
                bw.flush();
                break;
            } else {
                bw.write(result);
                bw.newLine();
                bw.flush();
            }
            count++;
        }
        s.close();
        ss.close();
    }
}

5.2 用户登录完善版

新的要求:
1.用户数据应该保存到文本文件当中 user.txt
2.登录操作的过程应该写入到日志文本当中 log.txt
格式为:账号$$密码$$登录成功$$时间

程序就放在这下面吧!

user.txt

卡卡西##绝版天堂
带土##轻轻风铃
琳##暗夜烤鱼
水门##黄色闪光

登录会产生的日志文本文件 log.txt

鸣人$$雏田$$登录失败$$2017年06月21日21:20
佐助$$小樱$$登录失败$$2017年06月21日21:20
带土$$轻轻风铃$$登陆成功$$2017年06月21日21:38

定义的学生类:

public class Student {
    private String username;
    private String password;

    public Student() {
        super();
    }

    public Student(String username, String password) {
        super();
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((password == null) ? 0 : password.hashCode());
        result = prime * result
                + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (password == null) {
            if (other.password != null)
                return false;
        } else if (!password.equals(other.password))
            return false;
        if (username == null) {
            if (other.username != null)
                return false;
        } else if (!username.equals(other.username))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Student [username=" + username + ", password=" + password + "]";
    }
}

获得学生数据的数据类:

public class StudentDB {
    static List<Student> users = new ArrayList<Student>();

    static {
        try {
            readInfo();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        getUsers();
    }

    public static void readInfo() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(
                "src/day12/practice/user.txt"));

        String line = null;
        while ((line = br.readLine()) != null) {
            String[] infos = line.split("##");
            Student s = new Student(infos[0], infos[1]);
            users.add(s);
        }
        br.close();
    }

    public static List<Student> getUsers() {
        return users;
    }
}

读写日志信息的工具类:

public class Utils {
    static List<String> arr = new ArrayList<String>();

    public static void main(String[] args) throws IOException {
        getInfo();
    }

    public static void writeInfo() throws IOException{
        BufferedWriter bw = new BufferedWriter( new FileWriter("src/day12/practice/log.txt"));

        for (int i = 0; i < arr.size(); i++) {
            String line = arr.get(i);
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        bw.close();
    }

    public static void readInfo() throws IOException{
        BufferedReader br = new BufferedReader(new FileReader(
                "src/day12/practice/log.txt"));
        String line;
        while((line=br.readLine())!=null){
            arr.add(line);
        }
        br.close();
    }

    public static List<String> getInfo() throws IOException{
        readInfo();
        System.out.println(arr);
        return arr;
    }
}

客户端:

public class Client4 {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8078);

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("请输入用户名:");
        String username = br.readLine();
        System.out.print("请输入密码:");
        String password = br.readLine();

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                s.getOutputStream()));
        bw.write(username);
        bw.newLine();
        bw.flush();
        bw.write(password);
        bw.newLine();
        bw.flush();

        BufferedReader br2 = new BufferedReader(new InputStreamReader(
                s.getInputStream()));
        String result = br2.readLine();
        System.out.println(result);

        s.close();
    }
}

服务端:

public class Server4 {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8078);

        Socket s = ss.accept();

        BufferedReader br = new BufferedReader(new InputStreamReader(
                s.getInputStream()));
        String username = br.readLine();
        String password = br.readLine();

        List<Student> users = StudentDB.getUsers();
        Student stu = new Student(username, password);
        boolean flag = false;
        if (users.contains(stu)) {
            flag = true;
        }
        String result = null;
        if (flag) {
            result = "登陆成功";
        } else {
            result = "登录失败";
        }

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm");
        Date d = new Date();
        String str = sdf.format(d);

        List<String> infos = Utils.getInfo();
        StringBuilder sb = new StringBuilder();
        sb.append(username);
        sb.append("$$");
        sb.append(password);
        sb.append("$$");
        sb.append(result);
        sb.append("$$");
        sb.append(str);
        System.out.println(sb.toString());
        infos.add(sb.toString());
        Utils.writeInfo();


        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                s.getOutputStream()));
        bw.write(result);
        bw.newLine();
        bw.flush();

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

6.总结

这篇文章算是对网络编程学习的一个总结吧,将学到的知识写下来心里也更有底一点,重温一遍也算是对知识进行一次加深认识吧,虽然做的都是很基础的东西,但是慢慢地去积累吧,要有一颗打磨自己的心。

坚持原创技术分享,您的支持将鼓励我继续创作!