Socket 的学习

      (一). socket 描述 

      HTTP通信中Client发送的每次请求都需要Server回送响应,在请求结束后,Client会主动释放连接。从建立连接到隔壁连接的过程成为一次连接。要保持Client程序的在线状态,需要不断地向Server发送请求。通常的做法是,即使不需要获取任何数据,Client也保持每隔一段固定的时间向Server发送一次保持连接的请求,Server在收到该请求后对Client进行回复,表明知道Client在线。若Server长时间无法收到Client的请求,则认为Client下线,若Client长时间无法收到Server的回复,则认为网络已经断开。

     Socket(套接字)用于描述IP地址和端口。App常常通过Socket向网络发出请求或者应答网络请求。Socket是支持TCP/IP协议的网络通信的基本操作单元,是网络通信过程中端点的抽象表示,包含进行网络通信必需的5种信息:网络协议、本地IP地址、本地端口、远程IP地址、远程端口。

    Socket有两种传输模式:面向连接和无连接。面向连接的Socket操作就像一部电话,必须建立一个连接。所有的数据达到时的顺序和它们发送时的顺序是一样的。无连接的Socket操作就像一个邮件投递,多个邮件达到时的顺序可能和发送的顺序不一样。到底用哪种模式是由应用程序的需要决定。如果可靠性更重要,用面向连接的模式会更好。确保数据的有序性和正确性需要额外的操作,这会带来内存消耗,降低系统的效率。无连接的操作使用数据报协议。一个数据报是一个独立的单元,它包含了这次投递的所有信息(目的地址、要发送的内容)。这种模式下的Socket不需要连接目的Socket,它只是简单地投出数据报。无连接的操作时快速的和高效的,但数据安全性不佳。面向连接的操作使用TCP协议。面向连接的Socket必须在发送数据之前和目的地Socket取得连接。一旦建立了连接,Socket就可以使用一个流接口来进行打开、读/写、关闭操作。所有发送的信息都会在另一端以同样的顺序接收。面向连接比无连接效率更低,但是数据更安全。

        (二)scoket 的通信方式模式,通常有三种 传统的阻塞式通信,非阻塞式通信和大名鼎鼎的MINA网络框架

                 1.统socket:阻塞式通信 。每建立一个Socket连接时,同时创建一个新线程对该Socket进行单独通信(采用阻塞的方式通信)。
这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较少的时候非常有效,但是如果对每一个连接都产生一个线程的无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况

        

example:
server code:

</pre></p><pre name="code" class="javascript">public class MultiUserServer extends Thread {
 private Socket client;
 
 public MultiUserServer(Socket c) {
  this.client = c;
 }

 public void run() {
  try {
   BufferedReader in = new BufferedReader(new InputStreamReader(client
     .getInputStream()));
   PrintWriter out = new PrintWriter(client.getOutputStream());
   // Mutil User but can't parallel
   while (true) {
    String str = in.readLine();
    System.out.println(str);
    SocketLog.debug("receive message: " + str);
    out.println("has receive....");
    out.flush();
    if (str.equals("end"))
     break;
   }
   client.close();
  } catch (IOException ex) {
  } 
 }

 
public static void main(String[] args) throws IOException {
  int port = 5678;
  if (args.length > 0)
   port = Integer.parseInt(args[0]);
  ServerSocket server = new ServerSocket(port);
  SocketLog.debug("the server socket application is created!");
  while (true) {
   // transfer location change Single User or Multi User
   MultiUserServer mu = new MultiUserServer(server.accept());
   mu.start();
  }
 }
}

<span style="font-size:14px;"></span>

client code:

public class Client {

 static Socket server;

 public static void main(String[] args) throws Exception {
  
  //set socket proxy.
  String proxyHost = "192.168.254.212";
  String proxyPort = "1080";
  System.getProperties().put("socksProxySet","true");
  System.getProperties().put("socksProxyHost",proxyHost);
  System.getProperties().put("socksProxyPort",proxyPort); 
  
  String host = "132.201.69.80";
  int port = 13086;
  if (args.length > 1)
  {
   host = args[0];
   port = Integer.parseInt(args[1]);
  }
  System.out.println("connetioning:" + host + ":" + port);
  server = new Socket(host, port);
  BufferedReader in = new BufferedReader(new InputStreamReader(server
    .getInputStream()));
  PrintWriter out = new PrintWriter(server.getOutputStream());
  BufferedReader wt = new BufferedReader(new InputStreamReader(System.in));
  while (true) {
   String str = wt.readLine();
   out.println(str);
   out.flush();
   if (str.equals("end")) {
    break;
   }
   System.out.println(in.readLine());
  }
  server.close();
 }
}

