余同学的开发之路

余同学的开发之路

前几天介绍了计算机网络的一些概念,并介绍了几个协议。下面就说说 Java 中的 Socket 编程,服务器和客户端是如何通信的呢?

首先要介绍一下 Socket ,我们知道在 TCP/IP 协议簇中,TCP、UDP 协议都是在传输层,应用层基于传输层进行通信。而 Socket 可以看成是对 TCP 、UDP 协议的实现。具体到编程的时候,要看业务选择是使用 TCP 还是 UDP 协议。今天主要讲的就是基于 TCP 通信的 Socket 实现。若你对 TCP 还不熟悉。可以看这篇文章。

Java 中为 TCP 协议提供了两个类:Socket 类和 ServerSocket 类。一个 Socket 实例代表了 TCP 连接的一个客户端,而一个 ServerSocket 实例代表了 TCP 连接的一个服务器端,一般在 TCP Socket 编程中,客户端有多个,而服务器端只有一个,客户端 TCP 向服务器端 TCP 发送连接请求,服务器端的 ServerSocket 实例则监听来自客户端的 TCP 连接请求,并为每个请求创建新的 Socket 实例,由于服务端在调用 accept()等待客户端的连接请求时会阻塞,直到收到客户端发送的连接请求才会继续往下执行代码,因此要为每个 Socket 连接开启一个线程(这里就是多线程的应用啊)。服务器端要同时处理 ServerSocket 实例和 Socket 实例,而客户端只需要使用 Socket 实例。

另外,每个 Socket 实例会关联一个 InputStream 和 OutputStream 对象,我们通过将字节写入 Socket 的 OutputStream 来发送数据,并通过从 InputStream 来接收数据。

好吧,上面的描述可能有点懵,下面就来看一个 demo。使用 Socket 实现一个简单的交互,在服务器端使用多线程来处理请求。

客户端实现如下:

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = null;
        PrintWriter pw = null;
        BufferedReader br = null;
        try {
            // 创建Socket对象,指明需要连接的服务器地址和端口
            socket = new Socket("localhost", 6688);

            // 连接建立后,通过 Socket 输出流向服务器端发送请求信息
            pw = new PrintWriter(socket.getOutputStream());
            pw.write("Hello , server . I'm Client !");
            pw.flush();
            socket.shutdownOutput();

            // 通过输入流获取服务器端返回的响应信息;
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String info = null;
                while((info = br.readLine()) != null){
                    System.out.println("服务器返回信息: "+ info);
            }
            socket.shutdownInput();

        ----后面的错误处理和关闭资源省略-----
    }
}

服务器端实现如下:

public class Server {
    public static void main(String[] args) throws IOException {
        Socket socket = null;
        try {
            // 创建ServerSocket对象,绑定监听端口
            ServerSocket serverSocket = new ServerSocket(6688);
            while(true){
                // 通过accept()方法监听客户端请求
                socket =serverSocket.accept();
                ServerThread serverThread = new ServerThread(socket);
                serverThread.start();
            }
    }
}

线程具体实现如下:

public class ServerThread extends Thread {
    Socket socket = null;
    BufferedReader br = null;
    PrintWriter pw = null;

    public ServerThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 连接建立后,通过输入流读取客户端发送的请求信息 msg
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            StringBuffer msg = new StringBuffer();
            String info = null;
            while((info = br.readLine()) != null){
                msg.append(info);
            }
            System.out.println("服务器收到 [ "+ socket.getInetAddress()+" ] 的消息 [ " + msg+" ]");
            socket.shutdownInput();

            // 通过输出流向客户端发送相应信息
            pw = new PrintWriter(socket.getOutputStream());
            pw.write(" success !");
            pw.flush();
            socket.shutdownOutput();
        }
    }
}

总结一下 Socket TCP 中实战的步骤。

服务器端:

(1) 创建ServerSocket对象,绑定监听端口;
(2) 通过accept()方法监听客户端请求;
(3) 连接建立后,通过输入流读取客户端发送的请求信息;
(4) 通过输出流向客户端发送相应信息;

(5) 关闭响应资源。

客户端:

(1) 创建Socket对象,指明需要连接的服务器地址和端口;
(2) 连接建立后,通过输出流向服务器端发送请求信息;
(3) 通过输入流获取服务器端返回的响应信息;

(4) 关闭响应资源。

注意:

1 首先执行服务器端代码。

2 服务器端执行之后默认就一直在等待客户端的连接请求。

3 以上只是一个非常基础的案例,这只是 Socket 编程的冰山一角。

4 可以优化的地方还有很多,服务器端参数的优化,如,接受数据的缓冲区大小、等待客户端连接的最长时间、使用线程池处理请求等。

12-25 19:56