java中的socket

AlgoCraftsman
• 阅读 1639

这次在java实验的时候,要求使用server socket编写服务器和客户端的网络通信。最开始认为应该是挺简单的,但是后来发现低估了它。出现了不少的问题,所以也在这里与大家分享。

问题描述

服务器程序的处理规则如下:
1) 向客户端程序发送Verifying Server!。
2) 若读口令次数超过3次,则发送Illegal User!给客户端,程序退出。否则向下执行步骤3)。
3) 读取客户端程序提供的口令。
4) 若口令不正确,则发送PassWord Wrong!给客户端,并转步骤2),否则向下执行步骤5)。
5) 发送Registration Successful!给客户端程序。

客户端程序的处理规则如下:
1) 读取服务器反馈信息。
2) 若反馈信息不是Verifying Server!,则提示Server Wrong!,程序退出。否则向下执行步骤3)
3) 提示输入PassWord并将输入的口令发送给服务器。
4) 读取服务器反馈信息。
5) 若反馈信息是Illegal User!,则提示Illegal User!,程序退出。否则向下执行步骤6)
6) 若反馈信息是PassWord Wrong!,则提示PassWord Wrong!,并转步骤3),否则向下执行步骤。
7) 输出Registration Successful!。

初步实现

首先,我们一定要清楚,这次和之前的程序不同,虽然都是在本地上,但是服务器客户端需要两个启动程序来实现,以达到我们模拟远程连接的效果。

然后就是如何利用socket实现我们的功能了。

java中的socket

通过上面的图示,我们可以知道,首先需要先开启服务器,然后等待客户端的连接。

当客户端通过socket进行连接后,服务器端也会建立一个socket对象来帮助实现服务器和客户端的通信。

这时候就建立了一个TCP连接,我们就可以在服务器写数据,然后在客户端读取了,实现双方通信。

最后,当有一方决定通信结束,就会关闭连接。通信结束。

下面是初步实现的代码:

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

/**
 * 服务器
 */
public class ServerTest {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            Socket clientSocket = serverSocket.accept();

            String welcome  = "verifying server!";
            OutputStream outputStream = clientSocket.getOutputStream();
            outputStream.write(welcome.getBytes());

            InputStream inputStream = clientSocket.getInputStream();
            int time = 0;

            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String password = bufferedReader.readLine();

            // 获取登录信息,允许3次登录
            while (time < 3) {
                if (password.equals("123")) {
                    outputStream.flush();
                    outputStream.write("Registration Successful!".getBytes());
                    break;
                } else {
                    outputStream.write("PassWord Wrong!".getBytes());
                    outputStream.flush();
                    password = bufferedReader.readLine();
                    time++;
                }
            }

            if (time >= 3) {
                outputStream.flush();
                outputStream.write("Illegal User!".getBytes());
            }

            outputStream.close();
            clientSocket.close();
            serverSocket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;
import java.net.Socket;

/**
 * 客户端
 */
public class ClientTest {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 8080);

            String aline = new String();
            
            // 获取输入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            aline = bufferedReader.readLine();

            // 访问失败
            if (!aline.equals("verifying server!")) {
                System.out.println("Server Wrong!");
                return;
            }

            // 获取响应结果
            String feedback = bufferedReader.readLine();

            while (feedback == null || feedback.equals("PassWord Wrong!")) {
                if (feedback != null) {
                    if (feedback.equals("PassWord Wrong!")) {
                        System.out.println("PassWord Wrong!");
                    } else if (feedback.equals("Registration Successful!")) {
                        System.out.println("Registration Successful!");
                        break;
                    }
                }

                System.out.println("输入密码:");
                
                // 输入密码
                Scanner scanner = new Scanner(System.in);
                OutputStream outputStream = socket.getOutputStream();
                String password = scanner.nextLine();
                outputStream.write(password.getBytes());
                
                feedback = bufferedReader.readLine();
            }

            // 关闭连接
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

初步实现后,运行:一片空白。什么都没有发生。

出问题了很正常,断点调试一下吧。发现问题所在:

java中的socket

客户端执行到这一行时,停止,然后服务器端接着执行;

java中的socket

同样的服务器端也到这行就停止了。

原因是服务器一方等着输入密码,而客户端一方等着响应结果,然后又没有开始输入密码。所以双方就这么一直等着,谁都不动了。

解决

通过上面的分析,我们只要将僵局打破就能够解决问题。所以就先输入密码,然后再获取响应结果。这样就不会出问题了。

所以服务器端的代码并不需要做什么改动,只用修改客户端程序就行了。

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

/**
 * 客户端
 */
public class ClientTest {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 8080);

            String aline = new String();
            
            // 获取输入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            aline = bufferedReader.readLine();

