java 关闭tcpclient 链接_ci

      这个答案答的很好。Socket与SocketChannel是俩套api而已,对于网络tcp通信而言不会关心你上层是用何api实现通信的。所以答案是肯定的。SocketChannel可以设置为非阻塞的,所以在某种情况下性能更好,线程不会被挂住。SocketChannel还能注册selector和感兴趣的事件。selector多路复用器这里就不多做介绍了。

     在这里介绍一下通信过程中数据的流动过程。首先当数据被网卡接收后,会被保存到内核中,电脑进行拆包解析,看源端口号,会将此数据包保存到对应链接的tcp接收缓冲区(有的地方成为Socket缓冲区)。当此缓冲区满了后,会发生的动作是:通知对端TCP协议中的窗口关闭。这便是滑动窗口的实现。保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。这就是TCP的流量控制。(刺猬本人自己理解如有不准确还望提出)

  虾面说一下聊天程序server端的简单实现逻辑。我们要实现私聊,当两个客户端与服务端建立连接后,服务端会保存所有建立连接的客户端信息:包括此客户端的name,Socket或SocketChannel(当建立连接后会返回)。其中A客户端向B客户端发送信息“B,hello!”。服务端收到消息后截取字符串“B”,get出与之对应的Socket(NIO的话为SocketChannel),往其中写入读到的内容。完成私聊。虾面贴出服务端的程序:(格式没了凑活看吧,过几天准备再用netty实现此功能)

package com.server;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;import com.server.vo.UserInfo;
public class NioServer implements Runnable {
	private static HashMap<String,UserInfo> users=new HashMap<String,UserInfo>();

	private Socket client;

	private Selector selector;	private ServerSocketChannel serverChannel;
	private volatile boolean stop;

	private UserInfo userinfo;	/**
	 * 初始化多路复用器,绑定监听端口
	 */
	public NioServer(int port) {
		try {
			selector = Selector.open();
			serverChannel = ServerSocketChannel.open();
			serverChannel.configureBlocking(false);
			serverChannel.bind(new InetSocketAddress(port), 1024);
			serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}	public void stop() {
		this.stop = true;
	}	@Override
	public void run() {
		while (!stop) {
			try {
				selector.select(1000);
				Set<SelectionKey> selectionKeys= selector.selectedKeys();
				SelectionKey key=null;
				Iterator<SelectionKey> it= selectionKeys.iterator();
				while(it.hasNext()){
					key=it.next();
					it.remove();
					try{
						handleInput(key);
					}catch(Exception e){
						e.printStackTrace();
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		if(selector!=null){
			try{
				selector.close();
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}

	public void handleInput(SelectionKey key) throws IOException{
		if(key.isValid()){
			if(key.isAcceptable()){
				ServerSocketChannel ssc=(ServerSocketChannel) key.channel();
				SocketChannel sc= ssc.accept();
				sc.configureBlocking(false);
				sc.register(selector, SelectionKey.OP_READ);
				userinfo = new UserInfo();
				userinfo.setUsername(sc.getRemoteAddress()+"");
				userinfo.setSocket(sc.socket());
				users.put(sc.getRemoteAddress()+"", userinfo);
				System.out.println(users);
			}
			if(key.isReadable()){
				SocketChannel sc= (SocketChannel) key.channel();
				ByteBuffer readBuffer=ByteBuffer.allocate(1024);//ByteBuffer最核心的方法是put(byte)和get()。分别是往ByteBuffer里写一个字节,和读一个字节。
				// 执行以上方法后,通道会从socket读取的数据填充此缓冲区,它返回成功读取并存储在缓冲区的字节数.在默认情况下,这至少会读取一个字节,或者返回-1指示数据结束.
				int readBytes=sc.read(readBuffer);//写入缓冲器
				if(readBytes>0){
					readBuffer.flip();//写模式转换成读模式
					byte[] bytes=new byte[readBuffer.remaining()];
					readBuffer.get(bytes);//读取到bytes中
					String body=new String(bytes,"UTF-8");
					String name=body.substring(0,body.lastIndexOf(".")+8);
					System.out.println(body);
					SocketChannel soc= users.get(name).getSocket().getChannel();
					doWrite(soc,body);
				}
			}
		}
	}

	private void doWrite(SocketChannel channel,String response) throws IOException{
		if(response!=null&&response.trim().length()>0){
			byte[] bytes=response.getBytes();
			ByteBuffer bf=ByteBuffer.allocate(bytes.length);
			bf.put(bytes);//往缓冲器中写数据,将bytes写入缓冲器
			bf.flip();//写模式转换成读模式
			channel.write(bf);//读取出缓冲器数据写入到channel中
		}
	}

	public static void main(String[] args){
		NioServer ns=new NioServer(8088);
		ns.run();
	}//byteBuffer = ByteBuffer.allocate(N);
////读取数据,写入byteBuffer
//readableByteChannel.read(byteBuffer);
////变读为写
//byteBuffer.flip();
////读取byteBuffer,写入数据
//writableByteChannel.write(byteBuffer);
//	看一段程序:
//	 1:ByteBuffer buffer = ByteBuffer.allocate(5);  position=0,limit=5,capacity=5
//	 2:buffer.put((byte)1);                         position=1,limit=5,capacity=5
//	 3:buffer.flip();                               position=0,limit=1,capacity=5
//	 4:buffer.get();                                position=1,limit=1,capacity=5
//
//	 第一行:创建一个ByteBuffer,初始化状态下,postion为0,limit=capacity=缓冲区大小
//	 第二行:往缓冲区放一个字节,position就会往后移动一下,但是不会超过limit。如果超过会报错。
//	 第三行: flip方法会把limit设为position的值,而position还原为0,为读缓冲区做准备。
//	 第四行:从缓冲区内读取一个字节,position会往后移动一下,但是不会超过limit,如果超过会报错。}