基于Socket,Swing实现聊天小软件

Socket

在我的上一篇博客中详细的讲到了Swing的使用与见解,这里着重写一下对Socket的认识与使用。
在大学的计算机网络课程里,我第一次了解到socket(套接字),当时只把它当作一个简单的运输层概念随便记了一下,后面才发现Socket在java的通信中是一个多么重要的概念,现在想想也是,socket在TCP连接的两端,是IP加上端口号的组合,怎么着也和通信脱不了干系啊。但是准确的讲的话,在本文中socket有两层意思。
一个就是上面讲的TCP连接的两端,在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

在这里插入图片描述
再一个就是java中的socket类了。这是通过代码进行socket编程,通讯软件都离不开socket的帮忙。下面随便记点java中的socket函数

Class ServerSocket (服务器)

这个类实现了服务器套接字。 服务器套接字等待通过网络进入的请求。 它根据该请求执行一些操作,然后可能将结果返回给请求者。
ServerSocket(int port)
创建绑定到指定端口的服务器套接字。这是我目前最常用的一种构造方法,只需要传入一个本地的端口浩就行了(1024以下端口由系统使用,建议使用较高数值端口
accept()
侦听要连接到此套接字并接受它。创建好socket后肯定是等待用户连接进来了,accept函数返回的是已连接的socket描述字。当有多个socket连接进服务器的时候,一般要使用多线程。(采用while( true)死循环一直监听连接请求)每个socket连接线程在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
close()
关闭此套接字。
getInetAddress()
返回此服务器套接字的本地地址。
isBound()
返回ServerSocket的绑定状态。
isClosed()
返回ServerSocket的关闭状态。

Socket(客户端)

该类实现客户端套接字(也称为“套接字”)。 套接字是两台机器之间通讯的端点。
Socket()
创建一个未连接的套接字,并使用系统默认类型的SocketImpl。
Socket(InetAddress address, int port)
创建流套接字并将其连接到指定IP地址的指定端口号。 (常用的创建socket方法)
getInetAddress()
返回套接字所连接的地址。
getLocalAddress()
获取套接字所绑定的本地地址。
getLocalPort()
返回此套接字绑定到的本地端口号。
getInputStream()
返回此套接字的输入流。
InputStreamReader in = new InputStreamReader(socket.getInputStream(),“utf-8”) 将字节流转为字符流,并指定字符集
BufferedReader br = new BufferedReader(in) 读取字符流,为字符流创建缓冲区
getOutputStream()
返回此套接字的输出流。
OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream(),“utf-8”) 将字节流转为字符流,并指定字符集
BufferedWriter wr = new BufferedWriter(out) 为字符流创建缓冲区
PrintWriter pw = new PrintWriter(wr,true) 打印字符流 并自动刷新

socket工作流程

socket是**open—write/read—close”模式 (Unix/Linux基本哲学之一就是一切皆文件)**的一种实现
在这里插入图片描述
我们知道TCP连接需要进行三次握手,那么和socket连接时交互的几步又怎样的联系呢?
在这里插入图片描述
当客户端调用connect时(如果时创建socket时就指明了服务器的ip及端口,就不用调用connection了,默认创建后就connection了),触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

聊天小程序的实现

