• 网络编程UDP、TCP详解


    网络编程

      网络编程主要用于解决计算机与计算机(手机、平板…)之间的数据传输问题。
    在这里插入图片描述
    在这里插入图片描述

    1.InetAddress(IP类)

      方法:

    方法 描述
    getLocalHost() 获取本机的IP地址对象
    getByName(“IP或者主机名”) 根据一个IP地址的字符串形式或者是一个主机名生成一个IP地址对象。 (用于获取别人的IP地址对象)
    getHostAddress() 返回一个IP地址的字符串表示形式。
    getHostName() 返回计算机的主机名。
    getAddress() 返回此InetAddress对象的原始 IP 地址

    2.端口号

      端口号是没有类描述的。

    端口号的范围: 0~65535
    从0到1023,系统紧密绑定于一些服务。
    1024~65535 我们可以使用

      端口常见报错 java.net.BindException: 端口被占用
      在cmd中查看端口号占用

    命令 描述
    netstat -ano 查看所有的端口
    netstat -ano |findstr “8080” 查看指定的端口
    tasklist|findstr “PID” 查看占用端口号的进程

    3.网络通讯协议

      每个网络程序都有自己所处理的特定格式数据,如果接收到的数据不符合指定的格式,那么就会被当成垃圾数据丢弃。(加密)

      在java网络通讯是通过Socket(插座)通讯技术实现的,要求通讯的两台器都必须要安装Socket。不同的协议就有不同的插座(Socket)

      1. 服务端开启并监听一个端口,时刻等待着客户端的连接请求
      2. 客户端知道服务端的ip地址和监听端口号,发出请求到服务端

      Socket类方法

    构造方法 描述
    Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号
    主要方法 描述
    getInetAddress() 返回套接字连接的地址
    public InputStream getInputStream() 返回此套接字的输入流
    public OutputStream getOutputStream() 返回此套接字的输出流
    方法 描述
    void bind​(SocketAddress bindpoint) 将套接字绑定到本地地址。
    void close​() 关闭此套接字。
    void connect​(SocketAddress endpoint) 将此套接字连接到服务器。
    void connect​(SocketAddress endpoint, int timeout) 将此套接字连接到具有指定超时值的服务器。
    SocketChannel getChannel​() 返回与此套接字相关联的唯一的SocketChannel对象(如果有)。
    InetAddress getInetAddress​() 返回套接字所连接的地址。
    InputStream getInputStream​() 返回此套接字的输入流。
    boolean getKeepAlive​() 测试是否启用了 SO_KEEPALIVE 。
    InetAddress getLocalAddress​() 获取套接字所绑定的本地地址。
    int getLocalPort​() 返回此套接字绑定到的本地端口号。
    SocketAddress getLocalSocketAddress​() 返回此套接字绑定到的端点的地址。
    boolean getOOBInline​() 测试是否启用了 SO_OOBINLINE 。
    T getOption​(SocketOption name) 返回套接字选项的值。
    OutputStream getOutputStream​() 返回此套接字的输出流。
    int getPort​() 返回此套接字连接到的远程端口号。
    int getReceiveBufferSize​() 获取此 Socket的 SO_RCVBUF选项的值,即该平台在此 Socket上输入的缓冲区大小。
    SocketAddress getRemoteSocketAddress​() 返回此套接字连接到的端点的地址,如果未连接,则 null 。
    boolean getReuseAddress​() 测试是否启用了 SO_REUSEADDR 。
    int getSendBufferSize​() 获取此 Socket的 SO_SNDBUF选项的值,即平台在此 Socket上输出使用的缓冲区大小。
    int getSoLinger​() 退货设置为 SO_LINGER 。
    int getSoTimeout​() 退货设置为SO_TIMEOUT 。 0返回意味着该选项被禁用(即无限超时)。
    boolean getTcpNoDelay​() 测试是否启用了 TCP_NODELAY 。
    int getTrafficClass​() 在从此Socket发送的数据包的IP头中获取流量类或服务类型
    boolean isBound​() 返回套接字的绑定状态。
    boolean isClosed​() 返回套接字的关闭状态。
    boolean isConnected​() 返回套接字的连接状态。
    boolean isInputShutdown​() 返回套接字连接的一半是否关闭。
    boolean isOutputShutdown​() 返回套接字连接的写半是否关闭。
    void sendUrgentData​(int data) 在套接字上发送一个字节的紧急数据。
    void setKeepAlive​(boolean on) 启用/禁用 SO_KEEPALIVE 。
    void setOOBInline​(boolean on) 启用/禁用 SO_OOBINLINE (接收TCP紧急数据)默认情况下,此选项被禁用,并且在套接字上接收的TCP紧急数据被静默地丢弃。
    Socket setOption​(SocketOption name, T value) 设置套接字选项的值。
    void setPerformancePreferences​(int connectionTime, int latency, int bandwidth) 设置此套接字的性能首选项。
    void setReceiveBufferSize​(int size) 设置 SO_RCVBUF选项,此规定值 Socket 。
    void setReuseAddress​(boolean on) 启用/禁用 SO_REUSEADDR套接字选项。
    void setSendBufferSize​(int size) 设置 SO_SNDBUF选项,此规定值 Socket 。
    static void setSocketImplFactory​(SocketImplFactory fac) 设置应用程序的客户端套接字实现工厂。
    void setSoLinger​(boolean on, int linger) 启用/禁用 SO_LINGER具有指定的逗留时间(以秒为单位)。
    void setSoTimeout​(int timeout) 启用/禁用 SO_TIMEOUT与指定的超时,以毫秒为单位。
    void setTcpNoDelay​(boolean on) 启用/禁用 TCP_NODELAY (禁用/启用Nagle的算法)。
    void setTrafficClass​(int tc) 在从此Socket发送的数据包的IP头中设置流量类或服务类型字节。
    void shutdownInput​() 将此套接字的输入流放置在“流的末尾”。
    void shutdownOutput​() 禁用此套接字的输出流。
    Set<SocketOption<?>> supportedOptions​() 返回此套接字支持的一组套接字选项。
    String toString​() 将此套接字转换为 String 。

    UPD通讯协议
      UDP通讯协议的特点:

    1.将数据极封装为数据包,面向无连接
    2.每个数据包大小限制在64K中,有大小限制
    3.因为无连接,所以不可靠
    4.因为不需要建立连接,所以速度快
    5.udp 通讯是不分服务端与客户端的,只分发送端与接收端。
    在udp协议中,有一个IP地址称作为广播地址,广播地址就是主机号为255地址。 给广播  IP地址发送消息的时候,在同一个网络段的机器都可以接收到信息。

      UDP协议下的Socket:

    方法 描述
    DatagramSocket(udp插座服务) 用来发送和接受数据包
    DatagramPacket(数据包类) 表示数据包

    DatagramPacket(buf, length, address, port)

    buf: 发送的数据内容
    length : 发送数据内容的大小
    address : 发送的目的IP地址对象
    port : 端口号

    发送端的使用步骤:client

    • 1.建立UDP的服务
      DatagramSocket client= new DatagramSocket();
      
    • 2.准备数据,把数据封装到数据包中发送
    	String data = "这个是要发送的数据";
    	//创建了一个数据包, 发送端的数据包要带上ip地址与端口号
    	//注意:传输的都是字节流,转换为字节数组
    	DatagramPacket packet = new DatagramPacket(data.getBytes(), 
    data.getBytes().length,InetAddress.getByName("IP或者主机名") , 端口号);
    
    • 3.发送数据包
       client.send(packet); //使用send发送数据包
      
    • 4.关闭资源 —实际上就是释放占用的端口号
      client.close();
      

    接收端的使用步骤:server

    • 1.建立UDP的服务 ,并且要监听一个端口。这个端口号需要与client中相同才能建立连接
      DatagramSocket  server= new DatagramSocket(要监听的端口号);
      
    • 2.准备空的数据包用于存放数据。
      byte[] buf = new byte[1024]; //接收的容器
      DatagramPacket packet= new DatagramPacket(buf,0, buf.length); // 1024
      
    • 3.接收数据包
      //receive是一个阻塞型的方法,没有接收到数据包之前会一直等待。数据是存储到了byte的字节数组中。
        server.receive(packet);
        System.out.println("接收端接收到的数据:"+ new String(buf,0,packet.getLength())); 
      // getLength() 获取数据包存储了几个字节。
      
    • 4.关闭资源
      socket.close();
      
    UPD通讯Demo
    UpdClient.class
     package com.gqz.udp;
    
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetSocketAddress;
    
    /**
    * 基本流程:发送端
    * 1、使用DatagramSocket  指定端口 创建接受端
    * 2、准备数据,一定要转成字节数组  
    * 3、封装成DatagramPacket包裹,需要指定目的地
    * 4、发送包裹send (DatagramPacket p)
    * 	byte[]	getData()
    * 		getLength()
    * 5、释放资源
    * @ClassName: UdpClient
    * @Description: TODO(这里用一句话描述这个类的作用)
    * @author ganquanzhong
    * @date 2019年7月12日 下午5:29:02
    */
    public class UdpClient {
       public static void main(String[] args) throws Exception {
       	System.out.println("client  发送方启动中......");
       	//  1、使用DatagramSocket  指定端口 创建接受端
       	DatagramSocket client = new DatagramSocket(8888);
       	//  2、准备数据,一定要转成字节数组  
       	String data = "模拟UPD发送数据,请求登录(username ,password)";
       	byte[] datas = data.getBytes();   //字符串转成字节数组
       	//  3、封装成DatagramPacket包裹,需要指定目的地本机
       	DatagramPacket packet = new DatagramPacket(datas, datas.length, new InetSocketAddress("localhost", 9999));
       	//  4、发送包裹send (DatagramPacket p)
       	client.send(packet);
       	//  	byte[]	getData()
       	//  		getLength()
       	//  5、释放资源
       	client.close();
       }
    }
    

    UpdServer.class

    package com.gqz.udp;
    
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    
    /**
     * 基本流程:接收端
     * 1、使用DatagramSocket  指定端口 创建接受端
     * 2、准备容器    封装成DatagramPacket 包裹
     * 3、阻塞式接收包裹  recevice  (DatagramPacket p)
     * 4、分析数据
     * 	byte[]	getData()
     * 		getLength()
     * 5、释放资源
     * 
    * @ClassName: UdpServer
    * @Description: TODO(这里用一句话描述这个类的作用)
    * @author ganquanzhong
    * @date 2019年7月12日 下午5:29:02
     */
    public class UdpServer {
    	public static void main(String[] args) throws Exception {
    		System.out.println("server接收  这里是服务端,接收数据启动中..........");
    		// 1、使用DatagramSocket  指定端口 创建接受端
    		DatagramSocket server = new DatagramSocket(9999);
    		// 2、准备容器    封装成DatagramPacket 包裹
    		byte[] container =new byte[1024*60];//最大60K
    		DatagramPacket packet = new DatagramPacket(container, 0,container.length);
    		// 3、阻塞式接收包裹  recevice  (DatagramPacket p)
    		server.receive(packet); //阻塞式
    		// 4、分析数据
    		// 	byte[]	getData()
    		// 		getLength()
    		byte[] datas = packet.getData();
    		System.out.println(packet.getLength());
    		System.out.println(packet.getOffset());
    		System.out.println(packet.getPort());
    		System.out.println(packet.getAddress());
    		System.out.println(packet.getSocketAddress());
    	
    		System.out.println(new String(datas));
    		// 5、释放资源
    		server.close();
    	}
    }
    
    

    TCP通讯协议
      TCP通讯协议特点:

    tcp是基于IO流进行数据的传输的,面向连接。(需要转换成字节数组)
    tcp进行数据传输的时候是没有大小限制的
    tcp是面向连接,通过三次握手的机制保证数据的完整性。可靠协议。
    tcp是面向连接的,所以速度慢。
    tcp是区分客户端与服务端的。

      tcp协议下的Socket:

    1. Socket(客户端类)    tcp的客户端一旦启动马上要与服务端进行连接。
    2. ServerSocket(服务端类) 使用在服务器端

    public class ServerSocket
      extends Object
        implements Closeable
       这个类实现了服务器套接字。 服务器套接字等待通过网络进入的请求。 它根据该请求执行一些操作,然后可能将结果返回给请求者。
      服务器套接字的实际工作由SocketImpl类的实例执行。 应用程序可以更改创建套接字实现的套接字工厂,以配置自己创建适合本地防火墙的套接字。

    ServerSocket服务端使用步骤:

    try{
      //1.建立Tcp的服务端,打开并且监听一个端口。
      ServerSocket server= new ServerSocket(监听的端口号); //ServerSocket服务端套接字
      //2. 等待接受客户端的连接产生一个Socket
      //accept()接受客户端的连接该方法是一个阻塞型的方法,没有客户端与其连接时会一直等待下去。
      Socket client  =  server.accept(); 
      //获取输入输出流对象
      InputStream is = client.getInputStream();       
      OutputStream os = client.getOutputStream();
      //3.1 获取输入流对象,读取客户端发送的内容。
      byte[] buf = new byte[1024];
      int length = 0;
      length = is.read(buf);
      System.out.println("服务端接收:"+ new String(buf,0,length));
      //3.2 获取socket输出流对象,向客户端发送数据
      os.write("客户端你好!".getBytes());
      //4.关闭资源
      is.close():
      os.close();
      client.close();
      server.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
    

    tcp的客户端使用步骤:

    try{
       //1.通过IP,端口 建立服务端连接
       Socket  client  = new Socket(InetAddress.getByName("IP或者主机名"),端口号);
       //2. 获取到Socket的输入、输出流对象
       InputStream is = client.getInputStream();
       OutputStream os = client.getOutputStream();
       //3.1 利用输出流对象,向服务器写出数据。
       os.write("服务端你好!".getBytes());
       //3.2 利用输入流对象,读取服务端回送的数据。
       byte[] buf = new byte[1024];
       int length = is.read(buf);
       System.out.println("客户端接收到的数据:"+ new String(buf,0,length));
       //4.关闭资源
       is.close();
       os.close();
       client.close();
    } catch (UnknownHostException e) {
       e.printStackTrace();
    } catch (IOException e) {
       e.printStackTrace();
    }
    

      tcp字符流注意细节:

    如果使用BuffrerdReader的readline方法一定要加上 才能把数据写出。
    使用字符流一定要调用flush方法数据才会写出。

    例:tcp字符流通信

    客户端
    	//获取socket的输出流对象。
    	OutputStreamWriter socketOut = new OutputStreamWriter(socket.getOutputStream());
    	//获取socket的输入流对象
    	BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    	//把数据写出给服务器
    	socketOut.write(发送给服务器的数据+"
    ");   //注意加上
    
    	socketOut.flush();
    	//读取服务端回送的数据
    	socketReader.readLine();
    服务端
    	//获取到Socket的输入流对象
    	BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    	//获取到Socket输出流对象
    	OutputStreamWriter socketOut =  new OutputStreamWriter(socket.getOutputStream());
    	//读取客户端的数据
    	socketReader.readLine()
    	//回送给客户端数据
    	socketOut.write(发给客户端的数据+"
    ");   //注意加上
    
    	socketOut.flush();
    
    TCP通讯Demo
    LoginMultiClient .class
    package com.gqz.tcp;
    
    import java.io.BufferedReader;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    /*
     * 单向连接
     * 创建客户端
     * 1、建立面向连接:使用Socket创建客户端 (服务器地址和端口) 
     * 3、操作:输入输出流操作
     * 4、释放资源
     */
    public class LoginMultiClient {
    	public static void main(String[] args) throws UnknownHostException, IOException{
    		System.out.println("=====client=====");
    		//使用Socket创建客户端 (服务器地址和端口) 
    		Socket client = new Socket("localhost",8888);
    	    //请求服务器 request
    	    new Request(client).send();	    
    	    //服务器响应
    	    new Response(client).recevie();
    		//3、释放资源
    		try {
    			client.close();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	/**
    	 * 
    	* @ClassName: Request
    	* @Description: 请求 
    	* @author ganquanzhong
    	* @date 2019年7月16日 上午10:56:12
    	 */
    	static class Request{
    		private Socket client;
    		private BufferedReader console;
    		private DataOutputStream dos;
    		private String msg;
    		public Request(Socket client) {
    			console = new BufferedReader(new InputStreamReader(System.in));
    			this.msg = init();
    			this.client = client;
    			try {
    				dos= new DataOutputStream(client.getOutputStream());
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		
    		private String init() {
    			try {
    				System.out.print("用户名:");
    				String username =console.readLine();
    				System.out.print("密码:");
    				String password = console.readLine();
    				return "username="+username+"&"+"password="+password;
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    			return "";
    		}
    		
    		private void send() {
    			try {
    				dos.writeUTF(msg);
    				dos.flush();
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	
    	/**
    	 * 
    	* @ClassName: Response
    	* @Description: 响应
    	* @author ganquanzhong
    	* @date 2019年7月16日 上午10:56:27
    	 */
    	static class Response{
    		private Socket client;
    		private DataInputStream dis;
    		
    		public Response(Socket client) {
    			this.client=client;
    			try {
    				dis = new DataInputStream(client.getInputStream());
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		
    		private void recevie() {
    			String result;
    			try {
    				result = dis.readUTF();
    				System.out.println(result);
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    

    LoginMultiServer.class

    package com.gqz.tcp;
    
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /*
     * 熟悉流程
     * 单向连接
     * 1、指定端口  使用ServerSocket创建服务器
     * 2、阻塞式等待连接accept
     * 3、操作:输入输出流操作
     * 4、释放资源
     */
    public class LoginMultiServer {
    	public static void main(String[] args) throws IOException {
    		boolean isRunning = true;
    		System.out.println("=====server=====");
    		//1、指定端口号 使用ServerSocket创建服务器
    		ServerSocket server = new ServerSocket(8888);
    		
    		//2、阻塞式等待连接accept
    		while(isRunning) {
    			Socket client = server.accept();//建立连接返回一个socket
    			System.out.println("一个客户端建立连接!");
    			new Thread(new Channel(client)).start();
    		}
    		server.close();
    		
    	}
    	
    	
    	//一个channel就是一个管道!
    	static class Channel implements Runnable{
    		private Socket client;
    		//输入流
    		private DataInputStream dis;
    		private DataOutputStream dos;
    		public Channel(Socket client) {
    			this.client=client;
    			try {
    				dis = new DataInputStream(client.getInputStream());
    				dos = new DataOutputStream(client.getOutputStream());
    			} catch (IOException e) {
    				e.printStackTrace();
    				release();
    			}
    		}
    		
    		//接收数据
    		private String recevie() {
    			String datas = "";
    			try {
    				datas = dis.readUTF();
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			return datas;
    		}
    		
    		//发送数据
    		private void send(String msg) {
    			try {
    				dos.writeUTF(msg);
    				dos.flush();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		
    		//释放资源
    		private void release() {
    			try {
    				if (null != dos) {
    					dos.close();
    				}
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    			try {
    				if (null != dis) {
    					dis.close();
    				}
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    			try {
    				if (null != client) {
    					client.close();
    				}
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		
    		@Override
    		public void run() {
    			//3、操作 :输入输出流操作
    			//分析数据
    			String username ="";
    			String password ="";
    			String[] dataArray = recevie().split("&");
    			for(String info:dataArray) {
    				String[] userInfo = info.split("=");
    				if (userInfo[0].equals("username")) {
    					username=userInfo[1];
    				}else {
    					password=userInfo[1];
    				}
    			}
    			//判断
    			if (username.equals("gqz") && password.equals("admin")) {
    				send("登录成功,欢迎进入ForFuture系统!!");
    			}else {
    				send("用户名或密码错误!!");
    			}
    			//4、释放资源
    			release();
    		}
    		
    	
    	}
    }
    
    
    
    基于TCP的简单聊天

    服务器端Chat.class

    package com.gqz.chat;
    
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    /**
     * 服务端,消息转发
     * 多个客户可以正常收发消息 
    * @ClassName: Chat
    * @Description: TODO(这里用一句话描述这个类的作用)
    * @author ganquanzhong
    * @date 2019年7月16日 下午3:01:34
     */
    public class Chat {
    	static private CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Chat.Channel>();
    	static int i=0;
    	
    	public static void main(String[] args) throws IOException {
    		System.out.println("=====Server=====");
    		//1、指定端口号 使用ServerSocket创建服务器   监听端口9999
    		ServerSocket server = new ServerSocket(9999);
    		
    		//2、阻塞式等待连接accept
    		while (true) {
    			Socket client = server.accept();
    			System.out.println("建立"+(++i)+"连接");
    			Channel c = new Channel(client);
    			all.add(c);//管理所有的成员
    			new Thread(c).start();
    		}
    	}
    	
    	
    	/**
    	 * 
    	* @ClassName: Channel
    	* @Description: 一个client代表一个channel
    	* @author ganquanzhong
    	* @date 2019年7月16日 下午5:49:02
    	 */
    	static class Channel implements Runnable{
    		private DataInputStream dis ;
    		private DataOutputStream dos ;
    		private Socket client;
    		private boolean isRunning;
    		private String name;
    		
    		
    		public Channel(Socket client) {
    			this.client=client;
    			try {
    				dis = new DataInputStream(client.getInputStream());
    				dos = new DataOutputStream(client.getOutputStream());
    				isRunning=true;
    				//获取名称
    				this.name=receive();
    				this.send("欢迎来到ForFuture群聊系统!");
    				sendOthers(this.name+"上线了", true);
    			} catch (IOException e) {
    				System.out.println("---初始化错误----");
    				release();
    				e.printStackTrace();
    			}
    		}
    		
    		//接收消息
    		private String receive() {
    			String msg="";
    			try {
    				msg= dis.readUTF();
    			} catch (IOException e) {
    				System.out.println("---接收消息错误----");
    				release();
    				e.printStackTrace();
    			}
    			return msg;
    		}
    		
    		//发送消息
    		private void send(String msg) {
    			try {
    				dos.writeUTF(msg);
    				dos.flush();
    			} catch (IOException e) {
    				System.out.println("---发送消息错误----");
    				release();
    				e.printStackTrace();
    			}
    		}
    		
    		
    		/**
    		 * 群聊:获取自己的消息,发送给别人
    		 * 私聊:约定数据格式 @:名称:msg
    		 * 
    		* @Title: sendOthers
    		* @Description: TODO(这里用一句话描述这个方法的作用)
    		* @author ganquanzhong
    		* @date  2019年7月17日 上午9:16:44
    		* @param msg
    		* @param isSys
    		 */
    		private void sendOthers(String msg,boolean isSys) {
    			boolean isPrivate = msg.startsWith("@");
    			if (isPrivate) {
    				//私聊
    				int idx1 = msg.indexOf(":");
    				int idx2 = msg.indexOf(":");
    				int idx = idx1 != -1?idx1:idx2;
    				String targetName = msg.substring(1, idx);
    				msg = msg.substring(idx+1);
    				for(Channel other :all) {
    					if (other.name.equals(targetName)) {
    						other.send("
    	"+new SimpleDateFormat("YYYY-MM-dd HH:MM:ss SSS").format(new Date()) +"
    "
    								+this.name+": "+msg);
    						break;
    					}
    				}
    			}else {
    				//群聊
    				for(Channel other:all) {
    					if (other == this) {//本身
    						continue;
    					}
    					if (!isSys) {
    						other.send("
    	"+new SimpleDateFormat("YYYY-MM-dd HH:MM:ss SSS").format(new Date()) +"
    "
    								+this.name+": "+msg);
    					}else {
    						other.send("
    	"+new SimpleDateFormat("YYYY-MM-dd HH:MM:ss SSS").format(new Date()) +"
    "
    								+"系统消息"+": "+msg);
    					}
    				}
    			}
    		}
    		
    		//释放资源
    		private void release () {
    			this.isRunning = false;
    			Utils.close(dis,dos,client);
    			//退出
    			all.remove(this);
    			sendOthers(this.name+"离开了!", true);
    		}
    		
    		@Override
    		public void run() {
    			while(isRunning) {
    				String msg = receive();
    				if (!msg.equals("")) {
    					//send(msg);
    					sendOthers(msg,false);
    				}
    			}
    		}
    	}
    }
    
    

    客服端LoginMultiServer.class

    package com.gqz.chat;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    /**
     * 在线聊天
     * 封装 实现多用户聊天
     * 
     * @ClassName: Client
     * @Description: TODO(这里用一句话描述这个类的作用)
     * @author ganquanzhong
     * @date 2019年7月16日 下午3:01:07
     */
    public class Client {
    	public static void main(String[] args) throws UnknownHostException, IOException {
    
    		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    		System.out.print("	进入群聊系统   输入您的用户名称:");
    		String name = br.readLine();
    		System.out.println("=====client="+name+"====");
    		// 1、建立面向连接:使用Socket创建客户端 (服务器地址和端口)
    		Socket client = new Socket("localhost", 9999);
    		
    		//客户端发送消息
    		new Thread(new Send(client,name)).start();
    		
    		//客户端接收消息
    		new Thread(new Receive(client)).start();
    	}
    }
    
    

    Send.class

    package com.gqz.chat;
    
    import java.io.BufferedReader;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.Socket;
    
    /**
     * 使用多线程封装:发送端
     * 1、发送消息
     * 2、从控制台获取消息
     * 3、释放资源
     * 4、重写run
     * 
    * @ClassName: Send
    * @Description: TODO(这里用一句话描述这个类的作用)
    * @author ganquanzhong
    * @date 2019年7月16日 下午5:09:16
     */
    public class Send implements Runnable {
    	private BufferedReader console;
    	private DataOutputStream dos;
    	private Socket client;
    	private boolean isRunning;
    	private String name;
    
    	public Send(Socket client,String name) {
    		this.client = client;
    		isRunning=true;
    		this.name= name;
    		console = new BufferedReader(new InputStreamReader(System.in));
    		try {
    			dos = new DataOutputStream(client.getOutputStream());
    			//发送名称
    			send(name);
    		} catch (IOException e) {
    			System.out.println("====初始化错误====");
    			this.release();
    			e.printStackTrace();
    		}
    	}
    
    	// 发送消息
    	private void send(String msg) {
    		try {
    			dos.writeUTF(msg);
    			dos.flush();
    		} catch (IOException e) {
    			System.out.println("--client-发送消息错误----");
    			release();
    			e.printStackTrace();
    		}
    	}
    
    	// 从控制台获取消息
    	private String getFromConsole() {
    		try {
    			return console.readLine();
    		} catch (IOException e) {
    			System.out.println("=====console error=====");
    			e.printStackTrace();
    		}
    		return "";
    	}
    
    	// 释放资源1
    	private void release() {
    		this.isRunning = false;
    		Utils.close(dos, client);
    	}
    
    	@Override
    	public void run() {
    		while (isRunning) {
    			String msg = getFromConsole();
    			if (!msg.equals("")) {
    				send(msg);
    			}
    		}
    	}
    }
    
    

    Receive.class

    package com.gqz.chat;
    
    import java.io.DataInputStream;
    import java.io.IOException;
    import java.net.Socket;
    
    /**
     * 使用多线程封装:接收端
     * 1、接收消息
     * 2、释放资源
     * 3、重写run
     * 
    * @ClassName: Receive
    * @Description: TODO(这里用一句话描述这个类的作用)
    * @author ganquanzhong
    * @date 2019年7月16日 下午5:08:34
     */
    public class Receive implements Runnable {
    	private DataInputStream dis;
    	private Socket client;
    	private boolean isRunning;
    
    	public Receive(Socket client) {
    		this.client = client;
    		isRunning=true;
    		try {
    			dis = new DataInputStream(client.getInputStream());
    		} catch (IOException e) {
    			System.out.println("====client 接收消息 初始化错误=====");
    			release();
    			e.printStackTrace();
    		}
    	}
    
    	// 接收消息
    	private String receive() {
    		String msg = "";
    		try {
    			msg = dis.readUTF();
    		} catch (IOException e) {
    			System.out.println("---client 接收消息错误----");
    			release();
    			e.printStackTrace();
    		}
    		return msg;
    	}
    
    	// 释放资源1
    	private void release() {
    		this.isRunning = false;
    		Utils.close(dis, client);
    	}
    
    	@Override
    	public void run() {
    		while(isRunning) {
    			String msg = receive();
    			if (! msg.equals("")) {
    				System.out.println(msg);
    			}
    		}
    	}
    }
    

    Utils.class

    package com.gqz.chat;
    
    import java.io.Closeable;
    
    /**
     * 工具类
     * 
    * @ClassName: Utils
    * @Description: TODO(这里用一句话描述这个类的作用)
    * @author ganquanzhong
    * @date 2019年7月16日 下午4:25:10
     */
    public class Utils {
    	/*
    	 * 释放资源
    	 */
    	
    	public static void close(Closeable... targets) {
    		for(Closeable target:targets) {
    			try {
    				if (null != target) {
    					target.close();
    				}
    			}catch(Exception e) {
    			}
    		}
    	}
    	
    }
    
    

    在这里插入图片描述

    项目下载:https://github.com/gqzGitHub/Net_study

  • 相关阅读:
    怎么制作html5网站页面让它适应电脑和手机的尺寸
    js面向对象 下
    认识面向对象及代码示例
    Math 对象
    js事件驱动函数
    模拟js中注册表单验证
    敏感词过滤 简单 模仿
    模仿随机验证码-简单效果
    字符串方法(函数)
    js中字符串概念
  • 原文地址:https://www.cnblogs.com/gqzdev/p/11667254.html
Copyright © 2020-2023  润新知