publicclass ScanPort {
publicstaticvoid main(String[] args) throws UnknownHostException, IOException {
Socket socket = null;
for(inti=1; i<1030; i++) {
try {
socket = new Socket("localhost", i);
System.out.println("there is a server on this port:" + i);
}catch(Exception e) {
System.out.println("can not connection to port:" + i);
}finally {
if(socket != null) {
socket.close();
}
}
}
}
}
1.1 设置超时时间
Socket socket = new Socket();
SocketAddress address = new InetSocketAddress("localhost", 1000);
socket.connect(address, 1000);
设置一个超时时间1000毫秒,如果1000毫秒后没有连接成功,有其他异常返回其他异常,没有其他异常返回SocketTimeoutException。如果设置为0则一直等待永不超时。
1.2 设置服务器地址:
Socket(InetAddress address, int port) //第一个参数表示主机的IP地址
Socket(String host, int port) //第一个地址表示主机的名字
InetAddress提供了一些静态工厂方法,获取IP地址:
InetAddress inetAddr = InetAddress.getLocalHost();
InetAddress addr1 = InetAddress.getByName("127.0.0.1");
InetAddress addr2 = InetAddress.getByName("www.baidu.com”);
1.3 设置客户端地址:
默认情况下,客户端的IP地址来自于客户端主机,端口由客户端随机指定。但是也可以显示指定客户端的IP和port:
Socket(InetAddress address, int port, InetAddress localAddress, int loaclPort)
Socket(String host, int port, String localHost, int localPort)
如果一台主机属于两个网络,则这个主机有两个IP,我们可以手动指定客户端地址与服务器连接:
InetAddress remoteAddr = InetAddress.getByName("192.168.1.1");
InetAddress localAddr = InetAddress.getByName("192.168.1.2");
Socket socket = new Socket(remoteAddr, 8000, localAddr, 5000);
1.4 客户端连接可能抛出的异常:
例程:publicclass ConnectionTest {
publicstaticvoid main(String[] args) {
String host = "localhost";
intport = 0;
if(args.length > 1) {
host = args[0];
port = Integer.parseInt(args[1]);
}
new ConnectionTest().connection(host, port);
}
publicvoid connection(String host, intport) {
Socket socket = null;
String result = "";
try {
SocketAddress addr = new InetSocketAddress(host, port);
longstart = System.currentTimeMillis();
socket = new Socket();
socket.connect(addr, 5000);
longend = System.currentTimeMillis();
result = (end - start) + "ms";
}catch(BindException e) {
result = "bindException";
}catch(UnknownHostException e) {
result = "UnknowHostException";
}catch(ConnectException e) {
result = "connectionException";
}catch(SocketTimeoutException e) {
result = "socket timeout exception";
}catch(IOException e) {
result = "io exception";
}finally {
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println(result);
}
}
a>UnknowHostException:
如果入法识别主机的名字或IP地址,会抛出此异常。
b>connectionException:
1>没有监听在该端口的进程时,会报该异常
2>有进程监听在该进程,但是进程处于休眠状态并且队列已经耗尽,则会抛出此异常。例程如下:
server:
publicclass SimpleServer {
publicstaticvoid main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8000, 2);
Thread.sleep(10000);
}
}
clinet:
publicclass SimpleClinet {
publicstaticvoid main(String[] args) throws UnknownHostException, IOException {
Socket socket1 = new Socket("localhost", 8000);
System.out.println("第一次连接");
Socket socket2 = new Socket("localhost", 8000);
System.out.println("第二次连接");
Socket socket3 = new Socket("localhost", 8000);
System.out.println("第三次连接");
}
}
3>SocketTimeoutException:
如果客户端连接超时,则抛出该异常。socket.connect(addr, 5000); 该方法的第二个参数指定超时时间。
4>BindException:
如果无法将socket对象与本地的IP地址或者端口绑定,则会抛出该异常。例如本地的IP地址指定错误或者端口已经被占用。
socket = new Socket(host, port, InetAddress.getByName("222.11.11.11"), 8888);
2.获取Socket信息:
getInetAddress():获取远程服务器IP地址
getPort():获取远程服务器port
getLocalAddress():获取客户本地IP地址
getLocalPort():获取客户本地端口
getInputStream():获取输入流,从socket对象读取数据。如果socket没有连接或者已经关闭,或者通过shutdownInput()方法关闭输入流,那么抛出IOException异常。
getOutputStream():获取输出流,向socket对象写入数据。如果socket对象没有连接或者已经关闭,或者通过shutdownOutputstream()关闭输出流,那么抛出IOException异常。
如下例程,用Socket发送了一个符合Http协议的请求,用于获取数据:
publicclass HttpClient {
private String host="www.javathinker.org";
privateintport=80;
private Socket socket;
publicvoid createSocket() throws UnknownHostException, IOException {
socket = new Socket(host, port);
}
publicvoid communicate() throws IOException {
StringBuilder sb = new StringBuilder("GET"+"/index.jsp"+"HTTP/1.1\r\n");
sb.append("Host: www.javathinker.org");
sb.append("Accept: */*\r\n");
sb.append("Accept-Language: zh-cn\r\n");
sb.append("Accept-Encoding: gzip,default\r\n");
sb.append("User-Agent: Mozilla/4.0\r\n");
sb.append("Connection: Keep-Alive\r\n\r\n");
//发送http请求
OutputStream os = socket.getOutputStream();
os.write(sb.toString().getBytes());
socket.shutdownOutput();
//响应http请求
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buff = newbyte[1024];
intlen;
while((len = is.read()) != -1) {
baos.write(buff, 0, len);
}
System.out.println(new String(baos.toByteArray()));
socket.close();
}
publicstaticvoid main(String[] args) throws UnknownHostException, IOException {
HttpClient client = new HttpClient();
client.createSocket();
client.communicate();
}
}
如果获取页面的数据量比较大,用ByteArrayOutputStream接受太耗内存,建议以下方法:
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String tmp;
while((tmp = br.readLine()) != null) {
System.out.println(tmp);
}
3关闭Socket:
当客户端与服务器端通信结束后,应该及时关闭socket连接,以释放socket占用的端口等资源。一旦socket被关闭,就不能通过socket进行IO操作,否则会抛出IOException。强烈推荐socket在finally里关闭,已确保socket一定会关闭:
try {
socket = new Socket(host, port);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(socket != null)
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
socket有几个测试方法:
isClosed():判断socket是否关闭。
isConnected():判断socket连接到远程主机,则返回true;
isBound():如果socket已经与一个端口绑定,则返回true
判断socket是否处于连接状态:socket.isConnected() && !socket.isClosed()
4 半关闭socket:
假设进程A与B进行通信,A发送数据,B接收收据,B怎么确定A数据发送完了呢,有如下几种方法:
a>如果A与B之间传输的字符流时,并且都是一行一行读取数据时,可以约定一个字符串如”bye”,如果B读取到此字符串则停止读取数据。
b>A给B发送一条消息,告诉自己发送的内容的长度。然后A发送正文内容。B读取到该长度的字符或字节时就结束。
c>A发送完数据后,执行close()方法。B继续执行输入流的read()时会返回-1,或者使用bufferedReader执行readLine()时返回null,则说明读到了数据末尾。
d>当执行close()时会输出流和输入流都会关闭,如果希望关闭输入流或输出流之一,那么可以使用socket提供的半关闭方法:
shutdownInput()和shutdownOutput()。进程A关闭输入流后,进程B就会读取到输入流的末尾。需要注意的是:执行了shutdownInput()和shutdownOutput()后并不等同于执行了close()方法。只有执行了close()方法,才会释放socket占用的端口等资源。
socket还提供了两个测试方法:
isInputShutdown()是否半关闭输入流。 isOutputShutdown()是否半关闭输出流。
5.设置Socket的选项:
a>TCP_NODELAY:
设置该选项public void setTcpNoDelay(bookean on)suanfa
读取改选项:public boolean getTcpNoDelay()
默认情况下:发送数据采用Negale算法,negale算法:发送方发送数据时数据不被立即发送到接收方,而是先放到缓存区内。等缓冲区满了在发出。发送一批数据后,等待这批数据回应后在发送下一批。negale算法适用于发送大量数据,并且及时响应的情况。该算法是通过减少传送次数来提高效率的。
如果每次数据发送量很少,并且不需要及时响应则不使用该算法。例如游戏程序鼠标的位置需要实时的发送到服务器端,所以该算法会造成很大的延时。
TCP_NODELAY的默认值是false,意思是使用Negale算法,可以通过setTcpNoDelay(true)把socket缓冲区关闭,让数据实时发送到接收端。如果socket底层不支持该参数。则getTcpNoDelay()和setTcpNoDelay()会抛出SocketException异常。
b>SO_RESUSEADDR:
设置该选项:public void setResuseAddr(boolean b)
读取该选项:public boolean getResuseAddr()
当接收方调用了close()关闭了socket后,如果网络上还有数据没有接收完毕,则底层的socket不会立即释放绑定的端口,它要等到数据接收完全后在释放端口。接收到的数据不会做任何处理。接收剩余数据的目的是防止绑定到该端口的新进程接收到这些数据。
客户端进程一般采用随机分配端口的方式。而服务器端使用固定端口的方式。当服务器程序关闭后,它的端口还会占用一段时间,此时如果重新启动程序,则会由于端口无法绑定而重启失败。为了使进程关闭后即使端口没有立即释放,新程序也能立即绑定到该端口,可以通过设置setResuseAddr(true)来实现。
值得注意的是setResuseAddr(true)需要在绑定到第一个端口之前调用才能生效,例如
Socket socket = new Socket(); //此时服务器没有绑定本地端口,并且没有连接到远程服务器。
socket.setResuseAddr(true);
SocketAddr addr = new InetSocketAddr("remoteAddr", 9000);
socket.connect(addr); //此时服务器绑定到本地匿名端口,并且连接点远程服务器。
c>SO_TIMEOUT:
设置该选项:public void setSoTimeout(int milliseconds)
读取该选项:public int getSoTimeout();
当通过socket的输入流读取数据时,如果没有数据就会等待。例如:
InputStream is = socket.getInputStream();
byte[] buff = new byte[];
is.read(buff);
此时有几种情况:
1>如果输入流里有1024个字节,则把字节读取到buff里,返回读取的字节数
2>如果输入流里没有到末尾,但是不够1024个字节,那么把这些数据读取到buff里,返回读取的自己数。
3>如果输入流里已经读到了末尾,那么返回-1
4>如果连接已断开,会抛出SocketException
5>如果设置了setSoTimeout()方法,那么如果超过了设置的时间这回抛出SocketTimeoutException
setSoTimeout()的单位是毫秒,默认是0表示一直等待。该方法需要在读取数据之前设置,另外抛出SocketTimeoutException后,socket并没有断开还可以尝试接收数据。
例程如下:
SendClient.java: