java程序员从笨鸟到菜鸟之(四十四)Socket编程一

Socket编程

1  计算机网络通信的问题----(网络编程中的两个问题)?

1)一个是如何准确的定位(识别)网络上一台或多台主机;

2)另一个就是找到主机后如何可靠高效的进行数据传输
在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠(TCP)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即:通信双方一方作为服务器等待客户提出请求并予以响应;客户则在需要服务时向服务器提出申请;服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。

2  网络编程的三要素是什么?
(1)找到IP地址---发到哪--地址
(2)必须有端口---谁接受--具体的人
(3)必须有协议(TCP协议、UDP协议)---怎么发,双方都遵守,都能看懂

3 计算机网络中的两个概念:端口和IP地址

IP地址引入整个网络中有多台电脑,那么如何区分这些电脑呢?

概念:每台计算的标识(身份证---独一无二的);由IP地址可以唯一地确定Internet上的一台主机,互相通信时不会发生混淆

全名:Internet Protocol----互联网协议;构成:四个无符号的字节组成的一串数字(字节之间以逗号隔开),形如:192.168.1.108;

IP如果每次写成二进制数据,非常麻烦;所以采用点分十进制法,将每一个二进制数据转成十进制数据,中间用.隔开

回环地址:127.0.0.1----代表这代计算机本身

常用的计算机命令:ipconfig---查看ip;ping ip+地址---查看当前本机与这台PC机器的网络通信原理和声呐系统是一样的

InetAdress类

Java中与Socket相关的类都在java.net这个包中,InetAdress就是代表IP地址的类

问题:InetAddress类构造方法私有化,那么是如何创建对象的呢?

特点:该类中的某些静态成员方法的返回值是该类的本身,从而创建对象

扩展如果一个类中没有构造方法

  A:这个类里面的成员方法都是静态(Math,Arrays,Collections)

  B:单例模式:在内存始终只有一个对象

  (1)构造方法私有化;(2)在成员变量中创建该类的实例(并且这个实例私有化被static修身);(3)提供该类公共方法可以通过外界调用

  C:该类中会某些 静态成员方法的返回值是该类本身(InetAddress就是一个例子)

二、InetAdress类的常用的成员方法

(1)--public static InetAddress getByName(String host)throws UnknownHostException

功能:创建当前主机的IP地址的对象

参数:计算机名或IP地址的文本(字符串)形式(如果是计算机名会通过DNS解析成计算机的IP地址,然后返回一个IP对象)

返回值:通过这个方法返回的是IP地址对象

编译时期的异常:UnknownHostException---主机名字或IP错误,无法通过DNS解析

总结:该主机名可以是计算机名(PC机器的计算机名称),也可以是IP地址的文本表现形式,通过这个方法返回的是IP地址对象
方式(1):InetAddress address = InetAddress.getByName("USER-20171205ZR") ;---主机得到对应的IP对象
方式(2):InetAddress address = InetAddress.getByName("192.168.10.101") ; ---常采用的方式

链接:创建IP对象的几种比较,点击打开链接点击打开链接

(2)--如何通过得到的地址对象得到前计算机名称?

public String getHostName():获取此 IP地址的主机名。 

说明:如果此 InetAddress是用主机名创建的,则记忆并返回主机名

(3)--获取具体的IP地址文本形式

public String getHostAddress()返回 IP地址字符串---以文本表现形式

关键字:IP对象、计算机名称、IP地址的字符串

实例1

package org.westos_01;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo {
	
	public static void main(String[] args) throws UnknownHostException {
		
		InetAddress address = InetAddress.getByName("192.168.10.101") ;//很重要,也可以是域名
		
		System.out.println(address);//返回主机名或IP地址(根据输入的情况而定)
		
		String name = address.getHostName() ;
		System.out.println("name:"+name);//返回对应的IP地址的主机名

	
		String ip = address.getHostAddress() ;返回IP地址字符串(以文本表现形式)
		System.out.println("ip:"+ip);
 	}
}

端口的引入由上面我们知道,有了IP地址,就可以区分网络中的计算机了,但是我们是不是就可以通信了,No!!!

举例说明:电脑A与电脑B通信,电脑C能否同时与电脑A通信;那么如何区分这二者的通信呢?这就需要端口了

端口号:Port Number,每台电脑都有65536个端口,端口号的编号范围:0-65535(有效端口);0-1024是系统保留端口;端口可以让一台电脑同时传输多路数据;端口号是通信的窗口

端口号的查询:通过360查看每个软件的对应应用程序的端口号

理解:计算机的通信,通过IP地址识别计算机,然后通过端口号对进程(应用程序)的数据进行传输

端口的作用:网络的作用是传输数据,而传输数据需要通道,这个通道就叫做连接的东西,换而言之-把连接的建立叫做数据传输的通道建立;连接--就是两个端口进行连接

补充:一旦建立连接,形成数据传输的通道,数据流就可以通过此通道进行交互(谁主动--谁输出;谁被动--谁输入----通道输入输出流)

疑问:每个逻辑端口号代表着一个进程的数据传输或数据的流出端口吗?

基于Socket的java网络编程

Java Socket编程的核心:使用Java语言让数据在网络上传输;网络层的“IP地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程);个人对Socket编程理解:利用三元组(IP地址,协议,端口)标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互,利用这三者进行编程。

1:Socke编程网络上的两个程序(进程)通过建立一个双向的通讯连接(此二字可以省略)实现数据的交换(交互),这个双向链路的每一端称为一个Socket;

Socket编程通常用来实现客户端和服务器的连接;Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。但是Socket所支持的协议种类也不光TCP/IP一种,还有HTTP协议等。因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。

2:Socket通讯的过程Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。

3:Socket网络编程的图示:

计算机网络中TCP的三次握手和四次挥手理解---具体可以参考:点击打开链接点击打开链接点击打开链接


为什么要三次握手呢?其实就是理解双方满足什么条件数据才可以进行交互,目的是保证数据的正确传输。

对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:

  (1) 创建Socket---两端;
  (2) 打开连接到Socket的输入/出流(通道);
  (3) 按照一定的协议对Socket进行读/写操作;
  (4) 关闭Socket.

说明:在实际应用中,并未使用到显示的close,但是还是推荐使用;客户端的一个方法表示自己已经没有数据可以发送了,但是仍然可以接受数据。

创建Socket

Java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。

SeverSocket类

常用的构造方法:SeverSocket(int port )--创建绑定到特定端口的服务器套接字

说明:参数是一个端口号,SeverSocket类对象创建出来后,表示服务器端在本机指定的端口号监听

常用的成员方法

(1)public Socket accept() throws IOException --侦听并接受到此套接字的连接

说明:在服务器端在指定的端口号处监听(等待客户端建立连接,然后发来信息),建立连接很重要的一步!!!

注意:客户端连接过来之前,当前线程是出于阻塞状(挂起状态),也即客户端没有与服务器建立连接,该方法不会执行下面的代码

返回值:Socket类型的实例

(2)close :结束监听,释放资源

Socket类

API解释:客户端的套接字

Socket类可以看作是两个端口之间的连接,两台电脑建立通信使用的就是这个类

此类对象的创建的两种途径:(1)SeverSocket类的accept()方法返回值;(2)通过Socket类的构造方法

提示:所谓的服务器端和客户端是在Socket对象创建之前的说法,一旦创建Socket的实例,就不用关心哪个是服务器端哪个是客户端。这个时候通过Socket实例进行通信

构造方法1:public Socket(InetAddress address, int port)throws IOException

方法说明:创建一个流套接字并将其连接到指定IP地址(目的IP地址---远程IP地址---数据要传输到的计算机)的指定端口号

构造方法2:public Socket(String host, int port)---常用

参数说明:host可以是主机名称也可是IP地址的字符串表现形式

成员方法

1)获取通道内的输出流
public OutputStream getOutputStream() throws IOException

说明:返回此套接字的输出流,通向通道中传输(写入)数据(向远程计算机输入信息)

2)获取通道内的输入流
public InputStream getInputStream()

说明:返回此套接字的输入流

注意:这两个方法必须在建立连接后才能调用此方法;使用时常常对字节流进行封装;

3)那么既然涉及到流,必然伴随着读写数据---介绍有关readLine和write的方法

