Sock又称“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求。抽象出来,Socket实质上是提供了进程通信的的端点。在进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
每一个Socket有一个相关描述,这个描述包含:协议,本地地址,本地端口三个内容。
一个完整的Socket有一个本地唯一的Socket号,由操作系统分配。
最重要的是,Socket是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的Socket系统调用。客户随机申请一个Socket(相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个Socket号:服务器拥有全局公认的Socket,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。Socket利用客户/服务器模式巧妙地解决了进程之间建立通信连接的问题。
1.连接过程
根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
(1)服务器监听:此时服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的转态,实时监控网络状态。
(2)客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后再向服务器端套接字提出连接请求。
(3)连接确认:是指当服务器端套接字监听到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接受其他客户端套接字的连接请求。
Java中的Socket的工作过程可以分为四个基本步骤:
(1)创建Socket:
(2)打开连接Socket的输入流或输出流:
(3)对Socket输入/输出流进行处理(读/写操作);
(4)关闭Socket。
在Java.net包中,有Socket和SocketServer两个类,它们分别负责客户端和服务器端。
它们两者的额构造方法如下:
Socket();
Socket(InetAddress address,int port);
Socket(InetAddress address,int port,boolean stream);
Socket(String host,int port);
Socket(String host,int port,boolean stream);
Socket(SocketImpl impl);
Socket(String host,int port,InetAddress localAddr,int localPort)
Socket(InetAddress address,int port,InetAddress localAddr,int localPort)
ServerSocket(int port);
ServerSocket(int port,int backlog);
ServerSocket(int port,int backlog,InetAddress bindAddr)只接收指定地址的指定接口发过来的数据。
其中address.host和port分别是双向连接中另一方的IP地址.主机名和端口号,stream指明socket是流socket还是数据报socket,localPort表示本机主机的端口号,localAddr和bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既可以用来创建serverSocket又可以用来创建Socket。count则表示服务器所能支持的最大连接数。传入连接指示(对连接的请求)的最大队列长度被设置为backlog参数,如果队列满时收到连接指示,则拒绝该连接。
(1)创建Socket。
我们往往使用构造方法创建Socket,例如:
Socket client=new Socket)(“127.0.01.”,80);
ServerSocket server=new ServerSocket(80);
也可以使用空的构造方法,例如:
Socket socket=new Socket();
SocketAddress remoteAddr=new InetSocketAddress(“localhost”,8000);
socket.connect(remoteAddr,60000);//等待建立连接的超时时间为1分钟
以上代码用于连接到本地机器上的监听8000端口的服务器程序,等待连接的最长时间为1分钟
如果在1分钟内连接成功则connet()方法顺利返回;如果在1分钟内出现某种异常,则抛出该异常;
如果超过1分钟,即没有连接成功,也没有出现其他异常,那么会抛出SocketTimeoutException.Socket类的connect(SocketAddress endpoint,int timeout)方法负责连接服务器,参数endpoint指定服务器的地址,参数timeout设定超时数据,以毫秒为单位。如果参数timeout设为0,表示永远不会超时,默认是不会超时的。
注意,在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23,所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
在创建socket时如果发生错误,将产生IOException,在程序中必须对之作出处理。所以在创建Socket或ServerSocket时必须捕捉或抛出例外。所以编程时常使用try...catch块。
例如:
try{
//socket的一些处理
}catch(IOException e){
//捕捉系统异常时的处理
}
(2)通过构造的Socket获取Socket的信息,如输入,输出流等。
在一个Socket对象中同时包含了远程服务器的IP地址和端口信息,以及客户本地的IP地址和端口信息。此外,从Socket对象中还可以获得输出流和输入流,分别用于向服务器发送数据,以及接收从服务器端发来的数据。以下方法用于获取Socket的有关信息:
getInetAddress():获得远程服务器的IP地址。
getPort():获得远程服务器的端口。
getLocalAddress():获得客户本地的IP地址。
getLocalPort():获得客户本地的端口。
getInputStream():获得输入流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownInput()方法关闭输入流,那么此方法会抛出IOException。
getOutputStream():获得输出流,如果Socket还没有连接,或者已经关闭,或者已经通过shutdownOutput()方法关闭输出流,那么此方法会抛出IOException。
记住:输入\输出流一般是以字节为单位的,我们有时需要把字符串转化为字节进行处理。
(3)对Socket输入/输出流进行处理(读/写操作)。
使用getInputStream()方法可以获得一个InputStream实例,然后使用InputStream的一些方法进行处理输入流。
使用getInputStream()方法可以获得一个InputStream实例,然后使用InputStream的一些方法进行处理输出流。
2.流的概念
流是字节序列的抽象概念
文件是数据的静态存储形式,而流是指数据传输时的形态,而流是指数据传输时的形态。
流分为两个大类:节点流和过滤流(过滤流也叫处理流)
1.InputStream类
程序可以从中连续读取字节的对象叫输入流,在JAVA中,用InputStream类来描述所有输入流的抽象概念。FileInputStream类是InputStream类的。
InputStream类的方法:
int read()从输入流中读取一个字节的内容,并且把这个内容以整数的形式返回。如果碰到流的结束处,那么返回的值就是“-1”;如果流没有结果,但临时没有数据可读,那read方法就将阻塞运行程序的执行过程,直到流中有新的数据可读。(流可以看作是一个通道)。read方法将读取的每一个字节复制到int类型(int类型占用4个字节)中的最低字节其他高字节的部分全部设置为零。
int read(byte[] b)用于从输入流读取若干个字节的内容到字节数组b中,最多读取的字节个数就是这个字节数组的长度,由于流中不一定有这么多的字节可读。
int read(byte[]b,int off,int len)这每次读取len个字节,并放入到字节数组b中,并且是以角标为off的位置依次放入。实际上读取的个数以返回值为准。
long skip(long n)跳过输入流中的n个字节,并返回实际跳过的字节数。这个方法主要用于包装流中,包装流中流可以跳跃,一般的低层流不能跳跃。
int available()返回当前输入流中可读的字节数,在使用时我们可以先用available方法来判断流中是否有可读数据,再用read方法进行读取,这样可以防止程序发生阻塞。
void mark(int readlimit)在输入流中建立一个标记,readlimit表示在建立标记地方开始最多还能读取多少个字节的内容(用于包装类的方法)
void reset()与mark方法配合使用,用mark方法在a处做标记后再读取b个字节并调回reset方法,当下次再度时就从a的地方开始读取,也就是说,reset方法是让指针回到以做的标记处。
boolean markSupported()返回当前流对象是否支持mark和reset操作
void close()用于完成一个流的所有操作以后,关闭这个流,放弃与这个流相关的所有资源。
InputStream是抽象类,程序中实际使用的是InputStream的各种子类对象,不是所有子类都会支持InputStream中定义的某些方法。比如skip。mark。reset在节点流中不适合,它们用于包装流。
2 OutputStream类
程序可以向其中连续写入字节的对象叫输出流,在JAVA中,用OutputStream类来描述所有输出流的抽象概念。FileOutputStream类是OutputStream类的子类。
OutputStream类的方法:
void write(int b) 将一个整数中的最低一个字节中的内容写到输出流中,高字节部分被弃。
void write(byte[] b)将字节数组中的所有内容写入到输出流对象中。
void write(byte[] b,int off,int len)将字节数组b中从off位置开始的len个字节写入到输出流对象中。
voId flush()将内存缓冲区的内容完全清空,新输入到I/O设备当中。
void close()关闭输出流对象。
(4)关闭Socket.
使用close()方法关闭。
当客户与服务器的通信结束,应该及时关闭Socket,以释放Socket占用的包括端口在内的各种资源。Socket的close()方法负责关闭Socket。当一个Socket对象被关闭,就不能再通过它的输入流和输出流进行I/O操作,否则会导致IOException。
为了确保关闭Socket的操作总是被执行,强烈建议把这个操作放在finally代码块中:
举个例子:
Socket socket=null;
try{
socket=new Socket(127.0.0.1,80);
//执行接收和发送数据的操作
。。。
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(socket!=null) socket.close();
}catch(IOException e){
eprintStackTrace();}
}