使用ServerSocket、Socket创建一个简单的服务聊天程序

使用ServerSocket创建TCP服务器端:
在两个实心实体没有建立虚拟链路之前,必须有一个通信实体先做出“主动姿态”,主动接收来自其他通信实体的连接请求。
ServerSocket在Java中能接收到其他通信的连接请求,ServerSocket对象用于监听来自客户端Socket的连接请求,ServerSocket使用完毕后再使用它的close()方法将ServerSocket关闭,一般服务端要接收好多个客户端的连接请求,所以程序中通常会用while循环不断的调用ServerSocket的accept()方法来接收客户端的请求。

ServerSocket server=new ServerSocket(8088);
//当接收到客户端的连接请求时,服务端就会产生一个Socket
//为了让服务端不断的接收来自客户端的请求,所以都是用while循环
while(true){
Socket s=server.accept();
}

上面的客户端通信就是用Socket来连接到服务端的,经常用的就是

Socket socket=new Socket(InetAddress/String remoteAddress,int port);

因为本地主机只有一个IP地址,所有使用这个比较方便简单
本地IP地址使用的是127.0.0.1,这个还是比较特殊的,它总是代表着本机的IP地址哈。

当客户端和服务端产生了对应的Socket后,程序也就不再区分什么服务器端和客户端,直接通过各自的Socket进行通信。
基于这个可以做一个简单的通信代码,当你连接上服务端后,服务器会向所有在线的客户端广播*******on Line,然后你无论说什么,服务端就像一个大喇叭给你广播出去给所有客户端看。
客服端看到的结果
服务端看到的结果

package Chat;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * 客户端
 * 
 * Java.net.Socket
 * 封装了TCP协议的Socket
 * 通过它来连接服务端的ServerSocket并创建输入输出流来与服务端通信
 * @author Administrator
 *
 */
public class Client {

    private Socket socket;
    /*
     * 构造方法,用来初始化客户端
     * 构造方法常用来初始化对象属性等操作
     */