<span style="font-size:14px;"></span>

         

               2.nio:非阻塞通讯模式.NIO 设计背后的基石:反应器模式,用于事件多路分离和分派的体系结构模式。 反应器模式的核心功能如下:
将事件多路分用 将事件分派到各自相应的事件处理程序NIO 的非阻塞 I/O 机制是围绕 选择器和 通道构建的。 Channel 类表示服务器和客户机之间的
一种通信机制。Selector 类是 Channel 的多路复用器。 Selector 类将传入客户机请求多路分用并将它们分派到各自的请求处理程序。通道(Channel 类):表示服务器和客户机之间的一种通信机制。 选择器(Selector类):是 Channel 的多路复用器。Selector 类将传入的客户机请求多路分用并将它们分派到各自的请求处理程序。
           简单的来说:
NIO是一个基于事件的IO架构,最基本的思想就是:有事件我通知你,你再去做你的事情.而且NIO的主线程只有一个,不像传统的模型,需要多个线程以应对客户端请求,也减轻了JVM的工作量。当Channel注册至Selector以后,经典的调用方法如下:

        
 while (somecondition) {
            int n = selector.select(TIMEOUT);
            if (n == 0)
                continue;
            for (Iterator iter = selector.selectedKeys().iterator(); iter
                    .hasNext();) {
                if (key.isAcceptable())
                    doAcceptable(key);
                if (key.isConnectable())
                    doConnectable(key);
                if (key.isValid() && key.isReadable())
                    doReadable(key);
                if (key.isValid() && key.isWritable())
                    doWritable(key);
                iter.remove();
            }
        }
          nio中取得事件通知,就是在selector的select事件中完成的。在selector事件时有一个线程 向操作系统询问,selector中注册的Channel&&SelectionKey的键值对的各种事件是否有发生, 如果有则添加到selector的selectedKeys属性Set中去,并返回本次有多少个感兴趣的事情发生。 如果发现这个值>0,表示有事件发生,马上迭代selectedKeys中的SelectionKey, 根据Key中的表示的事件,来做相应的处理。 实际上,这段说明表明了异步socket的核心,即异步socket不过是将多个socket的调度(或者还有他们的线程调度)
全部交给操作系统自己去完成,异步的核心Selector,不过是将这些调度收集、分发而已。

      

     2.2 nio example:
      server code:


     

public class NonBlockingServer
{
    public Selector sel = null;
    public ServerSocketChannel server = null;
    public SocketChannel socket = null;
    public int port = 4900;
    String result = null;


    public NonBlockingServer()
    {
  System.out.println("Inside default ctor");
    }
    
 public NonBlockingServer(int port)
    {
  System.out.println("Inside the other ctor");
  this.port = port;
    }

    public void initializeOperations() throws IOException,UnknownHostException
    {
  System.out.println("Inside initialization");
  sel = Selector.open();
  server = ServerSocketChannel.open();
  server.configureBlocking(false);
  InetAddress ia = InetAddress.getLocalHost();
  InetSocketAddress isa = new InetSocketAddress(ia,port);
  server.socket().bind(isa);
    }
    
 public void startServer() throws IOException
    {
  System.out.println("Inside startserver");
        initializeOperations();
  System.out.println("Abt to block on select()");
  SelectionKey acceptKey = server.register(sel, SelectionKey.OP_ACCEPT ); 
 
  while (acceptKey.selector().select() > 0 )
  { 
     
   Set readyKeys = sel.selectedKeys();
   Iterator it = readyKeys.iterator();

   while (it.hasNext()) {
    SelectionKey key = (SelectionKey)it.next();
    it.remove();
                
    if (key.isAcceptable()) {
     System.out.println("Key is Acceptable");
     ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
     socket = (SocketChannel) ssc.accept();
     socket.configureBlocking(false);
     SelectionKey another = socket.register(sel,SelectionKey.OP_READ|SelectionKey.OP_WRITE);
    }
    if (key.isReadable()) {
     System.out.println("Key is readable");
     String ret = readMessage(key);
     if (ret.length() > 0) {
      writeMessage(socket,ret);
     }
    }
    if (key.isWritable()) {
     System.out.println("THe key is writable");
     String ret = readMessage(key);
     socket = (SocketChannel)key.channel();
     if (result.length() > 0 ) {
      writeMessage(socket,ret);
     }
    }
   }
  }
    }

