Java 中的 Socket 的用法 |
普通 Socket 的用法 |
Java 中的网络通信时通过 Socket 实现的,Socket 分为 ServerSocket 和 Socket 两大类,ServerSocket 用于服务器端,可以通过 accept 方法监听请求,监听请求后返回 Socket,Socket 用于完成具体数据传输,客户端也可以使用 Socket 发起请求并传输数据。ServerSocket 的使用可以分为三步:
如下代码,我们在服务器端创建 ServerSocket,并调用 accept 方法监听 Client 的请求,收到请求后返回一个 Socket。
View Code
- public class Server {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- try {
- //创建一个ServerSocket监听8080端口
- ServerSocket server = new ServerSocket(8080);
- //等待请求
- Socket socket = server.accept();
- //接受请求后使用Socket进行通信,创建BufferedReader用于读取数据
- BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- String line = is.readLine();
- System.out.println("received frome client:" + line);
- //创建PrintWriter,用于发送数据
- PrintWriter pw = new PrintWriter(socket.getOutputStream());
- pw.println("this data is from server");
- pw.flush();
- //关闭资源
- pw.close();
- is.close();
- socket.close();
- server.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
然后我们再看看客户端的 Socket 代码,Socket 的使用也是一样,首先创建一个 Socket,Socket 的构造方法非常多,这里用的是 Socket(String host, int port),把目标主机的地址和端口号传入即可(本实验代码中服务器和 Client 代码没有在同一台机器上,服务器的 IP 地址:192.168.6.42,所以如果读者在实验过程中 ServerSocket 和 Client 在同一主机下,那么 Client 中的 IP 地址需要更改为:127.0.0.1,Socket 创建的过程就会跟服务器端建立连接,创建完 Socket 后,再创建 Writer 和 Reader 来传输数据,数据传输完成后释放资源关闭连接。
View Code
- public class Client {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- String msg = "Client data";
- try {
- //创建一个Socket,跟服务器的8080端口链接
- Socket socket = new Socket("192.168.6.42",8080);
- //使用PrintWriter和BufferedReader进行读写数据
- PrintWriter pw = new PrintWriter(socket.getOutputStream());
- BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- //发送数据
- pw.println(msg);
- pw.flush();
- //接收数据
- String line = is.readLine();
- System.out.println("received from server" + line);
- //关闭资源
- pw.close();
- is.close();
- socket.close();
- } catch (UnknownHostException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
最后先启动 Server 然后启动 Client 就可以完成一次 Client 和 Server 的通信。
NioSocket 的用法 |
从 JDK1.4 开始,Java 增加了新的 IO 模式 - nio(new IO),nio 在底层采用了新的处理方式,极大提高了 IO 的效率。我们使用的 Socket 也是 IO 的一种,nio 提供了相应的工具:ServerSocketChannel 和 SocketChannel,他们分别对应原来的 ServerSocket 和 Server。
在了解 NioSocket 之前我们先了解 Buffer、Channel、Selector。为了方便理解,我们来看个例子,要过圣诞节了,需要给同学们发贺卡和苹果,班长这时候又是最辛苦的,每次拿一个苹果和一张贺卡发给一个同学,发送完成后回来再取一张贺卡和一个苹果发给另一个同学,直到全班同学都拿到贺卡和苹果为止,这就是普通 Socket 处理方式,来一个请求,ServerSocket 就进行处理,处理完成后继续接受请求,这种方式效率很低啊!还是圣诞节的例子,班长发现班委不止他一个,就通知了生活委员(女)和组织委员(男)来帮助他发贺卡和苹果,女生的贺卡是粉色的,男生的贺卡是蓝色的,生活委员负责从全班的贺卡中挑选女生的贺卡,而组织委员则负责男生的贺卡,然后生活委员和组织委员分别以宿舍为单位通知宿舍长来领取宿舍同学的贺卡和苹果,班长将圣诞节发苹果和贺卡的工作布置给两个班委后,就可以继续干其他工作了。这就是 NioSocket,Buffer 就是所有传递的货物,也就是例子中的苹果和贺卡,而 Channel 就是传递货物的通道,也就是例子中的宿舍长,负责将礼物搬回自己宿舍,而生活委员和组织委员充当了 Selector 的职责,负责礼物的分拣。
ServerSocketChannel 可以使用自己的静态工厂方法 open 创建,每个 ServerSocketChannel 对应一个 ServerSocket(通过调用其 socket() 获取),如果直接使用获取的 ServerSocket 来监听请求,那么还是普通 ServerSocket,而通过将获取的 ServerSocket 绑定端口号来实现 NioSocket。ServerSocketChannel 可以通过 configureBlocking 方法来设置是否采用阻塞模式,如果设置为非阻塞模式,就可以调用 register 方法注册 Selector 来使用了。
Selector 可以通过其静态工厂方法 open 创建,创建后通过 Channel 的 register 方法注册到 ServerSocketChannel 或者 SocketChannel 上,注册完成后 Selector 就可以通过 select 方法来等待请求,select 方法有一个 long 类型参数,代表最长等待时间,如果在这段时间内收到相应操作的请求则返回可以处理的请求的数量,否则在超时后返回 0,如果传入的参数为 0 或者无参数的重载方法,select 方法会采用阻塞模式知道有相应操作请求的出现。当接收到请求后 Selector 调用 selectdKeys 方法返回 SelectionKey 集合。
SelectionKey 保存了处理当前请求的 Channel 和 Selector,并且提供了不同的操作类型。Channel 在注册 Selector 时可以通过 register 的第二个参数选择特定的操作(请求操作、连接操作、读操作、写操作),只有在 register 中注册了相应的操作 Selector 才会关心相应类型操作的请求。
介绍了这么多估计大家也烦了,我们就来看看服务器端 NioSocket 的处理过程吧:
View Code
- public class NIOServer {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- try {
- //创建ServerSocketChannel,监听8080端口
- ServerSocketChannel ssc = ServerSocketChannel.open();
- ssc.socket().bind(new InetSocketAddress(8080));
- //设置为非阻塞模式
- ssc.configureBlocking(false);
- //为ssc注册选择器
- Selector selector = Selector.open();
- ssc.register(selector, SelectionKey.OP_ACCEPT);
- //创建处理器
- Handler handler = new Handler(1024);
- while(true){
- //等待请求,每次等待阻塞3s,超过3s后线程继续向下运行,如果传入0或者不传入参数则一直阻塞
- if(selector.select(3000) == 0){
- System.out.println("等待请求超时----");
- continue;
- }
- System.out.println("处理请求----");
- //获取处理的SelectionKey
- Iterator keyIter = selector.selectedKeys().iterator();
- while(keyIter.hasNext()){
- SelectionKey key = keyIter.next();
- try{
- //接收到连接请求时
- if(key.isAcceptable()){
- handler.handleAccept(key);
- }
- //读数据
- if(key.isReadable()){
- handler.handleRead(key);
- }
- }catch(IOException ex){
- keyIter.remove();
- continue;
- }
- //处理完后,从待处理的SelectionKey迭代器中移除当前所使用的key
- keyIter.remove();
- }
- }
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- private static class Handler{
- private int bufferSize = 1024;
- private String localCharset = "UTF-8";
- public Handler(int bufferSize){
- this.bufferSize = bufferSize;
- }
- public void handleAccept(SelectionKey key) throws IOException{
- SocketChannel sc = ((ServerSocketChannel) key.channel()).accept();
- sc.configureBlocking(false);
- sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
- }
- public void handleRead(SelectionKey key) throws IOException{
- //获取Channel
- SocketChannel sc = (SocketChannel) key.channel();
- //获取buffer并重置
- ByteBuffer buffer = (ByteBuffer)key.attachment();
- buffer.clear();
- //没有读到内容则关闭
- if(sc.read(buffer) == -1)
- sc.close();
- else{
- //将buffer转换为读状态
- buffer.flip();
- //将buffer中接收到的值按localCharset格式编码后保存到receivedString
- String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
- System.out.println("received from client:" + receivedString);
- //返回数据给客户端
- String sendString = "this data is from Server";
- buffer = ByteBuffer.wrap(sendString.getBytes(localCharset));
- sc.write(buffer);
- sc.close();
- }
- }
- }
- }
客户端代码通普通 Socket 一样,Socket socket = new Socket("192.168.6.42",8080); 表示与服务器端建立连接,从而执行服务器端的 handleAccept() 方法,给 ServerSocketChannel 注册 selector 以及添加 SelectionKey.OP_READ 参数,表示 selector 关心读方法。然后通过 PrintWrite 在客户端将内容发送给服务器端,服务器端执行 handleRead 方法对接收到的内容进行处理,并将结果返回给客户端,客户端通过 BufferedReader 接受数据,最后关闭连接。
来源: