服务器的根本创建方法是 创建一个ServerSocket的对象,让对象在某个端口上监听。
调用accept方法阻塞端口,如果有客户机连接上了服务器的该端口,然后启动一个新的socket与该客户机进行通信。
如果把通信的处理方法放在一个线程类里面,就可以实现多人同时访问,即多线程处理。
服务器的多线程处理类(多人聊天室)
从宏观上来看,服务器主要是做两件事情:
1、从客户端得到输入的内容;
2、将输入的内容接收后写到其他的客户端。
从这里就可以明白,服务器处理方法所需要的方法或者属性变量如下:
需要一个存放连接对象的列表,并提供对列表中连接对象的增加和删除操作。(存放线程列表,里面的线程是用来操作连接对象socket的)
需要一个获取客户端输入的方法,该方法通过客户端的readLine方法返回一个字符串,然后服务器将以流的形式,写到每一个客户端中
需要一个将某个客户端发过来的消息转发全部客户端的一个方法。遍历每个元素,然后写给客户端。
处理类的本质其实是一个实现了多线程功能的 socket对象的包装类,在这个包装类中,所有的socket对象都可以从中获取到读写流对象,并且有读写方法,这样以来,服务器就不要担心如何去处理每个连接,它要做的只是将每个请求回应,然后将请求转交给处理类,即线程去处理。
其实可以说整个服务器的处理核心就是这个处理类,这个类的处理流程如下:
当创建了一个连接socket对象的线程之后,就将这个线程对象放入列表当中去,列表是静态的,所以可以让各个线程同时访问,或者说可以让各个线程共享这个变量。
之后就是将欢迎辞,发到各个线程所对应的socket对象,因为每个对象都在处理类中被包装了,所有的对象都有自己的读写流,则可以在每个单独的线程中去写信息。实现聊天室的效果。线程一直在等待读取客户端输入的数据,只要有输入,就会马上将数据反应到每个客户端上来。
如果输入某个字符,比如说“bye”就让其自动退出,将该线程多对应的连接对象close,将列表中该线程移除。
总体来说,服务器端的开发流程和要注意的地方已经介绍完了,最重要的一点就是,服务器其实只是在等待,处理线程类才是最终的“劳动者”。了解和熟悉服务器的工作流程对以后客户端课服务器的同时开发有着不可或缺的作用。
import java.net.ServerSocket;
public class ChatServerV3 {
//程序入口
public static void main(String[] args) {
ChatServerV3 ms=new ChatServerV3();
int port =9999;
ms.setUpServer(port);
}
//在指定端口上启动服务器
public void setUpServer(int port){
try{
ServerSocket sc=new ServerSocket(port);//在指定端口上构造一个服务器对象
new ChatTools().serverUI();
ChatTools.information.setText("简单聊天服务器创建成功,等待在端口: "+port+"\n");
while(true){//让服务器进入循环监听状态
java.net.Socket client=sc.accept();
ChatThread ct=new ChatThread(client);
ct.start();//启动一个线程,去处理这个客户机的连结对象
}
}catch(Exception ef){
ef.printStackTrace();
}
}
}
package cn.netjava.C_S.v3; import java.awt.Color; import java.awt.FlowLayout; import java.io.IOException; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextArea; public class ChatTools { //服务器显示框 static JTextArea information =new JTextArea(" "); static JLabel count= new JLabel(); static long history=0; //服务器内部用以保存所有客户线程对象的列表对象 static List<ChatThread> clientList=new ArrayList<ChatThread>(); //1.根据用户名,密码认证用户的方法,如果认证通过,返回true; public static boolean authUser(String userName,String pwd){ //返加Map中是否有这个用户名做key,这就要求用户名必须是唯一的 return userInfoMap.containsKey(userName); } //2.将一个连上来并通过认证的的客户机对象加入到队列中 public static void addClient2Queue(ChatThread ct){ clientList.add(ct); history++; } //3.从队列中移除 用户名所代表的客户机对象 public static void removeClient(String userName){ for(int i=0;i<clientList.size();i++){ ChatThread cs=clientList.get(i); if(cs.getUserName().equals(userName)){ clientList.remove(i); } } } //4.调用后,将userName用户发的消息发给所有客户机 public static void castMsg(String userName,String msg){ for(int i=0;i<clientList.size();i++){ ChatThread cs=clientList.get(i); cs.sendMsg2Me(userName+"说:"+msg+"\n"); } } //服务器端用以保存用户名,密码的列表,表中的键为用户名,值为密码 private static Map<String,String> userInfoMap=new HashMap<String,String>(); //静态块:用做在系统初始时,初始化表户信息表中的用户名和密码,此处模拟生成数据 static { for(int i=0;i<10;i++){ String key="user"+i; String value="pwd"; userInfoMap.put(key,value); javax.swing.SwingUtilities.updateComponentTreeUI(ChatTools.information); } } public JFrame serverUI(){ JFrame serverFrame= new JFrame("多人聊天室服务器监控端"); count.setSize(800, 20); serverFrame.setSize(800,600); serverFrame.setDefaultCloseOperation(3); serverFrame.setLayout(new FlowLayout()); information= new JTextArea(30,60); information.setEditable(false); information.setBackground(Color.decode("#33CCCC")); information.setWrapStyleWord(true); serverFrame.add(count); serverFrame.add(information); serverFrame.setVisible(true); return serverFrame; } }
package cn.netjava.C_S.v3; import java.text.DateFormat; import java.util.Date; public class DateProcess { public String process(){ DateFormat df = new java.text.SimpleDateFormat("yyyy年MM月dd日-hh:mm:ss"); return df.format(new Date()); } }
package cn.netjava.C_S.v3; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; public class ChatThread extends Thread{ //构造这个线程对象时,传客户机连结对象,在线程中处理. public ChatThread(Socket client){ this.client=client; } public void run(){ processClient(this.client);//调用处理客户端的方法 } //对外提供的调用接口,将消息发送给我代表的客户端 public void sendMsg2Me(String msg){ try{ ous.write((msg+"\r\n").getBytes()); ous.flush();//将消息发送到客户机 }catch(Exception ef){} } // 处理客户机连结对象socket private void processClient(java.net.Socket client){ try{ InputStream ins= client.getInputStream(); ous= client.getOutputStream(); BufferedReader br=new BufferedReader(new InputStreamReader(ins)); //1.验证用户名和密码是否合格,如果不合格,则关闭连结,方法执行在此中断 String msg = "请输入用户名:"; sendMsg2Me(msg); BufferedReader breader=new BufferedReader(new InputStreamReader(ins)); userName=breader.readLine();//读取用户输入的用户名,这个对象的userName属性赋上了值 msg="请输入你的密码:"; sendMsg2Me(msg); String pwd=br.readLine();//读取用户输入的密码 //1.验证用户名和密码是否合格,如果不合格,则关闭连结,方法执行在此中断 if(!ChatTools.authUser(userName, pwd)){ msg="用户名或密码错!请询问管理员"; sendMsg2Me(msg); client.close(); return ; } ChatTools.addClient2Queue(this); //2.将这个线程对象放入服务器列表 String input=""; ChatTools.castMsg(userName,"我来啦!(已经加入本聊天室)"); ChatTools.count.setText("当前在线客户端: "+ChatTools.clientList.size()+" ,总共有 "+ChatTools.history +"个客户端连接过本服务器"); String temp = ChatTools.information.getText(); String time = (new DateProcess().process()); ChatTools.information.setText(temp+time+"------"+userName+" 已经与服务器建立连接...IP地址: " +client.getRemoteSocketAddress()+"\n"); javax.swing.SwingUtilities.updateComponentTreeUI(ChatTools.information); //如果客户机发送 bye ,退出客户机 while(!input.equalsIgnoreCase("bye")){ input=br.readLine();//循环读取客户机的输入 ChatTools.castMsg(userName,input);//3.将读到的这条消息发送给客户机: } client.close(); }catch(Exception ef){ ef.printStackTrace(); } ChatTools.castMsg(userName,"我走了!(已经退出本聊天室)"); String temp = ChatTools.information.getText(); String time = (new DateProcess().process()); ChatTools.information.setText(temp+time+"------"+userName+" 已经与服务器断开连接...IP地址: " +client.getRemoteSocketAddress()+"\n"); javax.swing.SwingUtilities.updateComponentTreeUI(ChatTools.information); ChatTools.removeClient(userName);//4.至此,这个客户机己退出,将其从列表中移除, ChatTools.count.setText("当前在线客户端: "+ChatTools.clientList.size()+" ,总共有 "+ChatTools.history +"个客户端连接过本服务器"); } //得到这个处理线程对象所代表客户机的用户名字 public String getUserName(){ return this.userName; } private String userName;//客户机名字,要在类中其它方法中调用,所以设置为属性 private OutputStream ous;//输出流对象,要在类中其它方法中调用,所以设置为属性 private Socket client;//客户机连结对象 public Socket getClient() { return client; } }