readLine()是BufferedReader类的方法:一次读取一行,返回的是字符的文本形式

注意事项

(1)readline()在达到buffer大小之前,只有遇到换行符("/r"、"/n"、"/r/n")的时候才会结束;换句话说如果在读取一行的过程中,遇到换行符默认读取一行结束(很坑爹)

(2)readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回null----重点

以前错误理解:没有读取到数据时就返回null;

现在理解:在读取文件的时候,文件结尾就是流的结尾,但对于Socket而言不是的,不能认为流中数据读完了就是流的结尾了。Socket流还在,还是能够继续读写的。所以用Socket的输入流封装的BufferedReader调用readLine方法,是不会返回null的,也就发生阻塞了。

那么何时是null呢

:readLine()只有在数据流发生异常或者另一端被close()掉时或者对应的流结束,才会返回null值

(3)使用Socket之类的数据流时,要避免使用readLine(),以免为了等待一个换行/回车符而一直阻塞(不向下执行)

(4)直到程序遇到了换行符或者是对应流的结束符readLine()方法才会认为到了一行;

问题:readLine()→newLine()换行(光标指向下一行要读取的数据)----flush刷新(数据从缓冲区真正的写入)

理解:三秦套餐(凉皮,冰峰,肉夹馍)

write(String str):一次写入一行

注意事项:

(1)写入的时候不会把换行符("/r"、"/n"、"/r/n")写入

(2)要手动newLine()方法换行

(3)写入换行符之后一定记得如果输出流不是马上关闭的情况下记得flush刷新一下,确保数据从缓冲区写入到指定的文件中(可能没有达到缓冲区字节数组发送的要求)----以免加载图片或视频没有加载完,数据还在缓冲区中

总结一下:(1)没有换行符情况(2)有换行符的情况(3)异常或流关闭的情况

开发中遇到的问题:从客户端的某个文件中读取数据,在服务器端中显示读取的内容,发现在客户端发送数据完毕后,服务器端仍然在运行

原因:客户端从某个文件通过readLine()读取数据遇到换行符结束,在客户端通过通道输出流写入数据,此时不会把换行符写入,即使客户端数据读取完了,但在但服务器端由于没有遇到换行符会一直处于阻塞状态,等待读取数据。

readLine和read问题的相关链接:点击打开链接点击打开链接点击打开链接点击打开链接点击打开链接

通知服务器数据读取完了,常用的两种策略

(1)客户端端定义一个结束标志位---标志位的内容未写进去,一旦读到此标志位,break跳出方法;有缺陷(文件上传时本身有此内容)

(2)客户端的方法---shutdownOutput()方法---这个方法告诉服务器端,客户端这边没数据了,读完了,你可以反馈。

实例2  想说明:当客户端发送完数据后,服务器端和客户端会关闭吗?

客户端

package tcp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * @author Orange
 * @version 1.8
 */
public class Client {

	public static void main(String[] args) throws Exception{
		//(1)创建Socket对象
		Socket socket = new Socket("127.0.0.1", 6000);
		//(2)通过输出流,向通道写入数据,首先读取文件
		String file="InetAddressDemo.java";
		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
		//(3)通道输出流--封装
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		String str=null;
		while((str=br.readLine())!=null){
			bw.write(str);
			bw.newLine();
			bw.flush();
		}
		br.close();
		bw.close();
		socket.close();
		
	}
}

服务器端

package tcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Orange
 * @version 1.8
 */
public class Server {

	public static void main(String[] args) throws IOException {
		ServerSocket s = new ServerSocket(6000);//在此端口监听是否有数据
		System.out.println("服务器端请求建立连接。。。");
		Socket ac = s.accept();//监听客户端是否发来数据
		System.out.println("客户端和服务器端建立连接。。。");
		BufferedReader br = new BufferedReader(new InputStreamReader(ac.getInputStream()));
		String st=null;
		while((st=br.readLine())!=null){
			System.out.println(st);
		}
		br.close();
		s.close();
	}
}

分析:不会关闭,因为Socket流没有关闭,通道没有关闭,还可以向通道中写入和读取数据,会处于等待状态(死循环)

需求:System.in键盘录入数据,通过连接进行数据交互,在服务器端存储数据信息到文件中(应用:保存聊天记录)

