java-网络编程-(socket)-聊天室的编写 -01

  完整版代码 

java -聊天室的代码: 用于存放聊天室的项目的代码和思路导图icon-default.png?t=N7T8https://gitee.com/to-uphold-justice-for-others/java---code-for-chat-rooms.git

socket 是java中的一插口,要实现网络通信的话,需要连接插口和插口,而数据的传输使用了流的思想,读数据操作运用了输入流,而写数据运用了输出流,

聊天对话的实现

一个类作为服务端对象,用于接受客户端写出的输出流,

一个类作为客户端对象,用于输到的服务端的进行读取的输入流.

client客户端的类

私有一个成员变量 Socket插口

构造方法会优先比普通方法先调用,这边可以先定义一下客户端新建一个实例对象初始化的行为

public class Client {
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,使用它可以和远端计算机建立网络链接,并基于
        两条流(一条输入,一条输出)的读写与对方进行数据交换。
     */
    private Socket socket;

    /**
     * 构造器,用于初始化客户端
     */
    public Client(){
        try {
            System.out.println("正在链接服务端...");
            /*
                Socket实例化时就是与服务端建立链接的过程,此时需要传入两个
                参数
                参数1:服务端的IP地址,用于找到服务端的计算机
                参数2:服务端口,用于找到服务端程序

                如何查看IP地址:
                windows:窗口键+R 打开控制台
                        输入ipconfig
                        查看以太网适配器-以太网,找到ipv4查看自己的IP地址

                mac:打开[终端]程序
                    输入/sbin/ifconfig查看自己的IP地址
             */
//            socket = new Socket("127.0.0.1",8088);//127.0.0.1和localhost都是表示本机
            socket = new Socket("localhost",8088);
            System.out.println("与服务端成功链接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
   
}

主要点 Socket 需要传递两个参数 ip地址(服务端)  服务端占用的端口

ip地址 查看 

  如何查看IP地址:
                windows:窗口键+R 打开控制台
                        输入ipconfig
                        查看以太网适配器-以太网,找到ipv4查看自己的IP地址

                mac:打开[终端]程序
                    输入/sbin/ifconfig查看自己的IP地址
             */

socket端口查看

Windows系统:

  1. 按住Windows键+R,唤出运行对话框。
  2. 在对话框里输入“cmd”,然后点确定,就会出现cmd窗口。
  3. 在cmd窗口中输入命令“netstat -an”,然后回车,就能看见当前连接的列表,其中冒号后面跟着的数字就是端口号

 这边定义一个运行方法

public void start(){
        /*
            Socket提供的方法:
            OutputStream getOutputStream()
            通过Socket获取一个字节输出流,通过向该流写出字节,就可以发送给远端链接
            的计算机的Socket了
         */
        try {
            OutputStream out = socket.getOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
            BufferedWriter bw = new BufferedWriter(osw);
            PrintWriter pw = new PrintWriter(bw, true);
            pw.write(12);}catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //socket的close方法会进行四次挥手
                //并且也会关闭通过socket获取的输入流和输出流
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

       
          

finally是无论程序执行过程中是否发生异常都会执行,并且在强制退出方法也会执行,使用return中断下方的代码,强制退出该方法也会执行finally,

选择拔插口是不选择关闭文件流是为了告诉服务端 ,客户端想要中断输出,是为了保证传输的可靠性

三次握手主要用于建立TCP连接。这个过程包括三个步骤:

  1. 客户端发送一个带有SYN(synchronize)标志的数据包给服务端,并进入SYN_SEND状态,等待服务器确认。
  2. 服务端收到SYN包后,确认客户的SYN(ACK=客户端序列号+1),同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态。
  3. 客户端收到服务器的SYN+ACK包后,向服务器发送确认包ACK(ACK=服务器序列号+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。完成三次握手后,客户端与服务器端开始传送数据。

四次挥手则用于断开TCP连接。这个过程同样包含四个步骤:

  1. 客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT1状态。
  2. 服务端收到连接释放报文,发出确认报文,ACK=1,ack=序号+1,并且带上自己的序列号,服务端就进入了CLOSE_WAIT状态。此时TCP连接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接受。
  3. 若服务端也没有数据要发送了,就通知客户端,发送一个FIN,即释放数据报文,之后服务端进入LAST_ACK状态,等待客户端的确认。
  4. 客户端收到服务端的释放数据报文后,必须发出确认,ACK=1,ack=序号+1,而自己的序列号是seq=序号+1,此时,客户端就进入了TIME_WAIT状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

四次挥手中,服务器发送完ACK之后,释放了服务器端到客户端的数据传送,但是客户端到服务器端的这个方向数据传送还没有释放,所以客户端最后还要发送一次确认。

总的来说,三次握手和四次挥手确保了TCP连接的建立和断开过程中的数据同步和可靠性,是TCP/IP协议中非常重要的部分。

 最后运行

public static void main(String[] args) {
    //实际开发中不会在main方法中写业务逻辑,main方法是静态方法会有很多不便
    Client client = new Client();//调用构造器初始化客户端
    client.start();//调用start方法使客户端开始工作
}

服务端的类

 服务端构造方法定义一个启动服务端使用初始化行为,私有化的服务端插口

public class Server {
    /*
        java.net.ServerSocket
        运行在服务端的ServerSocket相当于时客户中心的"总机",上面有若干的插座(Socket)
        客户端的插座就是与总机建立链接,然后总机这边分配一个插座与之建立链接,来保持双方
        通讯的。
        ServerSocket有两个主要工作
        1:创建时向系统申请服务端口,以便客户端可以通过端口找到
        2:监听该端口,一旦一个客户端链接,便创建一个Socket,通过它与客户端通讯
     */
    private ServerSocket serverSocket;

  

    public Server(){
        try {
            System.out.println("正在启动服务端");
            /*
                实例化ServerSocket时需要指定向系统申请的服务端口,如果该端口
                已经被系统的其他应用程序占据,则这里会抛出异常
                java.net.BindException: Address already in use: bind()
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
   

ServerSocket 的参数是一个服务端占用的端口 

服务端运行的方法

public void start(){
        try {
           
                System.out.println("等待客户端链接...");
                /*
                ServerSocket的重要方法:
                Socket accept()
                该方法是一个阻塞方法,调用该方法后程序会"卡住",直到一个客户端使用
                Socket与服务端建立链接为止,此时accept方法会立即返回一个Socket
                通过返回的Socket就可以与链接的客户端双向通讯了。
             */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                String message;
                while ((message = br.readLine()) != null) {
                    //张三[192.168.2.5]说:XXXX
                    System.out.println(nickname+"[" + ip + "]说:" + message);
                    

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

服务端运行程序

public static void main(String[] args) {
    Server server = new Server();
    server.start();
}

 整理

客户端

ublic class Client {
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,使用它可以和远端计算机建立网络链接,并基于
        两条流(一条输入,一条输出)的读写与对方进行数据交换。
     */
    private Socket socket;

    /**
     * 构造器,用于初始化客户端
     */
    public Client(){
        try {
            System.out.println("正在链接服务端...");
            /*
                Socket实例化时就是与服务端建立链接的过程,此时需要传入两个
                参数
                参数1:服务端的IP地址,用于找到服务端的计算机
                参数2:服务端口,用于找到服务端程序

                如何查看IP地址:
                windows:窗口键+R 打开控制台
                        输入ipconfig
                        查看以太网适配器-以太网,找到ipv4查看自己的IP地址

                mac:打开[终端]程序
                    输入/sbin/ifconfig查看自己的IP地址
             */
//            socket = new Socket("127.0.0.1",8088);//127.0.0.1和localhost都是表示本机
            socket = new Socket("localhost",8088);
            System.out.println("与服务端成功链接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 用于客户端开始工作的方法
     */
    public void start(){
        /*
            Socket提供的方法:
            OutputStream getOutputStream()
            通过Socket获取一个字节输出流,通过向该流写出字节,就可以发送给远端链接
            的计算机的Socket了
         */
        try {
            OutputStream out = socket.getOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
            BufferedWriter bw = new BufferedWriter(osw);
            PrintWriter pw = new PrintWriter(bw, true);
            pw.write(01)
    } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //socket的close方法会进行四次挥手
                //并且也会关闭通过socket获取的输入流和输出流
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
   public static void main(String[] args) {
        //实际开发中不会在main方法中写业务逻辑,main方法是静态方法会有很多不便
        Client client = new Client();//调用构造器初始化客户端
        client.start();//调用start方法使客户端开始工作
    }
         

服务端

public class Server {
    /*
        java.net.ServerSocket
        运行在服务端的ServerSocket相当于时客户中心的"总机",上面有若干的插座(Socket)
        客户端的插座就是与总机建立链接,然后总机这边分配一个插座与之建立链接,来保持双方
        通讯的。
        ServerSocket有两个主要工作
        1:创建时向系统申请服务端口,以便客户端可以通过端口找到
        2:监听该端口,一旦一个客户端链接,便创建一个Socket,通过它与客户端通讯
     */
    private ServerSocket serverSocket;



    public Server(){
        try {
            System.out.println("正在启动服务端");
            /*
                实例化ServerSocket时需要指定向系统申请的服务端口,如果该端口
                已经被系统的其他应用程序占据,则这里会抛出异常
                java.net.BindException: Address already in use: bind()
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
 public void start(){
        try {
           
                System.out.println("等待客户端链接...");
                /*
                ServerSocket的重要方法:
                Socket accept()
                该方法是一个阻塞方法,调用该方法后程序会"卡住",直到一个客户端使用
                Socket与服务端建立链接为止,此时accept方法会立即返回一个Socket
                通过返回的Socket就可以与链接的客户端双向通讯了。
             */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                  String message;
                while ((message = br.readLine()) != null) {
                    //张三[192.168.2.5]说:XXXX
                    System.out.println("客户端"+说:" + message);
                   
            } catch (IOException e) {
                System.out.println(e);
            }
        }

问题 分析 

问题 这个客户端只能固定写死输出的数据,不能根据用户输入的内容传递到服务端,使服务端读取数据

这个问题,需要解决的话客户端需要用的Scanner,替换之前单调输出 pw.write()

 Scanner scanner = new Scanner(System.in);

 while(true) {
                String line = scanner.nextLine();
                if("exit".equalsIgnoreCase(line)){
                    break;
                }
                pw.println(line);
            }


需要提醒的是,Scanner中 nextline 是根据换行符鉴定用户是否输入一行内容,而PrintWriter是可以根据换行符刷新更新数据的,PrintWriter pw = new PrintWriter(bw, true);这边写了true是表示字符串输出流可以根据鉴定用户是否换行来刷新数据,记得 一定要使用printIn 或者 print(+"\n").

问题 假如多个客户端需要连接一个服务端怎么办

可以循环新建一个接受用户的插口

public void start(){
        while(true){
        try {
              
                System.out.println("等待客户端链接...");
                /*
                ServerSocket的重要方法:
                Socket accept()
                该方法是一个阻塞方法,调用该方法后程序会"卡住",直到一个客户端使用
                Socket与服务端建立链接为止,此时accept方法会立即返回一个Socket
                通过返回的Socket就可以与链接的客户端双向通讯了。
             */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                String message;
                while ((message = br.readLine()) != null) {
                    //张三[192.168.2.5]说:XXXX
                    System.out.println("客户端"+"说: " + message);
                    
}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

问题 服务端想要获取上线的用户名,同时获取客户端的ip地址

解决方式 客户端第一次传递给服务端的用户名为上线的用户名(这边使用昵称代替) ,这边客户端使用的是scanner获取用户的昵称

客户端代码 

Scanner scanner = new Scanner(System.in);
            //首先要求用户输入一个昵称
            String nickname = "";
            while(true) {
                System.out.println("请输入昵称:");
                nickname = scanner.nextLine();
                if(nickname.trim().length() > 0){
                    pw.println(nickname);//将昵称发送给服务端
                    break;
                }
                System.out.println("昵称不能为空");
            }
          System.out.println("开始聊天吧");

服务端

 服务端需要封装一个private 的昵称,并读取

public class Server {
   private String  nickname;
   private ServerSocket serverSocket;
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
           nickname = br.readLine();

}

同时 获取客户端的ip地址 是通过服务端接受的客户端插口的 inter地址和本机地址实现的,这个暂时先了解

ip = socket.getInetAddress().getHostAddress();

//socket是服务端接受的客户端插口的对象

问题 多个客户端连接一个服务器,同时不同客户端给服务端发送消息,服务端只能接受第一个人发生来的消息

原因分析 第一 服务端start方法使用两个循环,内存循环除非执行完,才能执行外层循环,而内层循环的Scanner是获取用户的输入,而内层需要结束需要根据用户的输入和字符串比较才能得到的结果.

我们需要引入一个新的概念 线程 线程是将我们程序用一条线的方式按照顺序依次执行.

这个我打算整理完,再说,我也是一个刚学的新手😂😂

  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些基本思路和代码示例。 首先,您需要使用Java编写一个GUI界面,可以使用Swing或JavaFX。在这个GUI界面中,您需要设置一个文本框用于显示聊天记录,一个文本框用于输入聊天内容,以及一个按钮用于发送聊天内容。 接下来,您需要使用Java的Socket编程实现客户端和服务器之间的通信。客户端需要连接到服务器,并向服务器发送聊天内容。服务器需要接收来自客户端的聊天内容,并将其广播给所有连接到服务器的客户端。 下面是一个简单的代码示例,帮助您开始编写聊天应用程序: 客户端代码: ```java import java.io.*; import java.net.*; import javax.swing.*; public class ChatClient extends JFrame implements ActionListener { private PrintWriter writer; private BufferedReader reader; private JTextArea chatArea; private JTextField inputField; public ChatClient() { // 创建GUI界面 chatArea = new JTextArea(); inputField = new JTextField(); JButton sendButton = new JButton("发送"); sendButton.addActionListener(this); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(new JScrollPane(chatArea), BorderLayout.CENTER); panel.add(inputField, BorderLayout.SOUTH); panel.add(sendButton, BorderLayout.EAST); this.add(panel); this.setTitle("聊天"); this.setSize(400, 300); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); // 连接服务器 try { Socket socket = new Socket("localhost", 8888); writer = new PrintWriter(socket.getOutputStream()); reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 接收服务器发送的消息 Thread t = new Thread(new Runnable() { @Override public void run() { try { String line; while ((line = reader.readLine()) != null) { chatArea.append(line + "\n"); } } catch (IOException e) { e.printStackTrace(); } } }); t.start(); } catch (IOException e) { e.printStackTrace(); } } @Override public void actionPerformed(ActionEvent e) { // 发送聊天内容到服务器 String text = inputField.getText(); writer.println(text); writer.flush(); inputField.setText(""); } public static void main(String[] args) { new ChatClient(); } } ``` 服务器代码: ```java import java.io.*; import java.net.*; import java.util.*; public class ChatServer { private List<PrintWriter> writers = new ArrayList<>(); public ChatServer() { try { ServerSocket server = new ServerSocket(8888); System.out.println("服务器已启动,等待客户端连接..."); while (true) { Socket socket = server.accept(); System.out.println("客户端已连接:" + socket.getInetAddress()); // 将新连接的客户端添加到列表中 PrintWriter writer = new PrintWriter(socket.getOutputStream()); writers.add(writer); // 开启新线程处理客户端发来的消息 Thread t = new Thread(new ClientHandler(socket)); t.start(); } } catch (IOException e) { e.printStackTrace(); } } public void broadcast(String message) { // 广播消息给所有客户端 for (PrintWriter writer : writers) { writer.println(message); writer.flush(); } } private class ClientHandler implements Runnable { private Socket socket; private BufferedReader reader; public ClientHandler(Socket socket) { this.socket = socket; try { reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { String line; try { while ((line = reader.readLine()) != null) { System.out.println("收到消息:" + line); broadcast(line); } } catch (IOException e) { e.printStackTrace(); } finally { // 客户端断开连接,从列表中移除 writers.remove(new PrintWriter(socket.getOutputStream())); } } } public static void main(String[] args) { new ChatServer(); } } ``` 希望这个简单的示例能够帮助您开始编写自己的Java GUI界面聊天应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值