希望代码本身解释这个问题:
class Server {
public void main() {
// ...
ServerSocket serverSocket = new ServerSocket(PORT);
while (true) {
Socket socket = serverSocket.accept();
Thread thread = new Thread(new Session(socket));
thread.start();
}
// ..
}
public static synchronized Session findByUser(String user) {
for (int i = 0; i < sessions.size(); i++) {
Session session = sessions.get(i);
if (session.getUserID().equals(user)) {
return session;
}
}
return null;
}
}
class Session {
public Session(Socket socket) {
attach(socket);
}
public void attach(Socket socket) {
// get socket's input and output streams
// start another thread to handle messaging (if not already started)
}
public void run() {
// ...
// user logs in and if he's got another session opened, attach to it
Session session = Server.findByUser(userId);
if (session != null) {
// close input and output streams
// ...
session.attach(socket);
return;
}
// ..
}
}
我的问题是:在Server.findByUser方法中发布会话引用是否安全,是否违反OOP样式等? 或者我应该通过一些不可变的id引用会话并封装整个事物?你还会改变其他什么吗?
String sessionId = Server.findByUser(userId);
if (sessionId != null && sessionId.length() > 0) {
// close input and output streams
// ...
Server.attach(sessionId, socket);
return;
}
托马斯:
感谢您的回答。
我同意,在现实世界中,在创建Session
的新实例时使用依赖注入是个好主意,但是也可能使用接口,对(代码如下)?尽管我可能应该对其进行单元测试,但我们可以考虑不这样做。然后我只需要一个Server实例。 使用静态方法而不是单线方法会是一个巨大的OO犯罪吗?
interface Server {
Session findByUser(String user);
}
class ServerImpl implements Server {
public Session findByUser(String user) { }
}
class Session {
public Session(Server server, Socket socket) { }
}
关于attach(...)
方法的好点 - 我从来没有考虑过继承Session
类,这可能就是为什么我没想到在构造函数中调用public方法会有多么糟糕。但实际上我需要一些公共方法将会话连接到不同的套接字,所以可能是一对方法?
class Session {
public Session(Socket socket) {
attach_socket(socket);
}
public void attach(Socket socket) {
attach_socket(socket);
}
private void attach_socket(Socket socket) {
// ...
}
}
允许Session的客户端呼叫attach(...)
似乎不对。这可能只是服务器应该访问的那些严重的方法之一。如果没有C ++的友谊关系,我该怎么办呢?不知怎的,内心阶级浮现在我的脑海中,但我没有多想,所以这可能是一条完全错误的道路。
每次收到新连接时,我都会生成一个新线程(并创建一个与之关联的新Session实例)来处理传输。这样,当用户发送登录命令时,服务器已准备好接受新连接。一旦用户的身份得到验证,我就会检查他是否已经登录(还有另一个正在进行的会话)。如果他是,那么我从它的套接字分离onging会话,关闭该套接字,将正在进行的会话连接到当前套接字并关闭当前会话。希望这更清楚地解释实际发生了什么?也许在这里使用单词 session 有点不幸。我真正拥有的是为每个连接(和3个线程)创建的4个不同的对象:套接字处理程序,消息发送方,消息接收方和会话(如果它是一个很好的解决方案,这是一个不同的问题......)。我只是尝试简单地将源代码集中在这个问题上。
我完全同意,当您可以使用地图时迭代会话列表是没有意义的。但我担心这可能是我正在努力解决的代码中的一个较小的问题(相信我)。我应该已经提到它实际上是一些遗留系统,毫不奇怪,最近发现它有一些并发性和性能问题。我的任务是修复它......当你几乎只掌握多线程的理论知识或只用它来显示进度条时,这不是一件容易的事。
如果在此之后,相当冗长,澄清你对架构的更多见解,我会非常愿意倾听。
答案 0 :(得分:1)
首先应该创建Server类OO(即非静态)并在Session类中使用dependency injection:
class Server {
public Session findByUser(String user) { }
}
class Session{
public Session(Server server, Socket socket){}
}
public void attach(..)
必须是私有的,以确保封装和正确的初始化。子类可能会破坏Session类,否则就像这样:
class BadSession extends Session{
@Override public void attach(Socket socket) {
//this is not initialized at this point
//now the instance is broken
}
}
从客户端调用附件似乎也是无效的。
将Socket附加到会话的责任应该是服务器的一部分。这是决定哪个Session获取哪个Socket的正确位置。据我所知,您正在使用Socket创建一个Session。不知何故,你发现用户已经有一个Session(与另一个Socket)。现在您将当前Session连接到此Socket。现在有旧套接字有两个会话和新套接字没有会话。我认为传统的Session应该有多个套接字而不是另一种方式:
Session session = findSession(userId);
session.attach(socket);
class Session{
List<Socket> sockets;
}
在此更改之后,线程将不会分配给Sessions,而是分配给处理程序,处理一个套接字的输入流并相应地更改Session。
对方法public static synchronized Session findByUser(String user)
使用synchronized不足以确保线程安全。您必须确保查找会话(按用户)和注册会话(如果用户未知)必须原子。语义应该类似于ConcurrentMap的putIfAbsent。 (迭代会话列表无论如何都没有效率。你应该使用Map<Id, Session>
。)
我希望这会有所帮助。