模型会有很多。
写一些我看到过的模型吧。
大都是C/S模型,分为client端 和 server端,client端通过servet端与其他client端实现通信。
db模型:负责client端的登陆验证等操作。
重点在实现通信的网络模型管理上的不同。
一、
多线程模型
client端登陆的时候会想servet端db验证username和password,
验证的时候发起TCP连接
s = new Socket("127.0.0.1",8000);
ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream());
oos.writeObject(o);
返回success的话,就在客户端起动一个线程
//每当成功登陆一个客户端,就建立一个客户端连接服务器的线程,用于接收服务器端发送的信息
//以及获取当前的Socket来发送信息
ClientToServerThread clientToServerThread = new ClientToServerThread(s);
clientToServerThread.start();
线程内部run方法不停的循环监听来自服务端的推送信息
public void run(){
while(true){
try {
//获取服务器端返回的信息
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
//获取到了Message对象,但是如何传递到Chat里面呢?利用一个类
Message m = (Message) ois.readObject();
//System.out.println(m.getSender()+"给"+m.getGetter()+"说"+m.getCon());
Chat chat = ManageChat.getQqChat(m.getGetter()+" "+m.getSender());
chat.display(m);
} catch (Exception e) {
e.printStackTrace();
}
}
}
要注意的是聊天应用的特性,socket的输入流要监听来自服务端的推送(服务端的推送信息要被展现到client端的聊天界面上),不过还要监听client端本身的输入,在点击发送之后将client端本身的输入通过socket的输出流发送到服务器端,好比cosole界面上也是要有输入的。
在Chat聊天面板的按钮监听中,通过Manager类获得与Chat相关的Socket对象,
public void actionPerformed(ActionEvent arg0) {
if (arg0.getSource().equals(button)) {
Message message = new Message();
message.setSender(myself);
message.setGetter(friend);
// 将textField里面的内容加载到message类当中
message.setCon(textField.getText());
SimpleDateFormat formatter = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
String dateString = formatter.format(new java.util.Date());
message.setSendTime(dateString);
textField.setText("");
textArea.append(message.getSender() + " " + message.getSendTime()
+ ":" + "\n" + message.getCon() + "\n");
try {
//在chat聊天窗口获得socket
Socket s = ManageClientThread.getClientThread(message.getSender()).getSocket();
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}在Socket的输出流当中将数据输出
Server端:
每个client端与server端建立连接之后都会在server端都建立一个连接线程,线程run方法也是不断监听来自client端的输入,如client1跟server建立连接,client2跟server建立连接,client1在chat面板上输入信息“Hello client2!”,server端接收到信息之后,将检查信息的发送对象是1,接收对象是2,于是找到2跟server端的连接线程,将数据通过2连接线程的socket输出流写出。
简单点对点聊天通信协议:
public class Message implements java.io.Serializable{
private String messageType;
private String sender;
private String getter;
private String con;
private String sendTime;
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getGetter() {
return getter;
}
public void setGetter(String getter) {
this.getter = getter;
}
public String getCon() {
return con;
}
public void setCon(String con) {
this.con = con;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getmessageType() {
return messageType;
}
public void setmessageType(String messageType) {
this.messageType = messageType;
}
}
利用了Java自身的序列化机制,将Message对象通过网络进行传播(首先我们的client端server端都是java写的,所以能无差别序列化反序列化,不过如果不是同一种语言,这种序列化机制会无法使用,此时可以使用xml,json或者protocolbuffer 这样的数据格式进行数据传输,当然,我们自己定义数据格式也是可以的)由于使用java自身序列化方式,所以TCP协议粘包问题这里也不用考虑
message协议的规范大概是登陆注册类型和消息传递类型两种,
登陆使用的协议是
public class UserBean implements java.io.Serializable{
private String userId;
private String passwd;
private String infoType;
//infoType = 1 表明是登陆信息
//infoType = 2 表明是注册信息
public String getInfoType() {
return infoType;
}
public void setInfoType(String infoType) {
this.infoType = infoType;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}在登陆注册类型当中其实需要加入一个result字段用于标示成功或者失败,这里当时迷糊使用了Message对象来表示是否登陆成功或者失败,中字段messagetype 1用来成功 2 用来失败
public UserInfoSendToCheck(Object o){
try{
s = new Socket("127.0.0.1",8000);
ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream());
oos.writeObject(o);
System.out.println("haha");
ObjectInputStream ois=new ObjectInputStream(s.getInputStream());
UserBean user = (UserBean)o;
Message message=(Message)ois.readObject();
//表明用户登陆成功
if(user.getInfoType().equals("1")){
if(message.getmessageType().equals("1"))
{
//每当成功登陆一个客户端,就建立一个客户端连接服务器的线程,用于接收服务器端发送的信息
//以及获取当前的Socket来发送信息
ClientToServerThread clientToServerThread = new ClientToServerThread(s);
clientToServerThread.start();
ManageClientThread.addClientThread(user.getUserId(),clientToServerThread);
flag_login=true;
}else if(message.getmessageType().equals("2")){
flag_login = false; //表示登陆失败
}
}
if(user.getInfoType().equals("2")){
if(message.getmessageType().equals("3")){
flag_register = true;
}
else{
flag_register = false;
}
}
}catch(Exception e){
e.printStackTrace();
}
}
上面都是一些具体实现了,不过题主问的是聊天室,上面讲述的都是点对点的聊天,聊天室,或者说聊天群应该怎么实现呢?在上面的基础之上实现聊天室也很简单,比如建立一个多人聊天室,发送信息的时候使用新的聊天室协议,协议中附带有所有群成员的name,这样就找到所有群成员跟server的连接,将message发送过去就可以了。
二、
上面的例子使用了TCP模型,于是可以建立一个client端跟server端的线程,同时建立一个servet端跟client端的线程用于监听socket数据。
上面还实现了点对点聊天,正是因为点对点聊天,所以需要启动线程在run方法当中while循环监听socket数据。
下面举这个例子‘http://blog.sina.com.cn/s/blog_89429f6d01010xvj.html
这个blog上的例子是单独实现了聊天室,但是是有问题的
while (true)
{
//这种不带信息长度的数据读取,在大并发量情况在肯定出问题,因为这个msg读取的可能不只是1条信息,可能多条信息糅杂在一起,也就是TCP粘包问题
String msg = fromserver.readUTF();
if (msg != null)
jta1.append(msg + "\n");
}
Linux下tcp协议socket的recv函数返回时机分析(粘包)