    public void writeMessage(SocketChannel socket,String ret)
    {
  System.out.println("Inside the loop");

  if (ret.equals("quit") || ret.equals("shutdown")) {
   return;
  }
  try
  {

   String s = "This is context from server!-----------------------------------------";
   Charset set = Charset.forName("us-ascii");
   CharsetDecoder dec = set.newDecoder();
   CharBuffer charBuf = dec.decode(ByteBuffer.wrap(s.getBytes()));
   System.out.println(charBuf.toString());
   int nBytes = socket.write(ByteBuffer.wrap((charBuf.toString()).getBytes()));
   System.out.println("nBytes = "+nBytes);
    result = null;
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }

    }
  
    public String readMessage(SelectionKey key)
    {
  int nBytes = 0;
  socket = (SocketChannel)key.channel();
        ByteBuffer buf = ByteBuffer.allocate(1024);
  try
  {
            nBytes = socket.read(buf);
   buf.flip();
   Charset charset = Charset.forName("us-ascii");
   CharsetDecoder decoder = charset.newDecoder();
   CharBuffer charBuffer = decoder.decode(buf);
   result = charBuffer.toString();
     
        }
  catch(IOException e)
  {
   e.printStackTrace();
  }
  return result;
    }

     
public static void main(String args[])
    {
     NonBlockingServer nb;
     if (args.length < 1)
     {
      nb = new NonBlockingServer();
     }
     else
     {
      int port = Integer.parseInt(args[0]);
      nb = new NonBlockingServer(port);
     }
   
  try
  {
   nb.startServer();
   System.out.println("the nonBlocking server is started!");
  }
  catch (IOException e)
  {
   e.printStackTrace();
   System.exit(-1);
  }
  
 }
}

        2.2 client code:

       
public class Client {
 public SocketChannel client = null;

 public InetSocketAddress isa = null;

 public RecvThread rt = null;

 private String host;

 private int port;

 public Client(String host, int port) {
  this.host = host;
  this.port = port;
 }

