TCP单线程(最简单)
|
|
|
|
TCP多线程
服务器主程序
1234567891011121314public class Server {//创建线程安全的ArrayList来保存与服务器连接的Socketpublic static List<Socket> socketList = Collections.synchronizedList(new ArrayList<>());public static void main(String[] args) throws Exception{//创建ServerSocket,监听30000端口的请求ServerSocket ss = new ServerSocket(20000);while(true){Socket s = ss.accept();socketList.add(s);//每个客户端新建一个服务线程new Thread(new ServerThread(s)).start();}}}服务器服务线程:负责将客户端送来的数据返回到和服务器连接的所有客户端
1234567891011121314151617181920212223242526272829303132333435public class ServerThread implements Runnable {//定义当前处理的SocketSocket s = null;BufferedReader br = null;public ServerThread(Socket s) throws Exception{this.s = s;br = new BufferedReader(new InputStreamReader(s.getInputStream()));}@Overridepublic void run() {try{String content = null;while((content = readFromClient()) != null ){for(Socket s : Server.socketList){PrintStream ps = new PrintStream(s.getOutputStream());ps.println(content);}}}catch(IOException e){e.printStackTrace();}}private String readFromClient() {try{return br.readLine();}catch(IOException e){Server.socketList.remove(s);}return null;}}客户端主程序
1234567891011121314151617public class Client {public static void main(String[] args) throws Exception {//创建Socket,绑定IP和端口Socket s = new Socket("127.0.0.1", 20000);//客户端启动线程不断读服务器的返回的数据new Thread(new ClientThread(s)).start();//获取Socket的输出流PrintStream ps = new PrintStream(s.getOutputStream());//获取键盘的输入流BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String line = null;while((line = br.readLine()) != null){//将键盘输入的数据输出到Socketps.println(line);}}}客户端服务线程:不停读取服务器发来的数据,并打印到屏幕
12345678910111213141516171819202122public class ClientThread implements Runnable {private Socket s;//Socket的输入流,接受来自服务器的数据BufferedReader br = null;public ClientThread(Socket s) throws Exception {this.s = s ;br = new BufferedReader(new InputStreamReader(s.getInputStream()));}@Overridepublic void run() {try{String content = null;while((content = br.readLine()) != null){System.out.println(content);}}catch(IOException e){e.printStackTrace();}}}
TCP基于NIO
- 服务器端有ServerSocketChannel和SocketChannel。ServerSocketChannel负责用来监听客户端请求,SocketChannel负责和客户端传输数据,二者都需要注册到Selector中。
- 客户端有SocketChannel,负责与服务器传输数据,也需要注册到Selector中
创建一个ServerSocketChannel,并设置其为非阻塞模式,将其注册到指定Selector
12345678910//通过open方法来打开一个未绑定的ServerSocketChannel实例ServerSocketChannel server = new ServerSocketChannel.open();//将ServerSocketChannel绑定到指定的IP地址InetSocketAddress isa = new InetSocketAddress("127.0.0.1",10000);server.bind(isa);//设置ServerSocketChannel以非阻塞模式工作server.configureBlocking(false);//将server注册到指定的Selector对象//之后可以调用Selecetor的select()方法来监听所有Channelserver.register(selector , SelectionKey.OP_ACCEPT);编程实例 (服务器端)
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273public class NServer {private Selector selector = null;static final int PORT = 12000;//设置编译码的码字集,编译码的目的是将String转换成ByteBuffer,用于SocketChannel的传输private Charset charset = Charset.forName("UTF-8");private void init() throws IOException{//开启Selectorselector = Selector.open();//开启ServerSocketChannelServerSocketChannel server = ServerSocketChannel.open();//ServerSocketChannel绑定IP和端口InetSocketAddress isa = new InetSocketAddress("127.0.0.1",PORT);server.bind(isa);//设置ServerSocketChannel为非阻塞模式server.configureBlocking(false);//将ServerSocketChannel注册到Selectorserver.register(selector, SelectionKey.OP_ACCEPT);//select()方法是一个选择等待IO的Channel的阻塞方法,直到至少选中一个等待IO的Channel//返回的是选中Channel的数量,没有被选择器取消选中的Channel不可重新选中while(selector.select() > 0){//遍历选择器中选中的SelectionKey,每个Selection对应一个Channelfor(SelectionKey sk : selector.selectedKeys()){//将SelectionKey取消选中selector.selectedKeys().remove(sk);//如果sk对应的Channel有连接请求if(sk.isAcceptable()){//创建与客户端通讯用的SocketChannelSocketChannel sc = server.accept();sc.configureBlocking(false);//将SocketChannel注册到Selectorsc.register(selector, SelectionKey.OP_READ);//将sk对应的Channel设置成准备接受其它请求,如果有连接请求,该Channel就会被select()方法选中sk.interestOps(SelectionKey.OP_ACCEPT);}//如果sk对应的Channel有数据需要读取if(sk.isReadable()){SocketChannel sc = (SocketChannel)sk.channel();ByteBuffer buff = ByteBuffer.allocate(1024);String content = "";try{while(sc.read(buff) > 0){buff.flip();content += charset.decode(buff);}System.out.println("读取的数据:" + content);//将sk对应的Channel设置成准备接收数据,如果有数据传入,该Channel就会被select()方法选中sk.interestOps(SelectionKey.OP_READ);}catch(IOException e){sk.cancel();if(sk.channel() != null){sk.channel().close();}}if(content.length() > 0){//遍历选择器中的所有keyfor(SelectionKey key : selector.keys()){Channel targetChannel = key.channel();//如果key对应的Channel是SocketChannel的实例if(targetChannel instanceof SocketChannel){SocketChannel dest = (SocketChannel) targetChannel;dest.write(charset.encode(content));}}}}}}}public static void main(String[] args) throws IOException{new NServer().init();}}编程实例(客户端)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051public class NClient {private Selector selector = null;static final int PORT = 12000;private Charset charset = Charset.forName("UTF-8");private SocketChannel sc = null;public void init() throws IOException{selector = Selector.open();InetSocketAddress isa = new InetSocketAddress("127.0.0.1",PORT);sc = SocketChannel.open(isa);sc.configureBlocking(false);sc.register(selector, SelectionKey.OP_READ);new Thread(new MyClientThread()).start();//检测键盘输入Scanner scan = new Scanner(System.in);while(scan.hasNext()){String line = scan.nextLine();sc.write(charset.encode(line));}}//每个客户端启动一个该线程,用于显示服务器发回来的消息private class MyClientThread implements Runnable{@Overridepublic void run() {try{while(selector.select() > 0){for(SelectionKey sk : selector.selectedKeys()){selector.selectedKeys().remove(sk);if(sk.isReadable()){SocketChannel sc = (SocketChannel) sk.channel();ByteBuffer buff = ByteBuffer.allocate(1024);String content = "";while(sc.read(buff) > 0){buff.flip();content += charset.decode(buff);}System.out.println("聊天信息:" + content);sk.interestOps(SelectionKey.OP_READ);}}}}catch(IOException e){e.printStackTrace();}}}public static void main(String[] args) throws IOException{new NClient().init();}}
UDP
- UDP是无连接的,只负责发送带有目标地址的数据包(DategramPacket),数据是否被正确接收,发送端并不知道。
服务器端实例
12345678910111213141516171819202122232425262728293031323334353637public class UdpServer {public static final int PORT = 30001;private static final int DATA_LEN = 4096;byte[] inBuff = new byte[DATA_LEN];//创建DatagramPacket,传入的byte[]的长度决定了该DatagramPacket能放入多少数据private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);private DatagramPacket outPacket;String[] data = new String[] { "aaa", "bbb", "ccc" };public void init(){try( //创建DatagramSocket,绑定端口DatagramSocket socket = new DatagramSocket(PORT);){for(int i = 0; i < 1000 ; i++){//阻塞方法,接收传入指定端口的DatagramPacketsocket.receive(inPacket);//inPacket中的数据和创造inPacket时传入的byte[]中的数据是一样的,说明inPacket的数据是存在byte[]数组中的//下面输出trueSystem.out.println(inBuff == inPacket.getData());//输出inPacket中的内容System.out.println(new String(inBuff,0,inPacket.getLength()));byte[] sendData = data[i%3].getBytes();//创建要发送的DatagramPacket,这时候除了要发送数据的byte[]数组,还要传入目标IP和端口//getSocketAddress()返回的是new InetSocketAddress(getAddress(), getPort())outPacket = new DatagramPacket(sendData , sendData.length,inPacket.getSocketAddress());//发送数据包socket.send(outPacket);}}catch(IOException e){e.printStackTrace();}}public static void main(String[] args){new UdpServer().init();}}客户端实例
1234567891011121314151617181920212223242526272829public class UdpClient {public static final int PORT = 30001;public static final String DEST_IP = "127.0.0.1";private static final int DATA_LEN = 4096;byte[] inBuff = new byte[DATA_LEN];private DatagramPacket inPacket = new DatagramPacket(inBuff , inBuff.length);private DatagramPacket outPacket = null;public void init() throws IOException{try(DatagramSocket socket = new DatagramSocket()){//使用一个空数组构造要发送的数据包,同时也要传入接受端的IP和端口outPacket = new DatagramPacket(new byte[0] , 0 ,InetAddress.getByName(DEST_IP),PORT);Scanner scan = new Scanner(System.in);while(scan.hasNext()){byte[] buff = scan.nextLine().getBytes();//重新设置要发送的数据包中的数据outPacket.setData(buff);socket.send(outPacket);//阻塞方法socket.receive(inPacket);System.out.println(new String(inBuff,0,inPacket.getLength()));}}}public static void main(String[] args) throws IOException{new UdpClient().init();}}