一 .我们知道java的socket是基于TCP的连接,而ServerSocket 的accept()方法是阻塞的,直到有客户端连接到服务器端,我们常用多线程的方式来实现服务器端响应多个客户端,以下是代码:
public class server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器端--开始监听");
while(true){
Socket socket = serverSocket.accept();
ServerHandel hm = new ServerHandel(socket);
Thread t = new Thread(hm);
t.start();
}
//1.socket 的输入输出流任意一个关闭,则socket都不可再用了,所以要关闭就一起关闭了。
//2.socket 流的读是阻塞的,A不要输入流关闭前时,要考虑B端的输出流是否还需要写。否者,B端一直等待A端接收,而A端却接受不了,B一直阻塞,在长连接中尤其要注意,注意流的结束标志
//3.io 流最后一定要关闭,不然会一直占用内存,可能程序会崩溃。文件输出也可能没有任何信息
//4.字符输出推荐使用printWriter 流,它也可以直接对文件操作,它有一个参数,设置为true 可以自动刷新,强制从缓冲中写出数据
//5.缓冲流 都有 bufw.newLine();方法,添加换行
//6.输入流 是指从什么地方读取/输出流是指输出到什么地方
//7.OutputStreamWriter和InputStreamReader是转换流,把字节流转换成字符流
//8.Scanner 取得输入的依据是空格符,包括空格键,Tab键和Enter键,任意一个按下就会返回下一个输入,如果需要包括空格之类的则用bufferreader来获取输入
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
二 这里有2个概念介绍下:
1.短连接: 当输出流发送完毕后,立即关闭流,下次发送,需要新建通信. 会频繁的新建通信,丢弃通信,效率低
2.长连接: 客户端和服务器端都不关闭流, 可以多次发送数据
三 长连接的使用需要注意一些问题:
1.客户端和服务器端,不能关闭任一socket的输入流或者输出流,否则socket通信会关闭
2.由于都不关闭连接,而read方法又是阻塞的,会一直读取数据,不知道何时读取结束,所以要约定通信协议,如特定字符,或者使用包头+包体的方式,传递数据,包头固定长度,里面保存包体长度等信息,这样服务端就知道读取到何时结束了.(本文使用此种方式)以下是代码:
客户端:
public class client {
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket("127.0.0.1",9999);
System.out.println("客户端开始连接");
//一直读取控制台
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true){
//包体
byte [] content = br.readLine().getBytes();
//包头,固定4个字节,包含包体长度信息
byte [] head = Tool.intToByteArray1(content.length);
BufferedOutputStream bis = new BufferedOutputStream(socket.getOutputStream());
bis.write(head);
bis.flush();
bis.write(content);
bis.flush();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
服务器端线程类:
public class ServerHandel implements Runnable {
public static int count = 0;
Socket socket = null;
public ServerHandel(Socket socket){
count++;
this.socket = socket;
System.out.println("用户"+count+"接入");
}
@Override
public void run() {
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(socket.getInputStream());
while (true){
byte [] head = new byte[4];
bis.read(head);
byte [] data = new byte[Tool.byteArrayToInt(head)];
bis.read(data);
System.out.println(new String(data).trim());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
bis.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
四 由于以字节数组传递包头,就涉及到字节数组和int类型转换的问题,java中int类型是4个字节,所以用byte[4]来装,
//int 转字节数组
public static byte[] intToByteArray1(int i) {
byte[] result = new byte[4];
result[0] = (byte)((i >> 24) & 0xFF);
result[1] = (byte)((i >> 16) & 0xFF);
result[2] = (byte)((i >> 8) & 0xFF);
result[3] = (byte)(i & 0xFF);
return result;
}
public static byte[] intToByteArray2(int i) throws Exception {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(buf);
out.writeInt(i);
byte[] b = buf.toByteArray();
out.close();
buf.close();
return b;
}
//字节数组转int
public static int byteArrayToInt(byte[] b) {
int intValue=0;
for(int i=0;i<b.length;i++){
intValue +=(b[i] & 0xFF)<<(8*(3-i));
}
return intValue;
}
五 对于网络IO有一些基本的处理规则如下:
1.减少交互的次数。比如增加缓存,合并请求。
2.减少传输数据大小。比如压缩后传输、约定合理的数据协议。
3.减少编码。比如提前将字符转化为字节再传输。
4.根据应用场景选择合适的交互方式,同步阻塞,同步非阻塞,异步阻塞,异步非阻塞。
六 在jdk1.4种引入了 java.nio.* 包, 大大提高了io的性能,并加入了非阻塞io模型,引入了通道和基础数据类型的buffer类型,各位看官可以去看看,这里就不说了,有空再总结.