通信基础小结
通信连接实现:
(服务器创建,客户端连接,文字流发送接收,线程实现群聊,客户端界面)
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框架)
项目开发: ——————————未完待续!