菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信)
UDP编程TCP编程(Socket通信)单个客户端的连接多个客户端的连接(聊天室案例)UDP编程
在上一篇中讲解了UDP协议是非面向连接的不安全但效率较高的通信协议。在了解完概念之后我们用Java来实现UDP编程。
Java中通过DatagramSocket和DatagramPacket来实现UDP通信,通信过程一般分为以下几步:
发送端(客户端):
创建客户端DatagramSocket类+端口准备数据打包DatagramPacket+地址及端口发送释放资源
接收端(服务端):创建服务端DatagramSocket类+端口准备接收容器接收数据包分析释放资源
依据这个过程我们直接通过代码来理解UDP发送过程:
首先构建服务端:
public class MyServer {public static void main(String[] args) throws IOException {//1、创建服务端 +端口DatagramSocket server = new DatagramSocket(8888);//2、准备接受容器byte[] container = new byte[1024];//3、封装成 包 DatagramPacket(byte[] buf, int length) DatagramPacket packet =new DatagramPacket(container, container.length) ;//4、接受数据server.receive(packet);//5、分析数据byte[] data =packet.getData();int len =packet.getLength();System.out.println(new String(data,0,len));//6、释放server.close();}}
这时服务端运行就会进入阻塞状态等待数据:
然后构建客户端
public class MyClient {public static void main(String[] args) throws IOException {//1、创建客户端 +端口DatagramSocket client = new DatagramSocket(6666);//2、准备数据String msg ="udp编程";byte[] data =msg.getBytes();//3、打包(发送的地点 及端口) DatagramPacket(byte[] buf, int length, InetAddress address, int port)DatagramPacket packet = new DatagramPacket(data,data.length,new InetSocketAddress("localhost",8888));//4、发送client.send(packet);//5、释放client.close();}}
运行客户端发送数据后服务端就会得到响应:
UDP的特点就是不管有没有服务器等待数据,客户端都可以发送数据,上例中不启动服务客户端运行依然不会报错,但数据会丢失。
TCP编程(Socket通信)
单个客户端的连接
TCP相对UDP而言,需要建立连接、安全可靠但效率较低。它在通信时两端必须连接才能进行通信。
何为Socket,比如用浏览器访问一个网站,点击访问后,浏览器会申请与服务器进行连接,连接后才能进行数据交互操作,在关闭网页后连接会自动断开。我们把客户端与服务端之间建立的连接就称之为Socket。
Java中Socket通信主要使用ServerSocket类来建立连接,通过Socket类来进行数据交互,我们直接通过代码来讲解,注意,交互过程采用的时数据处理流,如果大家忘了可以回去看看,链接:
服务端:
public class Server {public static void main(String[] args) throws IOException {//1、创建服务器 指定端口 ServerSocket(int port) ServerSocket server = new ServerSocket(8888);//2、接收客户端连接 阻塞式Socket socket =server.accept();System.out.println("一个客户端建立连接");//3、发送数据String msg ="欢迎使用";//输出流/*BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));bw.write(msg);bw.newLine();bw.flush();*///socket.getOutputStream()方法可以和之前的FileOutputStream()//对应起来它表示socket流,下面的write操作会在流中写入数据DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeUTF(msg);dos.flush();}}
与UDP协议不同,在连接之前必须先启动服务器,在建立连接后服务端也可以向客户端发送数据。
客户端:
public class Client {public static void main(String[] args) throws UnknownHostException, IOException {//1、创建客户端 必须指定服务器+端口 此时就在连接Socket client = new Socket("localhost",8888);//2、接收数据/*BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));String echo =br.readLine(); //阻塞式方法System.out.println(echo);*///这里采用之前讲的数据处理流进行交互DataInputStream dis = new DataInputStream(client.getInputStream());String echo = dis.readUTF();System.out.println(echo);}}
现在如果直接运行客户端,那么会报如下错误:
需要启动服务器才可以进行数据交互:
多个客户端的连接(聊天室案例)
与多用户交互最简单的方式就是将交互代码写道死循环中,所以服务器端可以改成如下代码:
public static void main(String[] args) throws IOException {//1、创建服务器 指定端口 ServerSocket(int port) ServerSocket server = new ServerSocket(8888);//2、接收客户端连接 阻塞式while(true){//死循环 一个accept()一个客户端Socket socket =server.accept();System.out.println("一个客户端建立连接");//3、发送数据String msg ="欢迎使用";//输出流DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeUTF(msg);dos.flush();}
但是这又带来了一个问题,客户端只能一个一个的连接,如果循环体中出现阻塞(比如死循环、等待用户输入等操作),那么后面的客户端就无法建立连接,这里就需要我们之前所学的多线程的知识来解决这个问题。为全面说明这个问题我们采用一个聊天室的案例来讲解。
聊天室案例
首先说明我们要实现的聊天室的功能,多个用户可以通过控制台进行交互,自己输入的内容在其他用户的控制台上会显示。
为实现这个功能,我们需要一个Server端,让其他Client端都与其建立连接,当用户发送消息时,服务器端会把消息转发给其他Client端。依照这个思路我们来完成聊天室。
为便于我们关闭线程,首先创建一个用于关闭线程的工具:
public class CloseUtil {//Closeable... 表示可以接受不定数量的Closeable类型的参数,它必须数函数接受参数的最后一个public static void closeAll(Closeable... io){for(Closeable temp:io){try {if (null != temp) {temp.close();}} catch (Exception e) {// TODO: handle exception}}}}
针对客户端,我们思考聊天室需要实时的接受和发送数据,这就需要多线程来实现,为此我们创建Send和Receive两个线程类:
Send类
public class Send implements Runnable{//控制台输入流private BufferedReader console;//管道输出流private DataOutputStream dos;//控制线程private boolean isRunning =true;//无参构造,初始化打印流public Send() {console =new BufferedReader(new InputStreamReader(System.in));}//有参构造传入与服务器建立的Socket连接public Send(Socket client){this();try {//初始化输出流dos =new DataOutputStream(client.getOutputStream());} catch (IOException e) {//e.printStackTrace();isRunning =false;CloseUtil.closeAll(dos,console);}}//1、从控制台接收数据private String getMsgFromConsole(){try {//接受控制台输入return console.readLine();} catch (IOException e) {//e.printStackTrace();}return "";}/*** 1、从控制台接收数据* 2、发送数据*/public void send(){//接收控制台输入String msg = getMsgFromConsole();try {if(null!=msg&& !msg.equals("")){//发送输入给服务器dos.writeUTF(msg);dos.flush(); //强制刷新}} catch (IOException e) {//e.printStackTrace();isRunning =false;CloseUtil.closeAll(dos,console);}}@Overridepublic void run() {//线程体while(isRunning){send();}}}
Receive类:
public class Receive implements Runnable {//输入流private DataInputStream dis ;//线程标识private boolean isRunning = true;public Receive() {}public Receive(Socket client){try {//初始化输入流dis = new DataInputStream(client.getInputStream());} catch (IOException e) {e.printStackTrace();isRunning =false;CloseUtil.closeAll(dis);}}//接收数据public String receive(){String msg ="";try {msg=dis.readUTF();} catch (IOException e) {e.printStackTrace();isRunning =false;CloseUtil.closeAll(dis);}return msg;}@Overridepublic void run() {//线程体while(isRunning){System.out.println(receive());}}}
在Client中我们启动这两个线程:
public class Client {public static void main(String[] args) throws UnknownHostException, IOException {Socket client = new Socket("localhost",9999);new Thread(new Send(client)).start(); //一条路径new Thread(new Receive(client)).start(); //一条路径}}
由于存在多个用户,所以建立连接过程(server.accept())和用户发送消息过程必须并行处理,所以还需要一个Channel线程来为各个Client端发送数据,为了方便访问Server类中的private属性,我们采用内部类的方式对Channel进行管理,并且还需要建立一个容器来存放已有用户。综上Server端的代码如下:
public class Server {private List<MyChannel> all = new ArrayList<MyChannel>();/*** @param args* @throws IOException */public static void main(String[] args) throws IOException {new Server().start();}public void start() throws IOException{ServerSocket server =new ServerSocket(9999);while(true){Socket client =server.accept();MyChannel channel = new MyChannel(client);all.add(channel);//统一管理new Thread(channel).start(); //一条道路}}/*** 一个客户端 一条道路* 1、输入流* 2、输出流* 3、接收数据* 4、发送数据* @author Administrator**/private class MyChannel implements Runnable{private DataInputStream dis ;private DataOutputStream dos ;private boolean isRunning =true;public MyChannel(Socket client ) {try {dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());} catch (IOException e) {//e.printStackTrace();CloseUtil.closeAll(dis,dos);isRunning =false;}}/*** 读取数据* @return*/private String receive(){String msg ="";try {msg=dis.readUTF();} catch (IOException e) {//e.printStackTrace();CloseUtil.closeAll(dis);isRunning =false;all.remove(this); //移除自身}return msg;}/*** 发送数据*/private void send(String msg){if(null==msg ||msg.equals("")){return ;}try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {//e.printStackTrace();CloseUtil.closeAll(dos);isRunning =false;all.remove(this); //移除自身}}/*** 发送给其他客户端*/private void sendOthers(){String msg = this.receive();//遍历容器for(MyChannel other:all){if(other ==this){continue;}//发送其他客户端other.send(msg);}}@Overridepublic void run() {while(isRunning){sendOthers();}}}}
这样就完成了聊天室的功能,我们创建两个客户Tom和Bob(可以有多个),代码测试结果如下:
以上就是Java网络的所有内容,这也是第一次展示综合案例,可能代码一多大家对逻辑理解起来会困难一些,建议大家多看几遍,结合聊天室也对之前的IO流、线程、容器好好的做做复习。相信大家要是能搞明白这段代码Java基础的内容差不多就没什么问题了。
上一篇:菜鸟学习笔记:Java提升篇9(网络1——网络基础、Java网络编程)
下一篇:菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)
如果觉得《菜鸟学习笔记:Java提升篇10(网络2——UDP编程 TCPSocket通信 聊天室案例)》对你有帮助,请点赞、收藏,并留下你的观点哦!