片段1
public void run() {
isRunning = true;
try {
serverSocket = new ServerSocket(port);
}
catch (IOException | IllegalArgumentException e) {
System.out.println("Could not bind socket to given port number.");
System.out.println(e);
System.exit(1);
}
while(isRunning) {
Socket clientSocket = null;
try {
clientSocket = serverSocket.accept();
}
catch (IOException e) {
logEvent("Could not accept incoming connection.");
}
Client client = new Client(clientSocket, this);
connectedClients.put(client.getID(), client);
Thread clientThread = new Thread(client);
clientThread.start();
logEvent("Accepted new connection from " + clientSocket.getRemoteSocketAddress().toString());
client.send("@SERVER:HELLO");
}
}
片段2
public void run() {
isRunning = true;
createServerSocket();
while(isRunning) {
Socket clientSocket = null;
clientSocket = acceptConnection();
addClient(clientSocket);
}
}
private void createServerSocket() {
try {
serverSocket = new ServerSocket(port);
}
catch (IOException | IllegalArgumentException e) {
System.out.println("Could not bind socket to given port number.");
System.out.println(e);
System.exit(1);
}
}
private Socket acceptConnection() {
try {
Socket clientSocket = serverSocket.accept();
}
catch (IOException e) {
logEvent("Could not accept incoming connection.");
}
return clientSocket;
}
private void addClient(Socket clientSocket) {
Client client = new Client(clientSocket, this);
connectedClients.put(client.getID(), client);
Thread clientThread = new Thread(client);
clientThread.start();
logEvent("Accepted new connection from " + clientSocket.getRemoteSocketAddress().toString());
client.send("@SERVER:HELLO");
}
但是,将这样的简单代码分成多个片段似乎是一个过大的选择(尽管它更具可读性-是吗?)。此外,
createServerSockets
有副作用(Sys.exit
),而addClient
一次执行太多操作。有什么建议吗?#1 楼
第二个更容易阅读,它表达了开发人员的意图。乍一看,我(例如,作为维护人员或同一团队中的另一个开发人员)只想快速了解代码,而不关心细节。 (例如,代码是如何创建服务器套接字的。)第二个代码正好说明了这一点。可以消除
System.exit
的副作用:抛出带有适当消息的异常(并且不要忘记确定原因)。然后使用run
方法处理异常。我经常看到如下包装纸:public void run() {
try {
doRun();
} catch (SomeException e) {
// exception handling here
}
}
这通常使
doRun()
更简单易读。其他一些注意事项:
Socket clientSocket = null;
clientSocket = acceptConnection();
可以写为
Socket clientSocket = acceptConnection();
I' d修改
createServerSocket()
方法以返回服务器套接字,并修改acceptConnection()
方法以使其具有ServerSocket
参数。它将消除两种方法之间的时间耦合。不可能以错误的顺序调用它们。我最近没有编写任何线程代码,但是我认为为每个客户创建一个单独的线程不会很好。您可能要在此处使用执行程序/线程池。
#2 楼
您对new ServerSocket(port)
的异常处理可能会更好。我不喜欢将System.exit(1)
埋在辅助函数中的方式。捕获异常的方式应使其绕过clientSocket
-accepting循环。类似地,如果serverSocket.accept()
抛出异常,则将其记录下来,但是无论如何会继续尝试创建客户端线程。相反,创建客户端线程的代码应位于try
块内,以便该异常将导致其被跳过。这是修复异常处理的初步重新安排。
public void run() {
try {
serverSocket = new ServerSocket(port);
isRunning = true;
while (isRunning) {
try {
Socket clientSocket = serverSocket.accept();
Client client = new Client(clientSocket, this);
connectedClients.put(client.getID(), client);
Thread clientThread = new Thread(client);
clientThread.start();
logEvent("Accepted new connection from " + clientSocket.getRemoteSocketAddress().toString());
// The "hello" should be sent by the clientThread code instead
// client.send("@SERVER:HELLO");
} catch (IOException e) {
logEvent("Could not accept incoming connection.");
}
}
} catch (IOException | IllegalArgumentException e) {
System.out.println("Could not bind socket to given port number.");
System.out.println(e);
System.exit(1);
}
}
如果您想减轻这种方法的负担,可以创建“客户端任务”以放入“客户端处理池”中。那么此
run()
方法可能是非常通用的。如上所述,
client.send("@SERVER:HELLO")
属于clientThread
代码,而不属于此循环。#3 楼
我不喜欢任何一种解决方案(或当前建议的答案)。在运行ServerSocket的任何应用程序中,我相信套接字管理都应该在Main线程上完成。这样可以避免出现System.exit(1)
的确切情况。服务器应用程序的基本模式是:public static void main(String[] args) {
// sort out the arguments, if any.
int port = 12345; // default, args can override
// open the ServerSocket to ensure you can reserve the port befor you do expensive setup.
try (ServerSocket serverSocket = createServerSocket(port)) {
// Initialize the system
setup();
listenForClients(serverSocket);
} catch (IOException ioe) {
// deal with exception
// can do System.exit(1) in the main thread to set exit codes...
// but my personal preference is to throw an exception from the main method.
// whihc exits with 1, and still does a clean shutdown.
}
}
ClientThread设置有些错误。如果希望客户机线程在主线程停止时死亡,则应将它们设置为守护程序线程:
Thread clientThread = new Thread(client);
clientThread.setDaemon(true);
当所有非守护程序都终止时,Java系统将终止线程停止。如果您唯一的非守护程序线程是主线程(它始终是非守护程序),那么当主线程结束时系统将干净地退出。方式...包括具有终止变量(例如
AtomicBoolean isRunning = new AtomicBoolean(true)
),任何线程都可以将其设置为false,主线程将定期检查...(使用Selector和ServerSocketChannel-一个高级概念)底线是您没有正确设置Daemon线程,因此,需要重量级的
System.exit(1)
。您的ServerSocket管理也应该在主线程上(或者可以通过从主线程派生它,并让主线程正常退出,使其在唯一的非守护进程线程上进行管理。)评论
\ $ \ begingroup \ $
我将在以后添加GUI,所以我认为我需要这个单独的线程。
\ $ \ endgroup \ $
–cafe_
2014年1月26日19:34
评论
\ $ \ begingroup \ $
谢谢-我喜欢您提出的代码。现在看来已经很明显了,但是我之前并没有提出这个想法。我想我也应该将addClient拆分为更多方法。
\ $ \ endgroup \ $
–cafe_
2014年1月26日17:13
\ $ \ begingroup \ $
@ prometh07:是的,很好。我将从registerNewClient()和sendInitialMessage()方法开始。
\ $ \ endgroup \ $
–palacsint
2014年1月26日17:20
\ $ \ begingroup \ $
由于clientSocket不能在其他任何地方使用(即,变量的范围如此之短),所以我什至不理会它。为什么不只是addClient(acceptConnection())?
\ $ \ endgroup \ $
–约书亚·泰勒(Joshua Taylor)
2014年1月27日,0:01
\ $ \ begingroup \ $
1)是的。 2)嗯... serverSocket是成员变量。它不在run()方法外部使用,因此您(和rolfl)就在这里。 3)该代码仅用于教育目的,因此我怀疑一次可以有5个以上的用户(这是一个聊天应用程序-我将在稍后的主要帖子中对其进行更精确的描述)。
\ $ \ endgroup \ $
–cafe_
14年1月27日,0:07
\ $ \ begingroup \ $
@JoshuaTaylor:我也很好。两个可能的缺点:读者不知道acceptConnection()的返回类型;在调试时使用step in会有点困难。
\ $ \ endgroup \ $
–palacsint
2014年1月27日在8:53