套接字不能像 LAN 或 NET 那样工作,但在本地完美
Sockets don't work as excpected with LAN or NET, but perfectly locally
我正在使用 Java 编写网络软件,但我在通过 "true" 网络使用我的应用程序时遇到了实际问题。
让软件成为主机,监听客户端连接。
这是我的服务器循环:
public void run() {
while (mServerSocket != null) {
try {
Socket wClient = mServerSocket.accept();
System.out.println("Client connecté");
wClient.setSoTimeout(50);
wClient.setTcpNoDelay(false);
Client c = new Client(wClient);
synchronized(this) {
mWaitingClients.add(c);
c.start();
}
} catch(Exception ex) {
System.out.println("Server error : " + ex.getMessage());
}
}
}
当客户端试图连接到服务器时,我使用这个函数:
public Client connect(InetAddress addr, int port) throws Exception {
Socket socket = new Socket(addr, port);
socket.setSoTimeout(50);
socket.setTcpNoDelay(false);
Client c = new Client(socket);
c.start();
return c;
}
这是客户端循环:
public void run() {
try {
ObjectOutputStream out = new ObjectOutputStream(mSocket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(mSocket.getInputStream());
while(mSocket.isConnected() && !mSocket.isClosed()) {
for (int i = 0; i < mOutMessages.size(); i++) {
Message msg = mOutMessages.get(i);
out.writeObject(msg);
}
out.flush();
mOutMessages.clear();
Thread.sleep(50);
out.reset();
while(true) {
try {
Message m = (Message) in.readObject();
mInMessages.add(m);
} catch (Exception e) {
break;
}
}
Thread.sleep(50);
}
} catch(Exception ex) {
try {
mSocket.close();
} catch(Exception exx) {
exx.printStackTrace();
}
ex.printStackTrace();
}
}
程序的其他部分做Message,放到Client的Output列表(mOutMessages)中。
程序的其他部分从Client的mInMessages读取Message。
但这有点不对劲。它在本地运行良好(服务器和客户端在同一台计算机上),但在使用两台计算机(使用 LAN 或通过 Internet)时会失败或有危险(某些消息已发送但从未收到)。
服务器曾经检测到来自客户端的连接,向客户端发送 "handshake" 消息,但客户端从未收到它们。
与 Java 相比,我更像是一名 C 程序员,而且我在使用 libc 套接字时从未遇到过此类问题,那么,为什么我的做法是错误的?
谢谢!
编辑:
我的服务器是使用此函数创建的:
public void open(int port) throws Exception {
mServerSocket = new ServerSocket(port);
start(); // Call the run mentionned above.
}
编辑:
这是我的解决方案,也许它并不完美,但它确实有效!
public void run() {
try {
BufferedOutputStream buf_out = new BufferedOutputStream(
mSocket.getOutputStream()
);
BufferedInputStream buf_in = new BufferedInputStream(
mSocket.getInputStream()
);
ObjectOutputStream out = new ObjectOutputStream(buf_out);
out.flush();
ObjectInputStream in = new ObjectInputStream(buf_in);
while(mSocket.isConnected() && !mSocket.isClosed()) {
for (int i = 0; i < mOutMessages.size(); i++) {
Message msg = mOutMessages.get(i);
out.writeObject(msg);
out.flush();
}
mOutMessages.clear();
out.reset();
while(true) {
try {
Message m = (Message) in.readObject();
mInMessages.add(m);
} catch (Exception e) {
break;
}
}
}
} catch(Exception ex) {
try {
mSocket.close();
} catch(Exception exx) {
exx.printStackTrace();
}
ex.printStackTrace();
}
如果我没理解错的话,client和server都使用了run
方法。如果客户端和服务器碰巧同时写入了足够大的消息(不适合所涉及的缓冲区)那么你会得到一个 死锁 因为双方都没有继续阅读(这会耗尽整个缓冲区) .由于网络延迟,这可能只发生在非本地场景中,即可能有足够的时间在 mOutMessages
缓冲区中堆积足够多的消息。
请注意 Socket.setSoTimeout
的文档(您使用的)仅说明它会影响 read()s。 (例如,在我的JDK中,ObjectOutputStream似乎使用了一个缓冲区大小为1024字节的BlockDataOutputStream)。
我建议为 reading/writing 使用单独的线程,或者(如果您知道最大消息大小)使用足够大的缓冲区(通过将 SocketOutputStream
包装在 BufferedOutputStream
).如果您选择更大的缓冲区,您可能还希望一次写入一条消息(并尝试在每条消息之后读取消息)。
我正在使用 Java 编写网络软件,但我在通过 "true" 网络使用我的应用程序时遇到了实际问题。
让软件成为主机,监听客户端连接。 这是我的服务器循环:
public void run() {
while (mServerSocket != null) {
try {
Socket wClient = mServerSocket.accept();
System.out.println("Client connecté");
wClient.setSoTimeout(50);
wClient.setTcpNoDelay(false);
Client c = new Client(wClient);
synchronized(this) {
mWaitingClients.add(c);
c.start();
}
} catch(Exception ex) {
System.out.println("Server error : " + ex.getMessage());
}
}
}
当客户端试图连接到服务器时,我使用这个函数:
public Client connect(InetAddress addr, int port) throws Exception {
Socket socket = new Socket(addr, port);
socket.setSoTimeout(50);
socket.setTcpNoDelay(false);
Client c = new Client(socket);
c.start();
return c;
}
这是客户端循环:
public void run() {
try {
ObjectOutputStream out = new ObjectOutputStream(mSocket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(mSocket.getInputStream());
while(mSocket.isConnected() && !mSocket.isClosed()) {
for (int i = 0; i < mOutMessages.size(); i++) {
Message msg = mOutMessages.get(i);
out.writeObject(msg);
}
out.flush();
mOutMessages.clear();
Thread.sleep(50);
out.reset();
while(true) {
try {
Message m = (Message) in.readObject();
mInMessages.add(m);
} catch (Exception e) {
break;
}
}
Thread.sleep(50);
}
} catch(Exception ex) {
try {
mSocket.close();
} catch(Exception exx) {
exx.printStackTrace();
}
ex.printStackTrace();
}
}
程序的其他部分做Message,放到Client的Output列表(mOutMessages)中。 程序的其他部分从Client的mInMessages读取Message。
但这有点不对劲。它在本地运行良好(服务器和客户端在同一台计算机上),但在使用两台计算机(使用 LAN 或通过 Internet)时会失败或有危险(某些消息已发送但从未收到)。 服务器曾经检测到来自客户端的连接,向客户端发送 "handshake" 消息,但客户端从未收到它们。
与 Java 相比,我更像是一名 C 程序员,而且我在使用 libc 套接字时从未遇到过此类问题,那么,为什么我的做法是错误的?
谢谢!
编辑: 我的服务器是使用此函数创建的:
public void open(int port) throws Exception {
mServerSocket = new ServerSocket(port);
start(); // Call the run mentionned above.
}
编辑: 这是我的解决方案,也许它并不完美,但它确实有效!
public void run() {
try {
BufferedOutputStream buf_out = new BufferedOutputStream(
mSocket.getOutputStream()
);
BufferedInputStream buf_in = new BufferedInputStream(
mSocket.getInputStream()
);
ObjectOutputStream out = new ObjectOutputStream(buf_out);
out.flush();
ObjectInputStream in = new ObjectInputStream(buf_in);
while(mSocket.isConnected() && !mSocket.isClosed()) {
for (int i = 0; i < mOutMessages.size(); i++) {
Message msg = mOutMessages.get(i);
out.writeObject(msg);
out.flush();
}
mOutMessages.clear();
out.reset();
while(true) {
try {
Message m = (Message) in.readObject();
mInMessages.add(m);
} catch (Exception e) {
break;
}
}
}
} catch(Exception ex) {
try {
mSocket.close();
} catch(Exception exx) {
exx.printStackTrace();
}
ex.printStackTrace();
}
如果我没理解错的话,client和server都使用了run
方法。如果客户端和服务器碰巧同时写入了足够大的消息(不适合所涉及的缓冲区)那么你会得到一个 死锁 因为双方都没有继续阅读(这会耗尽整个缓冲区) .由于网络延迟,这可能只发生在非本地场景中,即可能有足够的时间在 mOutMessages
缓冲区中堆积足够多的消息。
请注意 Socket.setSoTimeout
的文档(您使用的)仅说明它会影响 read()s。 (例如,在我的JDK中,ObjectOutputStream似乎使用了一个缓冲区大小为1024字节的BlockDataOutputStream)。
我建议为 reading/writing 使用单独的线程,或者(如果您知道最大消息大小)使用足够大的缓冲区(通过将 SocketOutputStream
包装在 BufferedOutputStream
).如果您选择更大的缓冲区,您可能还希望一次写入一条消息(并尝试在每条消息之后读取消息)。