Java 套接字服务器示例

在本 Java 网络编程教程中,您将学习如何开发套接字服务器程序来实现功能齐全的网络客户端/服务器应用程序。您还将学习如何创建多线程服务器。

首先,让我们了解一下工作流和 API。

 

1.ServerSocket API

ServerSocket的类用来实现服务器程序。以下是开发服务器程序的典型步骤:

1.创建一个服务端socket并绑定到一个特定的端口号

2. 监听来自客户端的连接并接受它。这导致为连接创建客户端套接字。

3.通过从客户端socket获取的InputStream从客户端读取数据。

4. 通过客户端套接字的OutputStream向客户端发送数据。

5. 关闭与客户端的连接。

根据服务器和客户端之间商定的协议,步骤 3 和 4 可以重复多次。

可以对每个新客户端重复步骤 1 到 5。每个新连接都应该由一个单独的线程处理。

让我们详细了解每个步骤。

 

创建服务器套接字:

使用以下构造函数之一创建ServerSocket类的新对象:

ServerSocket(int port) : 创建一个绑定到指定端口号的服务器套接字。排队的传入连接的最大数量设置为 50(当队列已满时,拒绝新连接)。

ServerSocket(int port, int backlog):创建一个绑定到指定端口号的服务器套接字,并且最大排队连接数由backlog 参数指定。

ServerSocket(int port, int backlog, InetAddress bindAddr):创建服务器套接字并将其绑定到指定的端口号和本地 IP 地址。


 

那么什么时候使用哪个?

对少量排队连接(少于 50 个)和任何可用的本地 IP 地址使用第一个构造函数。

如果要明确指定排队请求的最大数量,请使用第二个构造函数。

如果要明确指定要绑定的本地 IP 地址(以防计算机有多个 IP 地址),请使用第三个构造函数。

当然,第一个构造函数更适合简单使用。例如,以下代码行创建一个服务器套接字并将其绑定到端口号 6868:

1
ServerSocket serverSocket = new ServerSocket(6868);

请注意,如果在打开套接字时发生 I/O 错误,这些构造函数可能会抛出IOException,因此您必须捕获或重新抛出它。

 

监听连接:

一旦ServerSocket的实例被创建,调用accept()方法开始监听传入的客户端请求:

1
Socket socket = serverSocket.accept();

请注意,accept()方法会阻塞当前线程,直到建立连接。并且连接由返回的Socket对象表示。

 

从客户端读取数据:

一旦一个套接字对象被返回,你可以使用它的InputStream来读取客户端发送这样的数据:

1
InputStream input = socket.getInputStream();

的InputStream可以读取处于较低水平的数据:读取的字节数组。因此,如果您想在更高级别读取数据,请将其包装在InputStreamReader 中以将数据作为字符读取:

1
2
InputStreamReader reader = new InputStreamReader(input);
int character = reader.read();  // reads a single character

您还可以将InputStream包装在BufferedReader 中以将数据读取为 String,以便更方便:

1
2
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String line = reader.readLine();    // reads a line of text

 

向客户端发送数据:

使用与Socket关联的OutputStream向客户端发送数据,例如:

1
OutputStream output = socket.getOutputStream();

由于OutputStream仅提供低级方法(将数据写入字节数组),您可以将其包装在PrintWriter 中以文本格式发送数据,例如:

1
2
PrintWriter writer = new PrintWriter(output, true);
writer.println(“This is a message sent to the server”);

参数true表示编写器在每次方法调用后刷新数据(自动刷新)。

 

关闭客户端连接:

调用客户端Socket上的close()方法终止与客户端的连接:

1
socket.close();

此方法还关闭套接字的InputStreamOutputStream,如果关闭套接字时发生 I/O 错误,它可以抛出IOException

我们建议您使用try-with-resource 结构,这样您就不必编写代码来显式关闭套接字。

当然,服务器仍在运行,用于为其他客户端提供服务。

 

终止服务器:

服务器应该始终运行,等待来自客户端的传入请求。如果由于某些原因必须停止服务器,请调用ServerSocket 实例上的close()方法:

1
serverSocket.close();

当服务器停止时,所有当前连接的客户端都将断开连接。

ServerSocket的类还提供了其他的方法,你可以在它的Javadoc咨询这里

 

实现多线程服务器:

所以基本上,服务器程序的工作流程是这样的:

1
2
3
4
5
6
7
8
ServerSocket serverSocket = new ServerSocket(port);
 
while (true) {
    Socket socket = serverSocket.accept();
     
    // read data from the client
    // send data to the client
}

一段时间(真)循环用于允许服务器一直运行下去,一直等待来自客户端的连接。但是,有一个问题:一旦连接了第一个客户端,如果服务器忙于为第一个客户端提供服务,则它可能无法处理后续客户端。

因此,为了解决这个问题,使用了线程:每个客户端套接字由一个新线程处理。服务器的主线程只负责监听和接受新的连接。因此,工作流被更新以实现多线程服务器,如下所示:

1
2
3
4
5
while (true) {
    Socket socket = serverSocket.accept();
     
    // create a new thread to handle client socket
}

您将在下面的示例中清楚地了解。

 

2. Java 服务器套接字示例 #1:时间服务器

以下程序演示了如何实现一个简单的服务器,为每个新客户端返回当前日期时间。这是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.*;
import java.net.*;
import java.util.Date;
 
/**
 * This program demonstrates a simple TCP/IP socket server.
 *
 * @author www.codejava.net
 */
public class TimeServer {
 
    public static void main(String[] args) {
        if (args.length < 1return;
 
        int port = Integer.parseInt(args[0]);
 
        try (ServerSocket serverSocket = new ServerSocket(port)) {
 
            System.out.println("Server is listening on port " + port);
 
            while (true) {
                Socket socket = serverSocket.accept();
 
                System.out.println("New client connected");
 
                OutputStream output = socket.getOutputStream();
                PrintWriter writer = new PrintWriter(output, true);
 
                writer.println(new Date().toString());
            }
 
        catch (IOException ex) {
            System.out.println("Server exception: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
}

运行此服务器程序时需要指定端口号,例如:

1
java TimeServer 6868

这使服务器在端口号 6868 上侦听客户端请求。您将看到服务器的输出:

1
Server is listening on port 6868

下面的代码是一个客户端程序,它简单地连接到服务器并打印接收到的数据,然后终止:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.net.*;
import java.io.*;
 
/**
 * This program demonstrates a simple TCP/IP socket client.
 *
 * @author www.codejava.net
 */
public class TimeClient {
 
    public static void main(String[] args) {
        if (args.length < 2return;
 
        String hostname = args[0];
        int port = Integer.parseInt(args[1]);
 
        try (Socket socket = new Socket(hostname, port)) {
 
            InputStream input = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
 
            String time = reader.readLine();
 
            System.out.println(time);
 
 
        catch (UnknownHostException ex) {
 
            System.out.println("Server not found: " + ex.getMessage());
 
        catch (IOException ex) {
 
            System.out.println("I/O error: " + ex.getMessage());
        }
    }
}

要运行此客户端程序,您必须指定服务器的主机名/IP 地址和端口号。如果客户端与服务器在同一台计算机上,请键入以下命令以运行它:

1
java TimeClient localhost 6868

然后你会在服务器程序中看到一个新的输出,表明客户端已连接:

1
New client connected

您应该会看到客户端的输出:

1
Mon Nov 13 11:00:31 ICT 2017

这是从服务器返回的日期时间信息。然后客户端终止,服务器仍在运行,等待新的连接。就这么简单。

 

3. Java Socket Server Example #2:反向服务器(单线程)

接下来,让我们看一个更复杂的套接字服务器示例。以下服务器程序以相反的形式回显从客户端发送的任何内容(因此名称为ReverseServer)。这是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.io.*;
import java.net.*;
 
/**
 * This program demonstrates a simple TCP/IP socket server that echoes every
 * message from the client in reversed form.
 * This server is single-threaded.
 *
 * @author www.codejava.net
 */
public class ReverseServer {
 
    public static void main(String[] args) {
        if (args.length < 1return;
 
        int port = Integer.parseInt(args[0]);
 
        try (ServerSocket serverSocket = new ServerSocket(port)) {
 
            System.out.println("Server is listening on port " + port);
 
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("New client connected");
 
                InputStream input = socket.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(input));
 
                OutputStream output = socket.getOutputStream();
                PrintWriter writer = new PrintWriter(output, true);
 
 
                String text;
 
                do {
                    text = reader.readLine();
                    String reverseText = new StringBuilder(text).reverse().toString();
                    writer.println("Server: " + reverseText);
 
                while (!text.equals("bye"));
 
                socket.close();
            }
 
        catch (IOException ex) {
            System.out.println("Server exception: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
}

如您所见,服务器继续为客户端提供服务,直到它说“再见”为止。使用以下命令运行此服务器程序:

1
java ReverseServer 9090

服务器启动并运行,等待来自客户端的传入请求:

1
Server is listening on port 9090

现在,让我们创建一个客户端程序。以下程序连接到服务器,读取用户的输入并打印来自服务器的响应。这是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.net.*;
import java.io.*;
 
/**
 * This program demonstrates a simple TCP/IP socket client that reads input
 * from the user and prints echoed message from the server.
 *
 * @author www.codejava.net
 */
public class ReverseClient {
 
    public static void main(String[] args) {
        if (args.length < 2return;
 
        String hostname = args[0];
        int port = Integer.parseInt(args[1]);
 
        try (Socket socket = new Socket(hostname, port)) {
 
            OutputStream output = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(output, true);
 
            Console console = System.console();
            String text;
 
            do {
                text = console.readLine("Enter text: ");
 
                writer.println(text);
 
                InputStream input = socket.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(input));
 
                String time = reader.readLine();
 
                System.out.println(time);
 
            while (!text.equals("bye"));
 
            socket.close();
 
        catch (UnknownHostException ex) {
 
            System.out.println("Server not found: " + ex.getMessage());
 
        catch (IOException ex) {
 
            System.out.println("I/O error: " + ex.getMessage());
        }
    }
}

如您所见,此客户端程序一直在运行,直到用户键入“再见”为止。使用以下命令运行它:

1
java ReverseClient localhost 9090

然后它会要求您输入一些文本:

1
Enter text:_

输入一些东西,说“你好”,你应该看到服务器的响应是这样的:

1
2
3
Enter text: Hello
Server: olleH
Enter text:_

您会看到服务器响应“Server: olleH”,其中“olledH”是“Hello”的逆向形式。添加文本“服务器:”以明确区分客户端消息和服务器消息。客户端程序仍在运行,要求输入和打印服务器的响应,直到您键入“再见”以终止它。

保持第一个客户端程序运行,然后启动一个新的客户端程序。在第二个客户端程序中,您将看到它要求输入然后永远挂起。为什么?

因为服务端是单线程的,在忙着服务第一个客户端的时候,后面的客户端就被阻塞了。

让我们在下一个例子中看看如何解决这个问题。

 

4. Java Socket Server Example #3:反向服务器(多线程)

修改服务器的代码以在新线程中处理每个套接字客户端,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.io.*;
import java.net.*;
 
/**
 * This program demonstrates a simple TCP/IP socket server that echoes every
 * message from the client in reversed form.
 * This server is multi-threaded.
 *
 * @author www.codejava.net
 */
public class ReverseServer {
 
    public static void main(String[] args) {
        if (args.length < 1return;
 
        int port = Integer.parseInt(args[0]);
 
        try (ServerSocket serverSocket = new ServerSocket(port)) {
 
            System.out.println("Server is listening on port " + port);
 
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("New client connected");
 
                new ServerThread(socket).start();
            }
 
        catch (IOException ex) {
            System.out.println("Server exception: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
}

你看,服务器为每个套接字客户端创建一个新的ServerThread

1
2
3
4
5
6
while (true) {
    Socket socket = serverSocket.accept();
    System.out.println("New client connected");
 
    new ServerThread(socket).start();
}

ServerThread类的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.*;
import java.net.*;
 
/**
 * This thread is responsible to handle client connection.
 *
 * @author www.codejava.net
 */
public class ServerThread extends Thread {
    private Socket socket;
 
    public ServerThread(Socket socket) {
        this.socket = socket;
    }
 
    public void run() {
        try {
            InputStream input = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
 
            OutputStream output = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(output, true);
 
 
            String text;
 
            do {
                text = reader.readLine();
                String reverseText = new StringBuilder(text).reverse().toString();
                writer.println("Server: " + reverseText);
 
            while (!text.equals("bye"));
 
            socket.close();
        catch (IOException ex) {
            System.out.println("Server exception: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
}

如您所见,我们只是将要执行的处理代码移动到一个单独的线程中,在run()方法中实现。

现在让我们运行这个新的服务器程序并运行几个客户端程序,你会看到上面的问题已经解决了。所有客户端运行顺利。

让我们以不同的方式试验本课中的示例:运行多个客户端,在本地计算机上测试,并在不同的计算机上测试(服务器在一台机器上运行,客户端在另一台机器上运行)。

 

API参考:

套接字类 Javadoc

ServerSocket 类 Javadoc 

 

相关 Java 网络教程:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值