通信基础小结
通信连接实现:
(服务器创建,客户端连接,文字流发送接收,线程实现群聊,客户端界面)
1.ServerSocket和Socket类
使用ServerSocket server=new ServerSocket(port)语句在指定端口创建服务器,等待客户机的连接。通过cmd输入服务器的IP和端口号即连上服务器。
若要实现群聊,即多个客户端同时连上服务器,即避免accept()方法的阻塞,可以为每位客户端创建单独的线程。
2.文字传输
由Socket对象得到Input和Output流,以回车作为一句聊天语句的结束标志,“Bye“为结束聊天的标志对流进行写入和读取操作。
3.客户端界面实现:代替cmd,创建登录和聊天界面,可视化。
经分析,客户端与服务器基本功能既不能一致,即创建Socket对象,连接上上服务器,按照服务器收发信息流的方法同样接收于发送文本。
协议初识:(协议概念提出,实现xmpp协议)
1.协议概念:
让不同地理位置的通信系统,协同工作实现信息交换和资源共享的一种共同语言。它定义了一个让交流双方共同遵守的规则。这就是协议。简单说,交流什么,怎样交流。怎么样把计算机最底层的数据流分割,按照怎样的协议把一串串无规律的0,1传翻译成信息。
2.xmpp协议:
上面我们自定义的简单协议可以实现简单的文本聊天功能,但明显存在很多缺陷。所以便提出xmpp协议。
所谓xmpp协议,即The Extensible Messaging and Presence Protocol(可扩展通讯和表示协议).它是基于xml(可扩展标记语言)的。
其核心就是把每条信息用<></>这样成对出现的括号把信心封装起来,读取时解析即可。
如:
1.登录请求:
<msg><type>login</type><name>user</name><psw>password</psw></msg>
2.登录应答:
<msg><type>loginResp</type><state>登录结果</state></msg>
3.聊天信息:
<msg><type>chat</type><sender>sender</sender></receiver>receiver</receiver><content>消息内容</content></msg>
4.注册消息
5.注册应答
6.在线用户列表
7.上线消息
8.下线消息
9.传送文件
10.传送图片
等等。。。。
这种方式的扩展性很强,不论是发送什么类型的信息,都可以把标记值按照此方式封装。不会丢失,处理方式很简便统一。
解析方法包括重要的两部分:
1.从流中读取并解析出一条xml消息
private String readString() throws IOException {
String msg = "";
int i;
i = ins.read();
StringBuffer stb = new StringBuffer();
boolean end = false;
while (!end) {
char c = (char) i;
stb.append(c);
msg = stb.toString().trim();
if (msg.endsWith("</msg>")) {
break;
}
i = ins.read();
}
// 在此处,转化时必须使用GBK编码,即读到的消息编码为中文编格式,否则会乱码
msg = new String(msg.getBytes("ISO-8859-1"), "GBK").trim();
return msg;
}
2.解析标记后的内容:
static String getXMLValue(String flagName, String xmlMsg) throws Exception {
try {
// 1.<标记>头出现的位置
int start = xmlMsg.indexOf("<" + flagName + ">");
start += flagName.length() + 2;
// 2.</标记>结束符出现的位置
int end = xmlMsg.indexOf("</" + flagName + ">");
// 截取标记所代表的消息的值
String value = xmlMsg.substring(start, end).trim();
return value;
} catch (Exception ef) {
throw new Exception("解析" + flagName + "失败:" + xmlMsg);
}
}
功能扩充:(文件传输,网络画板,整合)
功能扩充就是传送不同形式的信息,如图片,文件,或者远程控制等,其本质还是流的相互收发,只是流封装的内容不同,如最简单的传送直线,一次性发送起始和终点的坐标,画笔粗细颜色等。接收发按照协议接收解析找一样的顺序画出即可。
这里举传送的文件的例子,需要强调的是,为了缓解大文件传输速度慢的问题,用包装后的Data流可以适当加快传输速率。
1.客户端向服务器发送文件:
public void sendFile(String sender, String receiver, String path,String FileName) {
DataOutputStream dous = new DataOutputStream(out);
try {
// 构造输入流
InputStream ins = new FileInputStream(path);
// 文件数据长度
int fileDataLen = ins.available();
// 定义存储文件内容的byte数组
byte[] fileData = new byte[fileDataLen];
// 读入文件数据
ins.read(fileData);
// 文件头信息
String fileHeadXml = "<msg><type>"+"file"+"</type>" + "<sender>" + sender
+ "</sender><receiver>" + receiver
+ "</receiver><FileName>" + FileName
+ "</FileName><FileLen>" + fileDataLen + "</FileLen></msg>";
System.out.println("写入时文件总长度为:" + fileDataLen);
// 以字符串形式写入服务器的流中
dous.write(fileHeadXml.getBytes());
// 文件内容,与文件头信息分开传送。
dous.write(fileData);
// dous.flush();
} catch (Exception ef) {
ef.printStackTrace();
}
}
2.服务器转发给某客户端
private void processChat(Socket client) throws Exception {
if(checkLogin()){//登录成功
ChatTools.addPT(this.userName,this);//加入到集合中
while(connect){
String msg=readServer();//读取客户端发来的一条信息
String type=getXMLValue("type",msg);
if(type.equals("chat")){//聊天消息
................
}else if(type.equals("file")){//发送的是文本信息
String senderName=getXMLValue("sender",msg);//解析发送者
String destUserName=getXMLValue("receiver",msg);//j解析接受者
int dataNum=Integer.parseInt(getXMLValue("FileLen",msg));//文件内容大小
DataInputStream dins=new DataInputStream(ins);//Data输入流
byte[] data=new byte[dataNum];//定义byte数组
dins.readFully(data);//一次性读取
String content=new String(data);//转化为String类型
//截取用户的名字发送,先发送文件头,再发送文件内容
ChatTools.castFile(senderName, msg, destUserName);
ChatTools.castFile(senderName, content, destUserName);
}
}
}
}
给某用户发送文件信息:
public static void castFile(String userName,String msg,String destUserName){
for(int i=0;i<map.size();i++){
ServerThread st=map.get(destUserName);//取出队列中一个线程
if(null!=st&&st.getMyUserName().equals(destUserName)){
st.sendToServer(msg);
break;
}
}
3.客户端接收到文本解析:
public void run() {// 解析服务器转发来的流中的信息,不同工具解析不同的信息
try {
while (connOK) {// 连接上服务器
String xmlMsg = readString();// 接受一条信息
String type = getXMLValue("type", xmlMsg);// 分析出type值
if (type.equals("chat")) {// 聊天信息
..............
}
if (type.equals("file")) {// 发送文件信息
String sender = getXMLValue("sender", xmlMsg);// 发送者名字
JOptionPane.showConfirmDialog(null, "收到来自" + sender
+ "的文件,是否接收?", "Option", 0);
int state = JOptionPane.YES_NO_OPTION;
if (state == 1) {// 不接受
} else if (state == 0) {// 接收文件传输
//弹出保存文件选择器
JFileChooser jfc=new JFileChooser();
jfc.setDialogTitle("保存文件");
jfc.setApproveButtonText("保存");
jfc.showOpenDialog(null);
//将要保存的文件名和路径
String fileName=jfc.getSelectedFile().getName();
String path=jfc.getSelectedFile().getAbsolutePath();
int fileDataLen = Integer.parseInt(ClientConn
.getXMLValue("FileLen", xmlMsg));
readFileContent(fileDataLen, fileName,path);
}
}
读取文件内容的方法:
private void readFileContent(int fileDataLen, String fileName,String path) {
// 创建新文件
File newFile = new File(path);
//包装流
DataInputStream dins = new DataInputStream(ins);
byte[] data = new byte[fileDataLen];
try {
// 一次性读取并写入到数组中
dins.readFully(data);
// 得到文件输出流
FileOutputStream fous = new FileOutputStream(newFile);
fous.write(data);// 写入文件内容数据
fous.flush();
fous.close();
} catch (IOException e) {
e.printStackTrace();
}
深入了解:(阻塞,线程异步问题,NIO模式——MINA框架)
项目开发: ——————————未完待续!