socket 相关方法说明
1,建立Socket
Socket socket = new Socket();
try{
socket.connect(SocketAddress endpoint,int timeout);
}catch(SocketTimeOutException e){
... do with timeOut...
}catch(OtherException){
... do with other Exception...
}
这样可以自定义连接的超时时间
2 accept(),read()和receive()
对于这些方法,我们可以使用Socket类、ServerSocket类和DatagramSocket类的setSoTimeout()方法,设置其阻塞的最长时间(以毫秒为单位)。如果在指定时间内这些方法没有返回,则将抛出一个InterruptedIOException异常。对于Socket实例,在调用read()方法前,我们还可以使用该套接字的InputStream的available()方法来检测是否有可读的数据.
(1)按字节读取
BufferedInputStream in = new BufferedInputStream(socket.getInputStream());
int r = -1;
List<Byte> l = new LinkedList<Byte>();
while ((r = in.read()) != -1) {
l.add(Byte.valueOf((byte) r));
}
(2)按字符流读取
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s;
while ((s = in.readLine()) != null) {
System.out.println("Reveived: " + s);
}
这两个方法read()和readLine()都会读取对端发送过来的数据,如果无数据可读,就会阻塞直到有数据可读。或者到达流的末尾,这个时候分别返回-1和null。发送完后调用Socket的shutdownOutput()方法关闭输出流,这样对端的输入流上的read操作就会返回-1。注意不能调用socket.getInputStream().close()。这样会导致socket被关闭。当然如果不需要继续在socket上进行读操作,也可以直接关闭socket。但是这个方法不能用于通信双方需要多次交互的情况。
在游戏开发中,我们可能需要维持一个长连接,用于实时交互数据,这个时候可以另起一个线程,用于循环的接收数据:
public class RecieveDataRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
InputStream in = null;
Socket socket = null;
SocketManager socketManager = SocketManager.getInstance();
while(true){
socket = socketManager.getDBServerConnection();
try {
in = socket.getInputStream();
while(in.read() != -1){
//这里对接收的数据进行处理,如果没有可接收的数据,in.read会阻塞,直到有数据可读取。
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3,write()方法
write()方法调用也会阻塞等待,直到最后一个字节成功写入到了TCP实现的本地缓存中。如果可用的缓存空间比要写入的数据小,在write()方法调用返回前,必须把一些数据成功传输到连接的另一端。因此,write()方法的阻塞总时间最终还是取决于接收端的应用程序。不幸的是Java现在还没有提供任何使write()超时或由其他线程将其打断的方法。所以如果一个可以在Socket实例上发送大量数据的协议可能会无限期地阻塞下去。
4,默认的Keep-Alive 机制
如果一段时间内没有数据交换,通信的每个终端可能都会怀疑对方是否还处于活跃状态。TCP协议提供了一种keep-alive的机制,该机制在经过一段不活动时间后,将向另一个终端发送一个探测消息。如果另一个终端还出于活跃状态,它将回复一个确认消息。如果经过几次尝试后依然没有收到另一终端的确认消息,则终止发送探测信息,关闭套接字,并在下一次尝试I/O操作时抛出一个异常。注意,应用程序只要在探测信息失败时才能察觉到keep-alive机制的工作。不过这个不常用,游戏开发中,用心跳机制代替这种方式了。
5,发送和接收缓存区的大小
一旦创建了一个Socket或DatagramSocket实例,操作系统就必须为其分配缓存区以存放接收的和要发送的数据:
int getReceiveBufferSize()
void setReceiveBufferSize(int size)
int getSendBufferSize()
void setSendBufferSize(int size)
为ServerSocket调用这些方法的时候,相当于为其accept()方法接收的Socket分配缓冲大小;
6,地址重用
对于TCP,当一个连接关闭后,通信的一端(或两端)必须在"Time-Wait"状态上等待一段时间,以对传输途中丢失的数据包进行清理(见第6.4.2节)。不幸的是,通信终端可能无法等到Time-Wait结束。对于这两种情况,都需要能够与正在使用的地址进行绑定的能力,这就要求实现地址重用。
boolean getReuseAddress()
void setReuseAddress(boolean on)
7,消除缓冲延迟
TCP协议将数据缓存起来直到足够多时一次发送,以避免发送过小的数据包而浪费网络资源。虽然这个功能有利于网络,但应用程序可能对所造成的缓冲延迟不能容忍。好在可以人为禁用缓存功能:
boolean getTcpNoDelay()
void setTcpNoDelay(boolean on)
8,关闭后停留
当调用套接字的close()方法后,即使套接字的缓冲区中还有没有发送的数据,它也将立即返回。这样不发送完所有数据可能导致的问题是主机将在后面的某个时刻发生故障。其实可以选择让close()方法"停留"或阻塞一段时间,直到所有数据都已经发送并确认,或发生了超时.
int getSoLinger()
void setSoLinger(boolean on, int linger)
如果发生了超时,TCP连接将强行关闭
9.关闭连接
调用Socket的close()方法将同时终止两个方向(输入和输出)的数据流.Socket类的shutdownInput()和shutdownOutput()方法能够将输入输出流相互独立地关闭。调用shutdownInput()后,套接字的输入流将无法使用。任何没有发送的数据都将毫无提示地被丢弃,任何想从套接字的输入流读取数据的操作都将返回-1。当Socket调用shutdownOutput() 方法后,套接字的输出流将无法再发送数据,任何尝试向输出流写数据的操作都将抛出一个IOException异常。在调用shutdownOutput()之前写出的数据可能能够被远程套接字读取,之后,在远程套接字输入流上的读操作将返回-1。应用程序调用shutdownOutput()后还能继续从套接字读取数据,类似的,在调用shutdownInput()后也能够继续写数据。
10,available 使用
在网络流中如果不使用任何标记,是不知道流是否结束的。但在读到网络流时,我们是可以知道这次可以读多少字节的内容,方法就是使用inputStream. available (),但一定要在调用read()至少一次之后,也就是说available方法一定要在read后调用,不然就只能得到零值。注,这里所的是网络传输中的流是这样,但文件流不是这样的,文件流可以直接使用available来判断还有多少字节的内容可读取。
InputStream的available()含义:返回此输入流在不受阻塞情况下能读取的字节数。网络流与文件流不同的关键就在于是否“受阻”二字,网络socket流在读取时如果没有内容read()方法是会受阻的,所以从socket初始化的输入流的available也是为零的,所以要read一字节后再使用,这样可用的字节数就等于 available + 1。但文件读取时read()一般是不会受阻的,因为文件流的可用字节数 available = file.length(),而文件的内容长度在创建File对象时就已知了。
网络socket输入流可用字节数代码所示:
//将接收到的数据存到字节数组array
int firstChar = inputStream.read();
int length = inputStream.available();
byte[] array = new byte[length+1];
array[0] = (byte)firstChar;
inputStream.read(array,1,length);
文件流的可用字节数如代码所示:
FileInputStream fi = new FileInputStream("e:/tmp/tmp");
//循环读出所有文件内容,可以在read前就直接使用
while (fi.available() > 0) {
System.out.println((byte) fi.read());//直接输出内容的编码,而非字符编码。可能为负,如二进制的图片文件
}
fi.close();