假设我们在Java中有一个简单的Echo客户端/服务器对。据我所知,一旦插座的一侧断开,整个连接就消失了。
但是,如果我想要一个可以永远保持活着的服务器,即使客户端死亡,该怎么办?我希望能够恢复断开的连接。
Echo Server:
import java.net.Socket;
import java.net.ServerSocket;
public class EchoServer {
public static void main(String[] args) throws Exception {
// create socket
int port = 4444;
ServerSocket serverSocket = new ServerSocket(port);
System.err.println("Started server on port " + port);
// repeatedly wait for connections, and process
while (true) {
// a "blocking" call which waits until a connection is requested
Socket clientSocket = serverSocket.accept();
System.err.println("Accepted connection from client");
// open up IO streams
In in = new In (clientSocket);
Out out = new Out(clientSocket);
// waits for data and reads it in until connection dies
// readLine() blocks until the server receives a new line from client
String s;
while ((s = in.readLine()) != null) {
out.println(s);
}
// close IO streams, then socket
System.err.println("Closing connection with client");
out.close();
in.close();
clientSocket.close();
}
}
}
任何提示表示赞赏
答案 0 :(得分:11)
您缺少的是“会话”的概念。想想服务器的位置,当一些位到达“线路”时。怎么办这些?使用TCP / IP,线路上已经存在一些信息,即:
服务器的操作系统使用src / dest addr / port信息来判断这是否是“正在进行的对话”。主要是它考虑dest port
(因为消息已经通过互联网和防火墙传达给机器本身)来决定是否在你的Java程序中监听'dest port'。但是,它使用整个src-addr / src-port / dest-addr / dest-port尝试按发件人发送的顺序将负载交付给您的程序(可能不是他们到达的顺序,因为互联网干预)。请注意,单个“消息”实际上可能会拆分为多个数据包。您的操作系统的TCP / IP堆栈正在为您做一些工作。
但是,请注意,为了代表您执行此功能,操作系统必须投入一些资源来“跟踪TCP / IP会话的状态”。至少,对于每组port / addr src / dest,需要有一个最后收到的“数据包”的计数器,一些缓冲空间来保存数据包,直到你的程序准备好使用它们,等等类推。
现在,TCP / IP堆栈实现者所面临的问题是“我应该在多长时间内”为此状态“?足够10秒? 20分钟?在某些时候,在没有从客户端听到一段时间后,会话必须“超时”。如果序列中有更多的数据包要发送,并且客户端再次开始发送它们,那么服务器必须能够说“对不起,你发送了一些先前消息的数据包234,但因为我没有收到来自你有一段时间了,我丢掉了1-233包。我们可以重新开始吗?“。
所以从这个意义上讲,没有办法阻止客户端“断开连接”。当然,如果客户端恢复并发送更多数据,您可以继续监听套接字。但是你和你的客户需要一种方法来“选择我们离开的地方”。
在HTTP中,这是使用'会话cookie'实现的 - 一个服务器提供给客户端的长唯一字符串,客户端重新发送每个新请求(无论它是否发生在同一个TCP级会话上) )。这是一个“应用程序级会话”,其生命周期比TCP会话的生命周期长。
由于您正在编写应用程序,并且听起来您对客户端和服务器之间可以相互说明的内容(“协议”)有一定程度的控制权,因此您可以选择更多关于如何达成一致的选项什么是会话,如果客户'离开'如何处理事情(超时会话),以及双方将如何恢复并“从我们中断的地方继续”(重新建立会话)。你不要忘记身份验证!
正如其他人所说,这在很大程度上取决于你想要实现的目标。
答案 1 :(得分:5)
当您想要创建服务器和多个客户端时,我们通常做的是创建一个负责服务器和其中一个客户端之间通信的线程,因此每次客户端连接到服务器时,服务器都会启动新的线程并将给它相应的套接字。
代码应该是这样的:
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class EchoServer {
public static void main(String[] args) throws Exception {
ArrayList<ClientHandler> clientHandlersList = new ArrayList<ClientHandler>();
// create socket
int port = 4444;
boolean isOver = false;
ServerSocket serverSocket = new ServerSocket(port);
System.err.println("Started server on port " + port);
// repeatedly wait for connections, and process
while (!isOver) {
// a "blocking" call which waits until a connection is requested
Socket clientSocket = serverSocket.accept();
System.err.println("Accepted connection from client");
ClientHandler handler = new ClientHandler(clientSocket);
handler.start();
clientHandlersList.add(handler);
}
serverSocket.close();
}
}
ClientHandler类:
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class ClientHandler extends Thread {
private Socket socket;
private ObjectInputStream in;
private ObjectOutputStream out;
private boolean disconnect = false;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
in = new ObjectInputStream(socket.getInputStream());
out = new ObjectOutputStream(socket.getOutputStream());
while(!disconnect){
Object message = readMessage();
//DO something
}
} catch (IOException e) {
e.printStackTrace();
}
}
public Object readMessage(){
try {
Object obj = in.readObject();
return obj;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void writeMessage(Object obj){
try {
out.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
}
public void disconnect(){
try {
disconnect = false;
out.close();
in.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
答案 2 :(得分:4)
正如有人指出的那样,你不能用TCP做到这一点。
以下是DatagramSocket
(UDP)回显服务器的简单示例。
package test;
import java.io.*;
import java.net.*;
public class UDPEchoServer {
public static void main(String[] args) throws IOException
{
new ServerThread().start();
}
static class ServerThread extends Thread
{
protected DatagramSocket socket = null;
public ServerThread() throws IOException
{
this("ServerThread");
}
public ServerThread(String name) throws IOException
{
super(name);
socket = new DatagramSocket(4444);
}
public void run()
{
try {
while (true) {
byte[] buf = new byte[256];
// receive data
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
// send back the response to the client at "address" and "port"
InetAddress address = packet.getAddress();
int port = packet.getPort();
packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);
}
}
catch (IOException e) {
e.printStackTrace();
}
socket.close();
}
}
}
更新:添加了测试信息
您可以使用nc
[gustaf@gustf-air]$ nc -u 127.0.0.1 4444
testing echo server
testing echo server
killing it now
killing it now
^C
[gustaf@gustf-air]$ nc -u 127.0.0.1 4444
now we are back
now we are back
答案 3 :(得分:0)
使用代码中显示的套接字api,这应该不是问题。 对于单个连接,如果任何一方被关闭(或者例如死亡) 完整的关联是&#34; down&#34;并且不能再使用了。 这就是您的代码中clientSocket所发生的事情。
但是,serverSocket仍处于打开状态并接受新连接。 对此,您的服务器始终保持活跃状态&#34;。
因此,您显示的代码是套接字api使用的简单示例,用于创建等待连接的服务器并调度处理的单个(包含)调用。
唯一的缺点&#34;您的代码是通过在与accept()相同的线程中进行处理,您的服务器将无法响应新请求 只要它处理普遍的一个。为了避免这种处理可能 委托给一个新线程(如已经指出的那样)。
您可以在accept()上找到有关文档的其他说明。 例如您可以阅读Socket API explanation
请注意,这不是Java问题。 Java只是envlopes并向Java程序提供与套接字通信相关的底层操作系统的功能。这主要是什么 作为Posix标准的一部分记录,实际上这是(关于套接字)只是重复(和聚合)各种RFC指定的内容(有关此规范的详细信息,请参阅https://www.ietf.org/rfc.html)。
如果套接字设置为使用TCP协议,则在一端终止后,套接字保持连接打开(功能完全扩展)。有 - 只是添加completness - 一种机制(关闭),用于声明本地发送端关闭,同时仍然接受进一步的数据输入。 (但我并不认为我们不是在谈论这个非常特殊的功能。) 当连接方拒绝通话时保持连接打开会导致逻辑问题,也将成为资源管理的噩梦。 (服务器保留资源多长时间?)
将TCP与守护进程(在给定端口上不断提供服务的进程)一起使用的标准模式是通过如上所示的accept():
服务器用于执行多个请求的策略 (一个接一个地同步处理,请求队列,每个连接一个线程是一个完全独立的设计决策。它取决于并行度,可用资源等。
服务器将有两种不同的&#34;种类#34;正在使用的插座: