您现在的位置是:首页 >技术教程 >Socket(一)网站首页技术教程
Socket(一)
1. 简介
Socket允许程序员将网路连接看作是另外一个可以读/写字节的流,Socket对程序员掩盖了网络的底层细节,如错误检测、包大小、包分解、包重传、网络地址等。Socket是两台主机之间有一个连接,它可以完成7个基本操作:
- 连接远程主机
- 发送数据
- 接受数据
- 关闭端口
- 绑定端口
- 监听入站信息
- 在绑定端口上接受来自远程机器的连接
Java的socket类(客户端和服务器都可以使用)提供了四个对应前面操作的方法。后面三个操作仅仅服务器需要,即等待客户端连接。这些操作有ServerSocket实现。Java程序通常采用以下方式使用客户端Socket
- 程序用构造函数创建一个新的Socket
- Socket尝试连接远程主机
一旦建立连接,本地和远程主机就从这个Socket得到输入流和输出流,使用这两个流相互发送数据。连接是全双工的,两台主机都可以同时发送和接受数据。数据的含义取决于协议,发送给HTTP服务器的命令就有所不同。一般会先完成某种协商握手,然后再具体传输数据。当数据传输结束后,一端或两端将关闭连接,有些协议,如HTTP1.0,要求每次请求得到服务后都要关闭连接。其他协议,如FTP和HTTP1.1 ,则允许在一个连接上处理多个请求。
2. 用Socket从服务器读取
我们用Telnet可以连接NIST(美国国家标准与技术研究院)的daytime服务器,请求当前时间。
60091 23-05-27 08:55:49 50 0 0 894.0 UTC(NIST) *
实际daytime的发送时间,读取这个socket的Inpustream时,就会得到这个结果。下面看看Socket是如果通过编程获取同样的内容的,首先在端口13打开time.nist.gov的连接
Socket socket=new Socket("time.nist.gov",13)
上面代码会在网络上建立连接,如果连接超时,或者由于服务器未在端口13上监听而失败,构造函数会抛出一个IOException异常,所以通常要把这个代码包装在try-catch语句块中。 使用setSoTimeout
方法会为连接设置一个超时时间,超时时间按照毫秒度量。如果服务器拒绝Socket连接,那么socket要很快的抛出一个ConnectException
,或者如果路由器无法确定如何将你的包发送到服务器,则要抛出一个NoRouteToHostException
异常。如果一个有问题的服务器接受了连接,然后停止与你对话,但是没有主动关闭连接,对socket设置一个超时时间,这意味着对这个socket的每一个读/写都最多耗费一定的毫秒数。如果你连接的服务器挂起,会抛出一个SocketTimeoutException
通知你。具体要设置多少时间这取决于你的应用的需要,以及你希望的服务器响应性。对于一个本地内部服务器来说,15s的响应时间太长了,但是对于一个负载很大的公共服务器,这个时间则很短。一旦代开socket并设置超时时间,可以调用getInpustStream
来返回一个InputStream,用它从socket读取字节,一般来讲服务器可以发送任何字节。
public class QuizCardBuilder {
public static void main(String[] args) throws IOException {
Socket socket=null;
try{
socket =new Socket("time.nist.gov",13);
socket.setSoTimeout(15000);
InputStream in=socket.getInputStream();
StringBuilder time=new StringBuilder();
InputStreamReader reader=new InputStreamReader(in,"ASCII");
for(int c=reader.read();c!=-1;c=reader.read()){
time.append((char)c);
}
System.out.println(time);
}catch (IOException ex){
System.err.println(ex);
}finally {
if(socket!=null)
try {
socket.close();
}catch (IOException ex){
}
}
}
}
可以看出这和上面Telnet程序的输出是一样的
3. 用Socket写入服务器
写入服务器并不比读取服务器困难,只需要向socket请求一个输出流以及一个输入流。使用输入流在Socket发送数据时,同时还可以使用输入流读取数据,不过大多数协议都针对客户端只读取socket或者只写入socket,而不是二者同时进行,最常见的模式是,客户端发送一个请求,然后服务器响应。客户端可能发送另一个请求,服务器再做出响应。这个过程会继续,直到客户端或服务器完成工作,然后关闭连接。RFC 2229定义的dict是一个简单饿双向TCP。在这个协议中客户端向dict服务器的2628端口打开一个socket,并发送类似“DEFINE eng-lat gold”命令,这会告诉服务器使用它的英语-拉丁语字典发送单纯“gold”的定义。telnet测试结果如下:
下面用java代码实现:
public class QuizCardBuilder {
public static void main(String[] args){
Socket socket=null;
try{
socket =new Socket("dict.org",2628);
socket.setSoTimeout(15000);
OutputStream out=socket.getOutputStream();
Writer writer=new OutputStreamWriter(out,"UTF-8");
writer=new BufferedWriter(writer);
InputStream in=socket.getInputStream();
StringBuilder time=new StringBuilder();
BufferedReader reader=new BufferedReader(new InputStreamReader(in,"UTF-8"));
define("DEFINE english computer",writer,reader);
writer.write("quit
");
writer.flush();
System.out.println(time);
}catch (IOException ex){
System.err.println(ex);
}finally {
if(socket!=null)
try {
socket.close();
}catch (IOException ex){
}
}
}
static void define(String word,Writer writer,BufferedReader bufferedReader) throws IOException {
writer.write(word+"
");
for(String line=bufferedReader.readLine();line!=null;line=bufferedReader.readLine()){
if(line.startsWith("250")){
return;
}else if(line.startsWith("552")){
System.out.println("NOT MATCH");
return;
}else if(line.matches("\d\d\d"))continue;
else if(line.trim().equals("."))continue;
else System.out.println(line);
}
}
}
半关闭Socket
close()方法同时关闭Socket的输入和输出。有时你希望只关闭一半连接,即输入或者输出。shutdownInput()
和shutdownOutput()
方法只关闭连接的一半。这两个方法并不关闭Socket,实际上它们的作用是调整与Socket连接的流。使它认为已经到了流的末尾。关闭输入之后再读取会返回-1。关闭输出之后再写入Socket会抛出一个IOException异常。注意,即使半关闭了连接,或许连接的两半都关闭了,使用结束后仍然需要关闭Socket。shudown方法只会影响Socket的流,它们并不释放与Socket关联的资源,如所占用的端口等。isInputShutdown()
和isOutputShutdown()
方法分别指出输入流和输出流是打开的还是关闭的。
4. 构造和连接Socket
java.net.Socket类是Java完成客户端TCP操作的基础类,其他建立TCP网络连接的面向客户端的类(如URL、URLConnection、Applet和JEditorpane)最终都会调用这个类的方法。这个类本身使用原生代码与主机操作系统的本地TCP栈进行通信。每个Socket构造函数指定要连接的主机和端口,主机可以指定为InetAddress或String。远程端口指定为1到65535之间的int值:
public Socket(String host,int port)throws UnknowHostException,IOException
public SOcket(InetAddress host,int port)throws IOException
下面代码查看本机已使用端口
public class QuizCardBuilder {
public static void main(String[] args)
{
for(int i=1;i<256;i++)
{
try
{
InetAddress localHost=InetAddress.getLocalHost();
Socket socket=new Socket(localHost,i);
System.out.println("本机已经使用了端口:"+i);
}
catch(UnknownHostException e)
{
//e.printStackTrace();
}
catch(IOException e)
{
// e.printStackTrace();
}
}
System.out.println("执行完毕!");
}
}
有3个构造函数可以创建未连接的Socket。这些构造函数对于底层Socket的行为提供了更多控制,例如可以选择一个不同的代理服务器或者一个加密机制。
public Socket()
public Socket(Proxy proxy)
protected Socket(SocketImpl impl)
4. 选择从哪个本地接口连接
有两个构造函数可以指定要连接的主机和端口,以及从哪个接口和端口连接:
public Socket(String port,int port, InetAddress interface,int localPort)
public Socket(InetAddress host, int port , InetAddress interface , int localPort)
这个Socket连接到前两个参数中指定的主机和端口,它从后两个参数指定的本地网络接口和端口来连接,网络接口可以是物理接口(例如,一个以太网卡),也可以是虚拟接口(一个有多个IP地址的多宿主主机)。如果localport传入0,Java会随机选择1024到65535之间的一个可用端口。
5. 构造但不连接
目前为止所有的构造函数在创建Socket对象的同时都会打开一个远程主机的网络连接。如果没有为Socket构造函数提供任何参数,它就没有目标主机可以连接:
public Socket()
可以以后再为某个connect()方法传入一个SocketAddress来建立连接:
try{
Socket socket=new Socket();
SocketAddress address=new InetSocketAddress("time.nist.gov".13);
socket.connect(adress);
}catch(IOException ex){
System.err.println(ex);
}
connect
方法还可以传入一个参数,来指定连接超时之前等待的时间(毫秒数),默认值0表示永远等待下去