网络通信
一.常用的网络命令
ping——用来查看网络是否通畅,不能用来证明主机上是否开放了某个端口
telnet——如果网络通畅,可以用telnet命令连接对方的端口,如果能连接上,证明对方端口打开。
netstat——打印出电脑与其他服务器建立的TCP连接或UDP连接信息。
二.网络通信的基本原理 服务器与客户端的通信 主要实现方法
第一步,创建服务器 ServerSocket server = new ServerSocket(端口号);
第二步,等待其他客户机来连接 java.net.Socket client = server.accept(); 注:调用server.accept()方法会阻塞程序。
第三步,从Socket连接对象上调用方法得到输入输出流 java.net.Socket client = server.accept(); OutputStream out = client.getOutputStream(); InputStream ins = client.getInputStream();
第四步,使用输入输出流对象进行数据的读写 String s="汉字charactor123\n\r"; byte[] data=s.getBytes(); out.write(data); out.flush(); client.close(); 将上述代码整合就可以得到一个基础的通信程序。 但是这个服务器只能用来等待一个客户端的连接,一旦客户机连接上来收到服务器发出的一条消息后服务器就退出了。而事实上我们的服务器是一对多的持续处理数据。为了实现服务器的持续等待新的客户机接入,我们可以将accept方法放入一个循环中:
第五步,设定服务器顺循环等待 while(true){ Socket client=server.accept(); }
第六步,读取字符 通过in=ins.read()语句可以将一个字符读取到in变量中,要注意这个字符是原字符在服务器端先转化成字节,然后将字节信息通过信道传到客户端,客户端将字节转型成字符型得到的字符。每次只能读一个,为了能够循环读取服务器发来的字符,可以使用while语句。例如: while(in!=13){ in=ins.read(); System.out.println(in); } 当服务器发来的不是回车时,客户机可以源源不断地接收服务器发来的字节。
第七步,读取字符串 上一步中实现客户机了一个个字符的读取,但是在很多情况下,我们的信息不是以一个一个的字节为单位发送的,倘若我们要读取的是一整个字符串(一次性读取很多个字节),可以定义这样一个函数: private String readString(InputStream ins) throws Exception{ StringBuffer str=new StringBuffer(); char c=0; while(c!=13){ int i=ins.read(); c=(char)i; stb.append(c); } String inputS=stb.toString().trim();//这个函数用来去掉字符串尾部的空格 return inputS; } 这样,当我们需要客户机接收一个字符串的时候,可以调用这个函数,例如: String inputS=readString(ins); while(!inputS.equals("bye")){ data=s.getBytes(); out.write(data); out.flush(); inputS=readString(ins); } 这段代码的意思是不断地通过readString函数读取客户端那边发来的字符串,如果字符串不为"bye"那么就不断循环读取。 通过上述的知识,一个可以进行字符串通信的服务器客户机就可以实现了。 但是现在的通信程序依然是一对一的,即一个服务器同一时刻只能跟一个客户端通信,而现实生活中的服务器如果一个时刻只能跟一个客户端通信显然太无效率。要实现一对多的服务,我们需要进行多线程服务器的改造。
第八步,多线程服务器 其实只需要将每个客户端节点放入服务器的一个线程就可以,请参考以下代码 public class ServerThread extends Thread{ private Socket client; //线程对象要处理的连接变量 private OutputStream out; //输出流对象 public ServerThread(Socket sc){ //传入一个要处理的连接对象,即每个线程均含有一个主要的客户端 this.client = sc; } public void run(){ processChat(this.client);//对客户端的操作都写在这个函数里 } } 然后在服务器创建客户端节点的时候把创建的节点传入一个新的线程并启动这个线程就可以了 ServerThread st=new ServerThread(client); st.start(); 这样,一个像模像样的多线程服务器就能实现了
在这个过程中我们需要注意的问题有: 1.从服务器到客户端与从客户端到服务器发送与接收信息的方法是完全相同的,即只要建立了连接之后,知道了从服务器向客户端发送信息的方法,同样的原理就可以写出从客户端到服务器发送消息的代码。 2.如果按照上述提供的方法进行字符串传输时,不能正常传输汉字。因为每个汉字由两个字节组成,在发送数据时,我们把汉字化成了两个字节,接收后又把每个字节转换成了相应的字符,相当于把原来的一个汉字转换成了两个字符,显示出来的是乱码。比较好的解决办法是用DataInputSteam与DataOutputStream把信息按照数据类型的方式发送接收与还原。但是底层的实现依然是通过字符的传递来实现的。 3.目前我们的通信程序只能传递字符串。如果改用DataInputStream DataOutputStream可以传送整数或其他基本数据类型。如何实现多种数字类型的传递?这需要我们设计自己的传送格式,例如在信息前事先发送信息的大小、信息的类型等信息,而接受的时候每次也先对接收到的头几位格式进行分析判断进行不同的操作。只要发送端与接收端信息格式一直保持一直,就能实现多种类型信息的传输。 4.我比较讨厌通信的主要原因是,通信是即画图板的保存、哈弗曼压缩后有一个跟位运算关系密切的课程。位运算很不直观,只要位对不齐或者长度出现问题就会出现各种错误。通信阶段极容易出现字符数量或者格式错误的情况,在文件传输的时候要慎重。 5.关于多人聊天。要用到队列哦,即将所有客户端的线程放到一个队列里,每当服务器收到一条消息,就将这条消息发给所有的客户端。
下面是在学习中遇到的一些重点 1.对于阻塞状态 某些进入阻塞状态的方法只有等到特定动作发生后才能继续进行 这样会影响到后继方法的调用 所以对于会发生阻塞状态的代码 一般都要放进独立的线程中去执行 2.程序代码中的封装思路 封装是面向对象的一大特性但是对于如何封装 为何封装都需要在实践中自己体会 封装的典型思路:方法的设计要考虑以后变化的需求 3.内存泄露问题 当某些存放在队列中的线程退出后 并没有从保存它的队列中移除 所以在测试时往往不能体现出来 如果应用到某些大访问量的服务器 则很快就会导致内存溢出 所以要在程序中定义一个从队列中移除的方法
<!--EndFragment-->