网络应用分为客户端和服务端两部分而Socket类是负责处理客户端通信的Java类通过这个类可以连接到指定IP或域名的服务器上并且可以和服务器互相发送和接受数据在本文及后面的数篇文章中将详细讨论Socket类的使用内容包括Socket类基础各式各样的连接方式get和set方法连接过程中的超时以及关闭网络连接等
在本文中我们将讨论使用Socket类的基本步骤和方法一般网络客户端程序在连接服务程序时要进行以下三步操作
连接服务器
发送和接收数据
关闭网络连接
一连接服务器
在客户端可以通过两种方式来连接服务器一种是通过IP的方式来连接服务器而另外一种是通过域名方式来连接服务器
其实这两种方式从本质上来看是一种方式在底层客户端都是通过IP来连接服务器的但这两种方式有一定的差异如果通过IP方式来连接服务端程序客户端只简单地根据IP进行连接如果通过域名来连接服务器客户端必须通过DNS将域名解析成IP然后再根据这个IP来进行连接
在很多程序设计语言或开发工具中(如C/C++Delphi)使用域名方式连接服务器时必须自己先将域名解析成IP然后再通过IP进行连接而在Java中已经将域名解析功能包含在了Socket类中因此我们只需象使用IP一样使用域名即可
通过Socket类连接服务器程序最常用的方法就是通过Socket类的构造函数将IP或域名以及端口号作为参数传入Socket类中Socket类的构造函数有很多重载形式在这一节只讨论其中最常用的一种形式public Socket(String host int port)从这个构造函数的定义来看只需要将IP或域名以及端口号直接传入构造函数即可下面的代码是一个连接服务端程序的例子程序
package mysocket;
import*;
publicclass MyConnection
{
publicstaticvoidmain(String[]args)
{
try
{
if(argslength>)
{
Socketsocket=newSocket(args[]);
Systemoutprintln(args[]+已连接成功!);
}
else
Systemoutprintln(请指定IP或域名!);
}
catch(Exceptione)
{
Systemerrprintln(错误信息+egetMessage());
}
}
}
在上面的中通过命令行参数将IP或域名传入程序然后通过Socket socket = new Socket(args[] )连接通过命令行参数所指定的IP或域名的端口由于Socket类的构造函数在定义时使用了throws因此在调用Socket类的构造函数时必须使用try…catch语句来捕捉错误或者对main函数使用throws语句来抛出错误
测试正确的IP
java mysocketMyConnection
输出结果已经连接成功!
测试错误的IP
java mysocketMyConnection
输出结果错误信息Connection timed out: connect
注是一个并不存在的IP如果这个IP在你的网络中存在请使用其它的不存在的IP
测试正确的域名
java mysocketMyConnection
输出结果已经连接成功!
测试错误的域名
java mysocketMyConnection
输出结果错误信息
使用Socket类连接服务器可以判断一台主机有哪些端口被打开下面的代码是一个扫描本机有哪些端口被打开的程序
package mysocket;
import*;
publicclass MyConnection extendsThread
{
privateintminPortmaxPort;
public MyConnection(intminPortintmaxPort)
{
thisminPort=minPort;
thismaxPort=maxPort;
}
publicvoidrun()
{
for(inti=minPort;i<=maxPort;i++)
{
try
{
Socketsocket=newSocket(i);
Systemoutprintln(StringvalueOf(i)+:ok);
socketclose();
}
catch(Exceptione)
{
}
}
}
publicstaticvoidmain(String[]args)
{
intminPort=IntegerparseInt(args[])maxPort=Integer
parseInt(args[]);
intthreadCount=IntegerparseInt(args[]);
intportIncrement=((maxPortminPort+)/threadCount)
+(((maxPortminPort+)%threadCount)==?:);
MyConnection[]instances=new MyConnection[threadCount];
for(inti=;i<threadCount;i++)
{
instances[i]=new MyConnection(minPort+portIncrement*iminPort
+portIncrement+portIncrement*i);
instances[i]start();
}
}
}
上面代码通过一个指定的端口范围(如至)并且利用多线程将这个端口范围分成不同的段进行扫描这样可以大大提高扫描的效率
可通过如下命令行去运行例程
javamysocketMyConnection
二发送和接收数据
在Socket类中最重要的两个方法就是getInputStream和getOutputStream这两个方法分别用来得到用于读取和写入数据的InputStream和OutputStream对象在这里的InputStream读取的是服务器程序向客户端发送过来的数据而OutputStream是客户端要向服务端程序发送的数据
在编写实际的网络客户端程序时是使用getInputStream还是使用getOutputStream以及先使用谁后使用谁由具体的应用决定如通过连接邮电出版社网站()的端口(一般为HTTP协议所使用的默认端口)并且发送一个字符串最后再读取从返回的信息
package mysocket;
import*;
importjavaio*;
publicclass MyConnection
{
publicstaticvoidmain(String[]args)throwsException
{
Socketsocket=newSocket();
//向服务端程序发送数据
OutputStreamops=socketgetOutputStream();
OutputStreamWriteropsw=newOutputStreamWriter(ops);
BufferedWriterbw=newBufferedWriter(opsw);
bwwrite(helloworld\r\n\r\n);
bwflush();
//从服务端程序接收数据
InputStreamips=socketgetInputStream();
InputStreamReaderipsr=newInputStreamReader(ips);
BufferedReaderbr=newBufferedReader(ipsr);
Strings=;
while((s=brreadLine())!=null)
Systemoutprintln(s);
socketclose();
}
}
在编写上面代码时要注意如下两点
为了提高数据传输的效率Socket类并没有在每次调用write方法后都进行数据传输而是将这些要传输的数据写到一个缓沖区里(默认是个字节)然后通过flush方法将这个缓沖区里的数据一起发送出去因此bwflush();是必须的
在发送字符串时之所以在Hello World后加上 \r\n\r\n这是因为HTTP协议头是以\r\n\r\n作为结束标志(HTTP协议的详细内容将在以后讲解)因此通过在发送字符串后加入\r\n\r\n可以使服务端程序认为HTTP头已经结束可以处理了如果不加\r\n\r\n那么服务端程序将一直等待HTTP头的结束也就是\r\n\r\n如果是这样服务端程序就不会向客户端发送响应信息而brreadLine()将因无法读以响应信息面被阻塞直到连接超时
三关闭网络连接
到现在为止我们对Socket类的基本使用方法已经有了初步的了解但在Socket类处理完数据后最合理的收尾方法是使用Socket类的close方法关闭网络连接虽然在中已经使用了close方法但使网络连接关闭的方法不仅仅只有close方法下面就让我们看看Java在什么情况下可以使网络连接关闭
可以引起网络连接关闭的情况有以下种
直接调用Socket类的close方法
只要Socket类的InputStream和OutputStream有一个关闭网络连接自动关闭(必须通过调用InputStream和OutputStream的close方法关闭流才能使网络可爱接自动关闭)
在程序退出时网络连接自动关闭
将Socket对象设为null或未关闭最使用new Socket(…)建立新对象后由JVM的垃圾回收器回收为Socket对象分配的内存空间后自动关闭网络连接
虽然这种方法都可以达到同样的目的但一个健壮的网络程序最好使用第种或第种方法关闭网络连接这是因为第种和第种方法一般并不会马上关闭网络连接如果是这样的话对于某些应用程序将会遗留大量无用的网络连接这些网络连接会占用大量的系统资源
在Socket对象被关闭后我们可以通过isClosed方法来判断某个Socket对象是否处于关闭状态然而使用isClosed方法所返回的只是Socket对象的当前状态也就是说不管Socket对象是否曾经连接成功过只要处于关闭状态isClosde就返回true如果只是建立一个未连接的Socket对象isClose也同样返回true如下面的代码将输出false
Socketsocket=newSocket();
Systemoutprintln(socketisClosed());
除了isClose方法Socket类还有一个isConnected方法来判断Socket对象是否连接成功看到这个名字也许读者会产生误解其实isConnected方法所判断的并不是Socket对象的当前连接状态而是Socket对象是否曾经连接成功过如果成功连接过即使现在isClose返回trueisConnected仍然返回true因此要判断当前的Socket对象是否处于连接状态必须同时使用isClose和isConnected方法即只有当isClose返回falseisConnected返回true的时候Socket对象才处于连接状态下面的代码演示了上述Socket对象的各种状态的产生过程
package mysocket;
import*;
publicclass MyCloseConnection
{
publicstaticvoidprintState(SocketsocketStringname)
{
Systemoutprintln(name+isClosed():+socketisClosed());
Systemoutprintln(name+isConnected():+socketisConnected());
if(socketisClosed()==false&&socketisConnected()==true)
Systemoutprintln(name+处于连接状态!);
else
Systemoutprintln(name+处于非连接状态!);
Systemoutprintln();
}
publicstaticvoidmain(String[]args)throwsException
{
Socketsocket=nullsocket=null;
socket=newSocket();
printState(socketsocket);
socketgetOutputStream()close();
printState(socketsocket);
socket=newSocket();
printState(socketsocket);
socketclose();
printState(socketsocket);
}
}
运行上面的代码后将有如下的输出结果
socketisClosed():false
socketisConnected():true
socket处于连接状态!
socketisClosed():true
socketisConnected():true
socket处于非连接状态!
socketisClosed():false
socketisConnected():false
socket处于非连接状态!
socketisClosed():true
socketisConnected():false
socket处于非连接状态!
从输出结果可以看出在socket的OutputStream关闭后socket也自动关闭了而在上面的代码我们可以看出对于一个并未连接到服务端的Socket对象socket它的isClosed方法为false而要想让socket的isClosed方法返回true必须使用socketclose显示地调用close方法
虽然在大多数的时候可以直接使用Socket类或输入输出流的close方法关闭网络连接但有时我们只希望关闭OutputStream或InputStream而在关闭输入输出流的同时并不关闭网络连接这就需要用到Socket类的另外两个方法shutdownInput和shutdownOutput这两个方法只关闭相应的输入输出流而它们并没有同时关闭网络连接的功能和isClosedisConnected方法一样Socket类也提供了两个方法来判断Socket对象的输入输出流是否被关闭这两个方法是isInputShutdown()和isOutputShutdown()下面的代码演示了只关闭输入输出流的过程
package mysocket;
import*;
publicclass MyCloseConnection
{
publicstaticvoidprintState(Socketsocket)
{
Systemoutprintln(isInputShutdown:+socketisInputShutdown());
Systemoutprintln(isOutputShutdown:+socketisOutputShutdown());
Systemoutprintln(isClosed:+socketisClosed());
Systemoutprintln();
}
publicstaticvoidmain(String[]args)throwsException
{
Socketsocket=newSocket();
printState(socket);
socketshutdownInput();
printState(socket);
socketshutdownOutput();
printState(socket);
}
}
在运行上面的代后将得到如下的输出结果
isInputShutdown:false
isOutputShutdown:false
isClosed:false
isInputShutdown:true
isOutputShutdown:false
isClosed:false
isInputShutdown:true
isOutputShutdown:true
isClosed:false
从输出结果可以看出isClosed方法一直返回false因此可以肯定shutdownInput和shutdownOutput并不影响Socket对象的状态