服务器代码

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server {
    private List<Socket> ClientList= new ArrayList<>();
    private  ServerSocket serverSocket = null;
    SocketThread socketThread;
    public static void main(String[] args) {
        new Server();
    }
    public Server(){
        try {
            serverSocket = new ServerSocket(8887);
            System.out.println("服务器已启动,等待用户接入");
            while (true){
                Socket socket = serverSocket.accept();
                ClientList.add(socket);
                socketThread = new SocketThread(socket,ClientList);
                
                /**这里的ClientList会实时的传到每个连接每个socket的线程里,
                 * 比如第一个socket连接进来的时候  ClientList.size为1;
                 * 第二个socket连接进来,重新开启一个线程,ClientList会重新传值进连接第一个socket的线程
                 */
                 
                socketThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

多线程,每来一个socket就创建一个线程,上文中注释很重要

import java.io.*;
import java.net.Socket;
import java.util.List;
public class SocketThread extends Thread {
    private Socket socket;
    private List<Socket> clientList;
    private BufferedReader br = null;
    private String message = "";
    public SocketThread(Socket socket, List<Socket> clientList) {
        this.socket = socket;
        this.clientList = clientList;
        try {
            br = new BufferedReader(new InputStreamReader(
                    socket.getInputStream(), "UTF-8"));
            message = "服务器地址" + this.socket.getInetAddress();
            this.sendMessage(message);
            message = "连接总数" + clientList.size();
            this.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        super.run();
        while (true) {
            try {
                if ((message = br.readLine()) != null) {
                    if (message.equals("exit")) {
                        closeSocket();
                        break;
                    } else {
                        message = socket.getInetAddress() + ":" + message;
                        this.sendMessage(message);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 本方法关闭socket连接,节约资源
     *
     * @throws IOException
     */
    public void closeSocket() throws IOException {
        clientList.remove(socket);
        br.close();
        message = "主机:" + socket.getInetAddress() + "关闭连接\n目前在线" + clientList.size();
        socket.close();
        this.sendMessage(message);
    }
    /**
     * 本方法是将信息发送到连接的每一个socket上
     * @param msg
     * @throws IOException
     */
    public void sendMessage(String msg) throws IOException {
        System.out.println(msg);
        int count = clientList.size();
        System.out.println("本线程连接socket数===" + count);
        for (int i = 0; i < count; i++) {
            Socket mSocket = clientList.get(i);
            PrintWriter out = null;
            out = new PrintWriter(new BufferedWriter(
                    new OutputStreamWriter(mSocket.getOutputStream(), "UTF-8")), true);
            out.println(msg);
        }
    }
}

接下来是客户端代码,由于涉及到可视化界面(swing在上篇博客中有详解),代码有点多,但是不复杂

import javax.swing.*;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;

public class Client implements ActionListener, WindowListener, KeyListener {
    private  Socket socket;
    private static final String HOST ="10.22.35.22";
    private static final int PORT=8887;
    BufferedReader br;
    PrintWriter out;
    OutputStream os;
    OutputStreamWriter osw;
    BufferedWriter bw;
    JFrame jFrame;
    JPanel jPanel;
    JTextArea jTextArea;
    JTextField jTextField;
    JScrollPane jScrollPane;
    JButton jButton;
    String info;
    public static void main(String[] args) throws IOException {
        Client client = new Client();
        client.showWindow();
        client.connection();
    }
    public void showWindow(){
        jFrame = new JFrame("Let'chat");
        jPanel = new JPanel();
        jTextArea = new JTextArea();
        jScrollPane = new JScrollPane(jTextArea);
        jScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        jScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        jTextField = new JTextField();
        jButton = new JButton("发送");
        jScrollPane.setBounds(0,0,500,400);
        jTextField.setBounds(0,400,400,100);
        jButton.setBounds(400,400,100,100);
        jPanel.setLayout(null);
        jPanel.setBounds(0,0,510,510);
        jPanel.add(jScrollPane);
        jPanel.add(jTextField);
        jPanel.add(jButton);
        jFrame.setSize(540,540);
        jFrame.add(jPanel);
        jFrame.setLocationRelativeTo(null);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.setVisible(true);
        jButton.addActionListener(this);
        jFrame.addWindowListener(this);
        jTextField.addKeyListener(this);
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource()==jButton){
            System.out.println("OK");
            out();
        }
    }
    @Override
    public void windowOpened(WindowEvent e) {
    }
    @Override
    public void windowClosing(WindowEvent e) {
        try {
            os = socket.getOutputStream();
            osw = new OutputStreamWriter(os,"UTF-8");
            bw = new BufferedWriter(osw);
            out = new PrintWriter(bw,true);
            out.println("exit");
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
    @Override
    public void windowClosed(WindowEvent e) {
    }
    @Override
    public void windowIconified(WindowEvent e) {
    }
    @Override
    public void windowDeiconified(WindowEvent e) {
    }
    @Override
    public void windowActivated(WindowEvent e) {
    }
    @Override
    public void windowDeactivated(WindowEvent e) {
    }
    @Override
    public void keyTyped(KeyEvent e) {
    }
    @Override
    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode()==KeyEvent.VK_ENTER){
            out();
        }
    }
    @Override
    public void keyReleased(KeyEvent e) {
    }
    public void out()
    {
        info=jTextField.getText().toString();
        try {
            os = socket.getOutputStream();
            osw = new OutputStreamWriter(os,"UTF-8");
            bw = new BufferedWriter(osw);
            out = new PrintWriter(bw,true);
            out.println(info);
            jTextField.setText("");

        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
    public void connection() throws IOException {
        try {
            socket = new Socket(HOST,PORT);
            br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
            out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8")),true);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("连接失败"+e.getMessage());
        }
        while (true){
            if (!socket.isClosed()){
                if (socket.isConnected()){
                    if (!socket.isInputShutdown()){
                        String getLine;
                        if ((getLine = br.readLine())!=null){
                            jTextArea.append(getLine+"\n");
                            System.out.println(getLine);
                        }
                    }
                }
            }
        }
    }
}

程序运行截图

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
由于在一台电脑上操作,显示IP是一样的

注意:要想顺利运行程序,服务器与客户端须在同一局域网,且关闭了防火墙。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值