            // 访问失败
            if (!aline.equals("verifying server!")) {
                System.out.println("Server Wrong!");
                return;
            }

            // 主要修改这里,不先获取响应结果
            // 初始化响应结果
            String feedback = null;

            while (true) {
                if (feedback != null) {
                    if (feedback.equals("PassWord Wrong!")) {
                        System.out.println("PassWord Wrong!");
                    } else if (feedback.equals("Registration Successful!")) {
                        System.out.println("Registration Successful!");
                        break;
                    }
                }

                System.out.println("输入密码:");
                
                // 输入密码
                Scanner scanner = new Scanner(System.in);
                OutputStream outputStream = socket.getOutputStream();
                String password = scanner.nextLine();
                outputStream.write(password.getBytes());
                
                feedback = bufferedReader.readLine();
            }

            // 关闭连接
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

java中的socket

成功实现!


升级:利用多线程实现

功能成功实现之后,还不能满足于当前的状况。由于在实际使用socket的时候,很多时候都不是简单的一对一的情况,这种情况下,就需要使用多线程来帮助我们了。

使用多线程,我们可以实现一个服务器多个客户端的情况,每当有一个客户端请求连接,我们就会建立一个线程。

1.服务器端线程实现

首先将服务器独立成一个线程:

/**
 * 服务器线程
 */
public class ServerThread extends Thread {
    private ServerSocket serverSocket;
    private Socket clientSocket;

    public ServerThread(int port) {
        try {
            serverSocket = new ServerSocket(port);
            // 接受客户端连接请求
            clientSocket = serverSocket.accept();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在构造函数中我们初始化服务器的socket,然后等待客户端的连接。接着就是最主要的部分了,线程主要执行的逻辑功能:run

@Override
public void run() {
    try {
        // 提示连接成功
        DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream());
        dataOutputStream.writeUTF("verifying server!");

        // 获取输入流
        InputStream inputStream = clientSocket.getInputStream();
        DataInputStream dataInputStream = new DataInputStream(inputStream);
        String password = dataInputStream.readUTF();

        // 获取登录信息,允许3次登录
        int time = 0;

        // 密码校验,允许输入3次
        while (time < 3) {
            if (password.equals("123")) {
                dataOutputStream.writeUTF("Registration Successful!");
                break;
            } else {
                dataOutputStream.writeUTF("PassWord Wrong!");
                password = dataInputStream.readUTF();
                time++;
            }
        }

        if (time >= 3) {
            dataOutputStream.writeUTF("Illegal User!");
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

run没有什么可以说的,主要就是实现服务器与客户端交互的时候执行的功能。然后在执行线程的时候,会自动调用run方法。

最后就是启动服务器端了:

/**
 * 服务器端
 */
public class ServiceTest {
    public static void main(String[] args) {
        ServerThread serverThread = new ServerThread(8080);
        serverThread.start();
    }
}

同样,启用8080端口。

2.客户端线程实现

类似服务器端,首先先建立客户端线程:

/**
 * 客户端线程
 */
public class ClientThread extends Thread {
    private Socket socket;

    public ClientThread(String host, int port) {
        try {
            this.socket = new Socket(host, port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在构造函数中,建立客户端的socket对象。然后实现run方法。

@Override
public void run() {
    try {
        // 获取输入流
        DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
        String aline = dataInputStream.readUTF();

        // 连接服务器失败
        if (!aline.equals("verifying server!")) {
            System.out.println("Server Wrong!");
            return;
        }

        // 获取响应结果
        String feedback = null;

        // 进行密码输入
        while (true) {
            if (feedback != null) {
                if (feedback.equals("PassWord Wrong!")) {
                    System.out.println("PassWord Wrong!");
                } else if (feedback.equals("Registration Successful!")) {
                    System.out.println("Registration Successful!");
                    System.exit(1);
                }
            }

            System.out.println("输入密码:");
            Scanner scanner = new Scanner(System.in);
            DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
            String password = scanner.nextLine();
            dataOutputStream.writeUTF(password);
            
            // 获取响应结果
            feedback = dataInputStream.readUTF();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

run方法中,主要实现了输入密码的功能。

最后就是启动客户端线程:

 /**
 * 客户端
 */
public class ClientTest {
    public static void main(String[] args) {
        ClientThread clientThread = new ClientThread("127.0.0.1", 8080);
        clientThread.start();
    }
}

总结

这次的实验给我提了个醒,看似简单的东西,也永远不要轻视它。只有拿出应有的实例去解决问题,问题才是你眼中那个简单的问题。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Easter79 Easter79
4年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
4年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
AlgoCraftsman
AlgoCraftsman
Lv1
我也怕你会在我看不见的地方陪着别人.
文章
3
粉丝
0
获赞
0