注意:I/O流的System.in也是个阻塞方法,没有关闭流的情况下可以一直写入

对问题的解答:由于开启了两个进程,客户端发送完数据后,由于连接还在,服务器端还在readLine()等待读取数据,由于是两个进程,服务器端没有反馈,所以客户端发送完数据后,直接关闭了客户端socket,导致连接中断,服务器端也关闭。

实例3 想说明:通信双方都阻塞

Client

package feedback2;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * @author Orange
 * @version 1.8
 */
public class Client {

	public static void main(String[] args) throws IOException {
		// (1)创建Socket对象
		Socket socket = new Socket("127.0.0.1", 8888);
		// (2)向通道中写入数据----包装
		BufferedReader bs = new BufferedReader(new FileReader("c.txt"));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		// 一次读取一行,并向通道内写入一行数据
		String str = null;
		while ((str = bs.readLine()) != null) {
			bw.write(str);
			bw.flush();
			bw.newLine();
		}
		//看是否有阻塞---
		BufferedReader bsff = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		String st=null;
		while((st=bsff.readLine())!=null){
			//内容自己写,主要说明阻塞
		}
		bw.close();
		socket.close();
	}
}

Server

package feedback2;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Orange
 * @version 1.8
 */
public class Server {

	public static void main(String[] args) throws IOException {
		//(1)创建Socket对象
		ServerSocket s = new ServerSocket(8888);
		//(2)在此端口监听是否有数据,是否建立连接---创建了Socket对象
		Socket so = s.accept();
		//(3)一旦建立连接---接受数据,放到指定的文件中

		BufferedWriter bw = new BufferedWriter(new FileWriter("d.txt"));
		BufferedReader br = new BufferedReader(new InputStreamReader(so.getInputStream()));
		String str=null;
		while((str=br.readLine())!=null){
			bw.write(str);
			bw.flush();
			bw.newLine();
		}
		System.out.println("服务器发送完文件了。。。");//一直未读到此文件
		/*//br.close()---通道输入流不能关闭
		//(4)一旦客户端执行了shutdown()方法告诉服务器端没有要发送的数据了,服务器端不用readLine(),即跳出阻塞状态,执行下面的代码
		
		//(5)服务器端既然要发送首先要向通道写入数据
		//写入数据
		
		OutputStream os = so.getOutputStream();
		os.write("我爱你".getBytes());
		//理论上bur的流对象不关闭,对应的底层流不关闭应该会一直等待
		os.close();
		br.close();
		bw.close();
		//不键盘录入了,直接写入数据
*/		s.close();
	}
}

说明:阻塞的原因不用重述了----双方都处于等待阶段

实例4  通信双方建立连接

Client

package feedback;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * @author Orange
 * @version 1.8
 */
public class Client {

	public static void main(String[] args) throws  IOException {
		//(1)创建Socket对象
		Socket socket = new Socket("127.0.0.1", 8888);
		//(2)向通道中写入数据----包装
		BufferedReader bs = new BufferedReader(new FileReader("c.txt"));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		//一次读取一行,并向通道内写入一行数据
		String str=null;
		while((str=bs.readLine())!=null){
			bw.write(str);
			bw.flush();
			bw.newLine();
		}
		//bw.close();----通道输出流不能关闭
		socket.shutdownOutput();//客户端需要给服务器提供一个终止,告诉服务器端我没有数据了,防止通信双方的readLine()相互阻塞----关闭本次连接
		//当时错误的地方----写成socket.shutdownInput
		/*你也可以调用shutdown()函数来关闭该socket。
		 *该函数允许你只停止在"某个方向上"的数据传输,而一个方向上的数据传输继 续进行。
		 *如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。*/
		System.out.println("我发送完数据了,你可以发送数据了。。。");
		//由于Socket对象一旦创建完毕,就不管那个是客户端哪个是服务器端,都可以发送数据
		//(3)客户端在通道中等待服务器端的反馈
		BufferedReader b = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		BufferedWriter bwf = new BufferedWriter(new FileWriter("copy.java"));
		String s=null;
		while((s=b.readLine())!=null){
			bwf.write(s);
			bwf.flush();
			bwf.newLine();
		}
		//socket.shutdownOutput();---要不要都可以
		//(4)客户端反馈完了,关闭客户端的Socket
		bs.close();
		bw.close();
		bwf.close();
		socket.close();
	}
}

Server

package feedback;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Orange
 * @version 1.8
 */
public class Server {

	public static void main(String[] args) throws IOException {
		//(1)创建Socket对象
		ServerSocket s = new ServerSocket(8888);
		//(2)在此端口监听是否有数据,是否建立连接---创建了Socket对象
		Socket so = s.accept();
		//(3)一旦建立连接---接受数据,放到指定的文件中
		
		BufferedWriter bw = new BufferedWriter(new FileWriter("d.txt"));
		BufferedReader br = new BufferedReader(new InputStreamReader(so.getInputStream()));
		String str=null;
		while((str=br.readLine())!=null){
			bw.write(str);
			bw.flush();
			bw.newLine();
		}
		System.out.println("客户端发送完文件了。。。");
		
		//(4)一旦客户端执行了shutdown()方法告诉服务器端没有要发送的数据了,服务器端不用readLine(),即跳出阻塞状态,执行下面的代码
		
		//(5)服务器端既然要发送首先要向通道写入数据
		BufferedReader bur = new BufferedReader(new FileReader("InetAddressDemo.java"));
		//写入数据
		BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(so.getOutputStream()));
		String strr=null;
		//注意一会用read()方法试试
		System.out.println("服务器向客户端开始发送数据。。。");
		while((strr=bur.readLine())!=null){
			bwr.write(strr);
			bwr.flush();
			bwr.newLine();
		}
		System.out.println("服务器发送数据完毕。。。");
		bw.close();
		bur.close();
		bwr.close();
		//不键盘录入了,直接写入数据
		s.close();
	}
}

注意:对于TCP协议来说,客户端和服务器端是需要建立连接通道的,如果没有启动服务器端,先启动客户端报异常:java.net.ConnectException;Connection refused:connect ---连接被拒绝!

掌握:shutdown和close的区别

close():当所有的数据操作结束以后,你可以调用close()函数来释放该Socket,从而停止在该socket上的任何数据操作;当涉及多个进程时,关闭本进程的socket id,但连接还是开着的,用这个socket id的其它进程还能用这个连接

shutdown():该函数允许你只停止某个方向上的数据传输,而一个方向上的数据传输继续进行;可以关闭某Socket的写入操

作而允许继续在Socket上接受数据,直至读入所有数据。

参考:点击打开链接点击打开链接点击打开链接(下面的唯一评论!!!)

发现问题:Connect reset的报错

点击打开链接1:问题---Connect reset的异常,老是提醒错误的原因是 in.readLine()

原因:是服务器端发送完后立马就关闭了连接,而客户端还在读数据而导致了connection reset异常。解决方法:在服务器端发

送数据后Sleep段时间,比如2秒,以等待客户端读完数据发送了关闭请求后才关闭连接。

点击打开链接2:总结的很详细

点击打开链接3:针对此报错的处理

点击打开链接4:大神级别的分析和解决

点击打开链接5:百度了解一下就OK了

到这里关于TCP编程暂时先结束了,提醒自己买一本TCP/IP协议详解

-----------------------------------------------------------------------------分割线

UDP编程

UDP全名是User Data Protocol:用户数据报协议,不需要建立连接进行通信

理解:发送电报---对于一段需要发送的数据,每次都会生成一个"数据包"实例,数据报包含目的IP和端口以及承载的信息(要发送

的数据);

UDP协议中的两端称之为发送端和接收端

DatagramPacket类

特点:不但用于发送数据,还可以接受数据,数据包类";理解为一个暂时存储数据等信息的容器

发送数据常用方法:

(1)setAddress(InetAdress ip):设置数据的"接收端"的IP地址

(2)setPort(int port):设置数据"接收端"的端口

(3)setData(byte[] data,int offset,int length):设置要发送的数据:从指定位置读取指定大小的字节长度

(4)构造方法:public DatagramPacket(byte[] buf, int length ,InetAddress address,int port)说明:发送端绑定了了数据要发

送到的IP地址的主机的端口号(接受端就在此逻辑端口监听)

接受数据常用方法:

(1)构造方法DatagramPacket(byte[] data,int length)

说明:创建数据报包DatagramPacket类的对象,来接收发送端发送来的数据data:表示用来放置接受到的数据(通常自己指定:1024大小);length:表示最大可以使用的字节长度(data数组可能不能够全部用来放置本次接收到的数据)----先有一个预判

(2)byte[] getData---得到接受到的数据(以字节数组的形式)

(3)int getLength----得到数据的长度

(4)int getPort------得到数据发送端的端口

(5)InetAdress getAdress---得到数据发送端的IP地址(出现0.0.0.0的情况)

 DatagramSocket类

特点:是"具体"发送和"接受"数据的,发送或接受数据包报(DatagramPacket对象)主要方法:(1)构造方法:DatagramSocket(int port)-----接收端在此端口监听说明:创建一个实例,让它在指定的port端口监听常使用此种:发送数据的程序就可以向确定的端口发送数据(2)构造方法:DatagramSocket()-----发送端随机选择端口发送数据,所以不指定端口说明:创建一个DatagramSocket的实例,让系统自动分派监听的端口(3)send(DatagramPacket dp)----发送数据包(4)receive(DatagramPacket dp)-接受数据包--阻塞式方法(等待发送端发送数据,只要没有数据,就一直等待,有数据的话将数据显示控制)两端接受数据的简单总结:发送端有一个端口,数据从此端口流出----到接收端的端口----从此接收端接受数据

实例5  发送一句话接收端接受此句话

Send

package demo;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * @author Orange
 * @version 1.8
 */
public class SendDemo {

	public static void main(String[] args) throws IOException {
		//(1)创建发送端的Socket对象(类似)---发送端的端口系统选择
		DatagramSocket ds = new DatagramSocket();
		//(2)创建数据包对象----这里来指定指定
		byte []buf="我爱你".getBytes();//要发送的数据
		InetAddress address = InetAddress.getByName("127.0.0.1");
		DatagramPacket dp = new DatagramPacket(buf, buf.length, address, 10086);
		//(3)数据包报封装好后,开始发送
		ds.send(dp);
		ds.close();
	}
}

Receive

package demo;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * @author Orange
 * @version 1.8
 */
public class ReceiveDemo {

	public static void main(String[] args) throws IOException {
		//(1)创建Socket对象----在指定的端口监听
		DatagramSocket ds = new DatagramSocket(10086);
		//(2)创建数据包报用于接受数据
		byte []buf=new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf, buf.length);
		ds.receive(dp);//接收数据--所有的信息都在此数据报包中(容器中)
		//(3)显示数据信息
		System.out.println("得到数据发送端的端口:"+dp.getPort());
		System.out.println("得到数据的长度:"+dp.getLength());
		System.out.println("得到发送端的IP地址:"+dp.getAddress().getHostAddress());
		System.out.println("得到发送的数据:"+new String (dp.getData(),0,dp.getLength()));
	}
}

注意:先运行接收端监听,在运行发送端发送数据;多次运行会发现发送的端口号是随机的

实例6  

需求:发送端的数据来源不是简单的一条语句而是不停的键盘录入
   键盘录入的方式:
   1)Scanner类
   2)BufferedReader类(字符缓冲流)特有功能一次读取一行

Send

package demo2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * @author Orange
 * @version 1.8
 */
public class SendDemo {

	public static void main(String[] args) throws IOException {
		DatagramSocket ds = new DatagramSocket();
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		//源源不断的通过键盘录入数据
		String str=null;
		while((str=br.readLine())!=null){
			//首先创建数据包报
			if("886".equals(str)){
				break;
			}
			byte[] buf = str.getBytes();
			DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 10086);
			ds.send(dp);
		}
		//br.close();
		ds.close();
		
	}
}

Receive

package demo2;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * @author Orange
 * @version 1.8
 */
public class ReceiveDemo {

	public static void main(String[] args) throws IOException {
		DatagramSocket ds = new DatagramSocket(10086);
		byte [] buf=new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf, buf.length);
		//ds.receive(dp);
		//读取数据---接收端不停的接收键盘录入的数据显示到控制台上
		while(true){
			ds.receive(dp);
			System.out.println(new String(dp.getData(),0,dp.getData().length));
		}
		
	}
}

