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") ; ---常采用的方式
(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是独立的
理解上:邮递员送信件(谁发都行,不需要建立固定连接)和打电话(双方在通信前已经明确了)
未完待续。。。