    public Client(){
        try {

            /*
             * 初始Socket时需要传入两个参数
             * 1.服务端IP地址
             * 2.服务端端口号
             * 
             * 首先要清楚:
             * 通讯是客户端计算机上的一个客户端应用程序与服务端计算机(俗称服务器)上的一个服务端
             * 应用程序之间的通讯
             * 
             * IP地址的作用是让我们通过网络可以找到服务器,而端口可以让我们找到运行在服务器上的服务端
             * 应用程序。
             * 
             * 创建Socket实例的过程就是与服务端连接的过程,若可以成功与服务器连接上,则会创建Socket
             * 实例,否则构造方法会抛出异常。
             */

            System.out.println("正在尝试连接服务端……");
            socket=new Socket("localhost",8088);
            System.out.println("与服务端连接成功");


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



    /*
     * 客户端开始工作的方法
     */
    public void start(){

        try {
            /*
             * 当客户端启动后,就启动接收服务端发送过来消息的线程
             */
            GetServerMessageHandler handler=new GetServerMessageHandler();
            new Thread(handler).start();
            /*
             * OutputStream getOutputStream()
             * Socket提供了该方法,用来获取输出流来向服务端发送数据。
             */
            Scanner scan=new Scanner(System.in);
            OutputStream out=socket.getOutputStream();

            OutputStreamWriter osw=new OutputStreamWriter(out,"UTF-8");
            PrintWriter pw=new PrintWriter(osw,true);
            System.out.println("客户端输入信息:");
            while(true){
                String message=scan.next();
                pw.println(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

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


    /**
     * 由于接收服务端发送来的消息,与我们给服务端发送消息没有必然关系,所以两者应当在两个不同
     * 线程上完成,各做各的,互不干涉。
     * @author Administrator
     *
     */
    private class GetServerMessageHandler implements Runnable{
        public void run(){
            try {

                /*
                 * 该线程的职责就是读取服务端发送过来的每一条消息,并输出到控制台
                 */
                InputStream in=socket.getInputStream();
                InputStreamReader isr=new InputStreamReader(in,"utf-8");
                BufferedReader br=new BufferedReader(isr);
                String message=null;
                while((message=br.readLine())!=null){
                    System.out.println("服务器说:"+message);
                }

            } catch (Exception e) {

            }
        }
    }
}

程序写的有点繁琐,上面那个是客户端的代码,下面这个是服务端的代码:

package Chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * 聊天室服务端
 * 
 * java.net.ServerSocket
 * ServerSocket是运行在服务端的,其作用是向系统申请服务端端口,以便监听该端口,等待客户端的连接,
 * 一旦一个客户端连接,就会创建一个Socket与该客户端进行通信。
 * @author Administrator
 *
 */
public class Server {
    private ServerSocket server;//ServerSocket对象用于监听来自客户端的Socket连接,
                                //ServerSocket包含一个 监听来自客户端连接请求的方法
    //存放所有客户端输出流的集合,用于广播信息
    private List<PrintWriter> allOut;
    public  Server(){
        try {
            allOut=new ArrayList<PrintWriter>();
            /*
             * 初始化ServerSocket的同时需要指定服务端口,该端口不能与当前系统使用TCP协议的
             * 其他程序申请的端口冲突,否则会抛出端口被占用异常
             */
            server = new ServerSocket(8088);

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

    }

    private synchronized void addOut(PrintWriter pw){
        allOut.add(pw);
    }
    private synchronized void removeOut(PrintWriter pw){
        allOut.remove(pw);
    }
    private synchronized void sendMessageToAllClient(String m){
        for(PrintWriter pw:allOut){
            pw.println(m);
        }
    }

    //服务端开始工作的方法
    public void start(){
        try {


            /*
             * Socket accept()
             * ServerSocket提供该方法用来监听打开服端口(8088),该方法是一个阻塞方法,
             * 直到一个客户端尝试连接才会解除阻塞,并创建一个Socket与刚连接的客户端进行通讯。
             * 
             * accept方法每次调用都会等待一个客户端的连接,所以若希望让若干个客户端连接,就需要多次
             * 调用该方法,来分别获取对应这些客户端
             * 的socket与他们通讯。
             */

            while(true){
            System.out.println("等待客户端连接……");
            Socket socket=server.accept();
            System.out.println("一个客户端连接了!");
            /**
             * 当一个客户端连接后,启动一个线程,来负责与该客户端交互
             */
            ClientHandler handler=new ClientHandler(socket);
            new Thread(handler).start();
            }

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

    }

    /**
     * 
     * 该线程用来与一个指定的客户端进行交互
     * 每当一个客户端连接服务端后,都会启动当前线程来负责与之交互工作。
     */
    private class ClientHandler implements Runnable{
        private Socket  socket;

        //客户端地址信息
        private String host;

        public ClientHandler(Socket socket){
            this.socket=socket;
            //通过socket可以得知远端计算机信息
            InetAddress address=socket.getInetAddress();
            host=address.getHostAddress();
        }
        public void run(){
            PrintWriter pw=null;
            try {
                /*
                 * 通过客户端的Socket获取输出流,以便将消息发送给客户端
                 */
                OutputStream out=socket.getOutputStream();
                OutputStreamWriter osw=new OutputStreamWriter(out,"UTF-8");
                pw=new PrintWriter(osw,true);

                //共享该客户端的输出流
                addOut(pw);

                //广播该用户上线
                sendMessageToAllClient(host+"onLine");


                /*
                 * InputStream getInputStream()
                 * Socket提供的该方法用来获取输入流,读取远端计算机发送过来的数据
                 */
                    InputStream in=socket.getInputStream();
                    InputStreamReader isr=new InputStreamReader(in,"utf-8");
                    BufferedReader br=new BufferedReader(isr);

                    String message=null;
                    /*
                     * 当我们使用BufferedReader读取来自远端计算机发送过来的内容时,由于远端计算机的操作系统不同,
                     * 当他们断开连接时,这里readline方法的结果也不同:
                     * 当远端计算机操作系统是Windows时,若断开连接,这里的readline方法直接会抛出异常。
                     * 当远端计算机操作系统是Linux时,若断开连接,这里的readline方法返回NULL。
                     */
                    while((message=br.readLine())!=null){
                    sendMessageToAllClient(host+"说:"+message);
                    }


            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                /*
                 * 当该客户端与服务端断开时,应当将该客户端的输出流从共享集合删除。
                 */
                allOut.remove(pw);

                /**
                 * 无论是Linux的客户端还是Windows的客户端,当与服务器断开连
                 * 接后,都应当将与该客户端交互的 socket关闭,来释放底层资源。
                 */
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人圭先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值