网络编程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

原文地址:https://www.cnblogs.com/gqzdev/p/11667254.html