 public void makeConnection() {
  String proxyHost = "192.168.254.212";
  String proxyPort = "1080";
  System.getProperties().put("socksProxySet", "true");
  System.getProperties().put("socksProxyHost", proxyHost);
  System.getProperties().put("socksProxyPort", proxyPort);

  int result = 0;
  try {
   client = SocketChannel.open();
   isa = new InetSocketAddress(host, port);
   client.connect(isa);
   client.configureBlocking(false);
   receiveMessage();
  } catch (UnknownHostException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
  long begin = System.currentTimeMillis();

  sendMessage();

  long end = System.currentTimeMillis();
  long userTime = end - begin;
  System.out.println("use tiem: " + userTime);
  try {
   interruptThread();
   client.close();
   System.exit(0);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 public int sendMessage() {
    System.out.println("Inside SendMessage");
  String msg = null;
  ByteBuffer bytebuf;
  int nBytes = 0;
  try {
   msg = "It's message from client!";
   System.out.println("msg is "+msg);
   bytebuf = ByteBuffer.wrap(msg.getBytes());
   for (int i = 0; i < 1000; i++) {
    nBytes = client.write(bytebuf);
    System.out.println(i + " finished");
   }
   interruptThread();
   try {
    Thread.sleep(5000);
   } catch (Exception e) {
    e.printStackTrace();
   }
   client.close();
   return -1;

  } catch (IOException e) {
   e.printStackTrace();
  }

  return nBytes;

 }

 public void receiveMessage() {
  rt = new RecvThread("Receive THread", client);
  rt.start();

 }

 public void interruptThread() {
  rt.val = false;
 }
         
     
public static void main(String args[]) {
  if (args.length < 2) {
   System.err.println("You should put 2 args: host,port");
  } else {
   String host = args[0];
   int port = Integer.parseInt(args[1]);
   Client cl = new Client(host, port);
   cl.makeConnection();
  }
  BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  String msg;

 }

 public class RecvThread extends Thread {
  public SocketChannel sc = null;

  public boolean val = true;

  public RecvThread(String str, SocketChannel client) {
   super(str);
   sc = client;
  }

  public void run() {
   int nBytes = 0;
   ByteBuffer buf = ByteBuffer.allocate(2048);
   try {
    while (val) {
     while ((nBytes = nBytes = client.read(buf)) > 0) {
      buf.flip();
      Charset charset = Charset.forName("us-ascii");
      CharsetDecoder decoder = charset.newDecoder();
      CharBuffer charBuffer = decoder.decode(buf);
      String result = charBuffer.toString();
      System.out.println("the server return: " + result);
      buf.flip();

     }
    }

   } catch (IOException e) {
    e.printStackTrace();

   }

  }
 }
}

     

       3:Socket网络框架 MINAMINA是一个网络应用框架,在不牺牲性能和可扩展性的前提下用于解决如下问题:
            1:快速开发自己的英勇。
            2:高可维护性,高可复用性:网络I/O编码,消息的编/解码,业务逻辑互相分离。
            3:相对容易的进行单元测试。

 

          3.1 IoFilters:
            IoFilter为MINA的功能扩展提供了接口。它拦截所有的IO事件进行事件的预处理和后处理(AOP)。我们可以把它想象成Servlet的filters。IoFilter能够实现以下几种目的:
                  事件日志
                  性能检测
                 数据转换(e.g. SSL support),codec
                 防火墙…等等

          3.2 codec: ProtocolCodecFactory
           MINA提供了方便的Protocol支持。如上说讲,codec在IoFilters中设置。
           通过它的Encoder和Decoder,可以方便的扩展并支持各种基于Socket的网络协议,比如HTTP服务器、FTP服务器、Telnet服务器等等。

           要实现自己的编码/解码器(codec)只需要实现interface: ProtocolCodecFactory即可.
           在MINA 1.0版本,MINA已经实现了几个常用的(codec factory):

            DemuxingProtocolCodecFactory, 
            NettyCodecFactory, 
            ObjectSerializationCodecFactory, 
            TextLineCodecFactory

     

          3.3 business logic: IoHandler

          MINA中,所有的业务逻辑都有实现了IoHandler的class完成
              interfaceHandles:all protocol events fired by MINA. There are 6 event handler methods, and they are all invoked by MINA automatically. 
          当事件发生时,将触发IoHandler中的方法:
              sessionCreated, sessionOpened, sessionClosed, sessionIdle, exceptionCaught, messageReceived, messageSent
          MINA 1.O中,IoHandler的实现类:
            ChainedIoHandler, DemuxingIoHandler, IoHandlerAdapter, SingleSessionIoHandlerDelegate, StreamIoHandler 
          具体细节可参考javadoc。

        3.4   MINA的高级主题:线程模式
          MINA通过它灵活的filter机制来提供多种线程模型。没有线程池过滤器被使用时MINA运行在一个单线程模式。
          如果添加了一个IoThreadPoolFilter到IoAcceptor,将得到一个leader-follower模式的线程池。
          如果再添加一个ProtocolThreadPoolFilter,server将有两个线程池;
          一个(IoThreadPoolFilter)被用于对message对象进行转换,另外一个(ProtocolThreadPoolFilter)被用于处理业务逻辑。 
          SimpleServiceRegistry加上IoThreadPoolFilter和ProtocolThreadPoolFilter的缺省实现即可适用于需

<span style="background-color: rgb(238, 238, 221); font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13px; line-height: 19px;">        要高伸缩性的应用。如果想使用自己的线程模型,请参考SimpleServiceRegistry的源代码,并且自己</span>

         初始化Acceptor。

        

IoThreadPoolFilter threadPool = new IoThreadPoolFilter();threadPool.start();
IoAcceptor acceptor = new SocketAcceptor();
acceptor.getFilterChain().addLast( "threadPool", threadPool);
ProtocolThreadPoolFilter threadPool2 = new ProtocolThreadPoolFilter();
threadPool2.start();
ProtocolAcceptor acceptor2 = new IoProtocolAcceptor( acceptor );
acceptor2.getFilterChain().addLast( "threadPool", threadPool2 );
...
threadPool2.stop();
threadPool.stop();

            采用MINA进行socket开发,一般步骤如下:

        

1:
server:
IoAcceptor acceptor = new SocketAcceptor(); //建立client接收器
or client:
SocketConnector connector = new SocketConnector();  //建立一个连接器
2:server的属性配置:
        SocketAcceptorConfig cfg = new SocketAcceptorConfig();
        cfg.setReuseAddress(true);
        cfg.getFilterChain().addLast(
                    "codec",
                    new ProtocolCodecFilter( new ObjectSerializationCodecFactory() ) ); //对象序列化 codec factory
        cfg.getFilterChain().addLast( "logger", new LoggingFilter() );
3:绑定address和business logic
server:
        acceptor.bind(
                new InetSocketAddress( SERVER_PORT ),
                new ServerSessionHandler( ), cfg ); // 绑定address和handler

client:
        connector.connect(new InetSocketAddress( HOSTNAME, PORT ),
                        new ClientSessionHandler(msg), cfg );


          下面的这个简单的example演示client和server传递object的过程:

         客户端的
Message.java
public class Message implements Serializable {

    private int type;
    private int status;
    private String msgBody;
    
    public Message(int type, int status, String msgBody)
    {
        this.type = type;
        this.status = status;
        this.msgBody = msgBody;
    }

    public String getMsgBody() {
        return msgBody;
    }

    public void setMsgBody(String msgBody) {
        this.msgBody = msgBody;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }
}
      
Client.java
public class Client
{
    private static final String HOSTNAME = "localhost";
    private static final int PORT = 8080;
    private static final int CONNECT_TIMEOUT = 30; // seconds


    public static void main( String[] args ) throws Throwable
    {
        SocketConnector connector = new SocketConnector();        
        // Configure the service.
        SocketConnectorConfig cfg = new SocketConnectorConfig();
        cfg.setConnectTimeout( CONNECT_TIMEOUT );
          cfg.getFilterChain().addLast(
                    "codec",
                    new ProtocolCodecFilter( new ObjectSerializationCodecFactory() ) );

        cfg.getFilterChain().addLast( "logger", new LoggingFilter() );
        
        IoSession session;
        Message msg = new Message(0,1,"hello");
        connector.connect(new InetSocketAddress( HOSTNAME, PORT ),
                        new ClientSessionHandler(msg), cfg );

    }
}
       
ClientSessionHandler.java
public class ClientSessionHandler extends IoHandlerAdapter
{
    private Object msg;
    
    public ClientSessionHandler(Object msg)
    {
        this.msg = msg;
    }


    public void sessionOpened( IoSession session )
    {
        session.write(this.msg);
    }

    public void messageReceived( IoSession session, Object message )
    {
        System.out.println("in messageReceived!");
        Message rm = (Message ) message;        
        SessionLog.debug(session, rm.getMsgBody());
        System.out.println("message is: " + rm.getMsgBody());
        session.write(rm);
    }

    public void exceptionCaught( IoSession session, Throwable cause )
    {
        session.close();
    }
}
        服务端的

            
Server.java
public class Server
{
    private static final int SERVER_PORT = 8080;

    public static void main( String[] args ) throws Throwable
    {
        IoAcceptor acceptor = new SocketAcceptor();
        
        // Prepare the service configuration.
        SocketAcceptorConfig cfg = new SocketAcceptorConfig();
        cfg.setReuseAddress( true );

        cfg.getFilterChain().addLast(
                    "codec",
                    new ProtocolCodecFilter( new ObjectSerializationCodecFactory() ) );
        cfg.getFilterChain().addLast( "logger", new LoggingFilter() );

        acceptor.bind(
                new InetSocketAddress( SERVER_PORT ),
                new ServerSessionHandler( ), cfg );

        System.out.println( "The server Listening on port " + SERVER_PORT );
    }
}
     
ServerSessionHandler.java
public class ServerSessionHandler extends IoHandlerAdapter
{
    public void sessionOpened( IoSession session )
    {
        // set idle time to 60 seconds
        session.setIdleTime( IdleStatus.BOTH_IDLE, 60 );
        session.setAttribute("times",new Integer(0));
    }

    public void messageReceived( IoSession session, Object message )
    {
        System.out.println("in messageReceived");
        int times = ((Integer)(session.getAttribute("times"))).intValue();
        System.out.println("tiems = " + times);
        // communicate 30 times,then close the session.
        if (times < 30)
        {
            times++;
            session.setAttribute("times", new Integer(times));           
         Message msg;
         msg = (Message) message;
         msg.setMsgBody("in server side: " + msg.getMsgBody()); 
         System.out.println("begin send msg: " + msg.getMsgBody());
         session.write(msg);
        }
        else
        {
            session.close();
        }
    }

    public void sessionIdle( IoSession session, IdleStatus status )
    {
        SessionLog.info( session, "Disconnecting the idle." );
        // disconnect an idle client
        session.close();
    }

    public void exceptionCaught( IoSession session, Throwable cause )
    {
        // close the connection on exceptional situation
        session.close();
    }
}

     MINA自己附带的Demo已经很好的说明了它的运用。
值得一提的是它的SumUp:客户端发送几个数字,服务端求和后并返回结果。这个简单的程序演示了如何自己实现CODEC。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值