说明:发送端中断标志位886,接收端用一个while(true)循环始终处于接受数据的状态

注意:由于没有建立连接,一方中断,另一方不会中断

实例7 多线程
需求:接收端和发送端是在两个窗口中显示的,如何让这两个接收端和发送端处于一个窗口下(main中),(使用多线程第二种方式:Runable接口的方式实现 发送端和接收端处于一个主线程中)

Send

package org.westos_05;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class SendThread implements Runnable {
	//定义一个变量
	private DatagramSocket ds ;
	public SendThread(DatagramSocket ds){
		this.ds = ds ;
	}
	
	@Override
	public void run() {
		try {
			//创建发送端Socket对象
			//以Io流的读取键盘录入的数据
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in)) ;
			String line = null ;
			while((line=br.readLine())!=null){
				//结束条件
				if("over".equals(line)){
					break ;
				}
				创建数据报包
				byte[] bys = line.getBytes() ;
				DatagramPacket dp = new DatagramPacket(bys, bys.length, 
						InetAddress.getByName("127.0.0.1"), 8888) ;
				
				//发送数据
				ds.send(dp) ;//阻塞方法
				
			}
			//释放资源
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			//释放资源
			if(ds!=null){
				ds.close() ;
			}
		}
		
	}

}

Receive

package org.westos_05;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ReceiveThread implements Runnable {
	
	private DatagramSocket ds ;
	public ReceiveThread(DatagramSocket ds){
		this.ds =ds ;
	}
	
	@Override
	public void run() {
		//创建接收端的socket对象		
		try {
			while(true){
				
				byte[] bys = new byte[1024] ;
				DatagramPacket dp = new DatagramPacket(bys, bys.length) ;

				//接收数据
				ds.receive(dp) ;//阻塞方法
				
				//获取ip字符串的表现形式
				String ip = dp.getAddress().getHostAddress() ;
				
				//解析数据接收容器,获取里面实际的内容
				byte[] buff = dp.getData();
				int len = dp.getLength() ;//获取实际数据缓冲区实际长度
				String s = new String(buff, 0, len) ;
				
				//显示到控制台
				System.out.println("from" + ip +"data is :"+s);
			}
			
			//接收要不停接收数据,不需要关闭
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

}

测试类

package org.westos_05;

import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * 需求:接收端和发送端是在两个窗口中显示的,如何让这两个接收端和发送端处于一个窗口下(main中)
 * 	(使用多线程第二种方式:Runable接口的方式实现 发送端和接收端处于一个主线程中)
 * @author Apple
 */
public class ChatRoom {

	public static void main(String[] args) {
		
		try {
			//发送端和接收端分别自定义类表示发送端线程和接收端线程
			//SendThread,receiveThread
			//需要有两个Socket对象
			
			//创建发送端和接收端的Socket对象
			DatagramSocket sendSocket = new DatagramSocket() ;
			DatagramSocket receiveSocket = new DatagramSocket(8888) ;
			
			//创建资源对象
			SendThread st = new SendThread(sendSocket) ;
			ReceiveThread rt = new ReceiveThread(receiveSocket) ;
			
			//创建线程类对象
			Thread t1 = new Thread(st) ;
			Thread t2 = new Thread(rt) ;
			
			//启动线程
			t1.start() ;
			t2.start() ;
			
			
		} catch (SocketException e) {
			e.printStackTrace();
		}
	}
}

注意:多次运行接收端会出现一处BindException---绑定异常;异常原因接收端运行一次就可以了,端口号已经被占用了,不能再继续使用这个端口号

补充:UDP的开发步骤

TCP与UDP这两种数据协议传输数据的区别

1:基于连接与无连接;----传输的可靠性
2:对系统资源的要求(TCP较多,UDP少);---可靠性的原因,必须做出牺牲
3:UDP程序结构较简单;
4:流模式与数据报模式 ;---重点
5:TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

6:TCP数据传输是连续的的,UDP是独立的

理解上:邮递员送信件(谁发都行,不需要建立固定连接)和打电话(双方在通信前已经明确了)

未完待续。。。


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值