网络编程
一.目录
扫盲(网络编程基础知识)
网络通讯要素
java基本网络支持
socket套接字
TCP网络编程
UDP网络编程
URL&URLConnection
二.内容
扫盲(网络编程基础知识)
-
计算机网络通常是按照规模大小和延伸范围来分类的,常见的划分为:局域网(LAN)、城域网(MAN)、广域网(WAN)。Internet可以看成为世界上最大的广域网。
-
计算机网络中实现通信必须有一些约定,这些约定被称为通信协议。
-
国际化标准组织ISO将网络定性为模型,即 “开放系统互联参考模型”,即著名OSI7层参考模型。
开放系统互联参考模型力求将网络简化,并以模块化的方式来设计网络。
开放系统互联参考模型把计算网络分成物理层、数据链路层、网络层、传输层、会话层、表示层、应用层等七层
-
虽然开放系统互联参考模型,针对网络在不同层次的工作进行了非常详细的划分,而且这种划分也非常的清晰,但是在真正开发时却是非常的繁琐,所以在工业的实际应用中又对这七层进行了简化,出现了另外一种模型,即:TCP/IP参考模型。
网络通讯要素
-
IP地址
- IP地址用于唯一地标识网络中的一个通信实体(计算机)
每个被传输的数据包也要包括一个源IP地址和一个目的IP地址,当该数据包在网络中进行传输时,这两个地址要保持不变,以确保网络设备总能根据确定IP地址,将数据包从源通信实体送往指定的目的实体。
- IP地址用于唯一地标识网络中的一个通信实体(计算机)
-
端口号
-
端口是一个16位的整数,用于表示数据交给哪个通信程序处理.
通过网络防火墙对端口的设置,可以将想禁止上网的软件禁用掉。
-
-
传输协议
-
协议指的就是规则,传输协议就是传输的规则。就是定义以何种的规则进行数据的传递。在传输层中最重要的两种协议就是:UDP(User Datagram Protocol)和TCP(Transfer Control Protocol)。
-
UDP(对讲机、快递、广播)
-
将数据和目的封装成数据包,不需要建立连接。
2.每个数据包的大小限制在64K内
-
因无连接,是不可靠连接(缺点)
4.不需要建立连接,速度快(优点)
-
-
TCP
-
建立连接,形成传输数据的通道
2.在连接中进行大数据量传输
3.通过三次握手完成连接,是可靠连接(优点)
4.必须建立连接,效率比较低。(缺点)
-
-
java基本网络支持
使用InetAddress
- host<==>域名/ip
1.Java提供了InetAddress类来代表IP地址
2.InetAddress类没有提供构造方法,而是提供了两个静态方法来获取InetAddress的实例对象。
getByName(String host):根据主机获取对应的InetAddress对象。 //获取的是主机的InetAddress对象
getLocalHost()方法来获取本机IP地址对应的InetAddress实例 //获取的是本机的InetAddress对象
3.String getHostAddress():返回该InetAddress实例对应的IP地址字符串。// 返回的是字符串类型的!!!
String getHostName():获取此IP地址的主机名(域名)。 //返回的是字符串类型的!!!
4.InertAddress类还提供了
isReachable()方法,用于测试是否可以到达该地址.
getByName(String host):根据主机获取对应的InetAddress对象。
如果参数写域名==>返回值是 “域名/ip”,是InetAddress!!! 如果参数写ip==>返回值是ip,是InetAddress类型的!!!
getLocalHost()方法来获取本机IP地址对应的InetAddress实例
返回值是 “本地主机名/ip” 是InetAddress类型的!!!
public class InetAddressDemo {
public static void main(String[] args) throws Exception {
//getByName() 得到指定主机名(域名)的InetAddress对象
InetAddress ip = InetAddress.getByName("www.baidu.com");
//打印该对象返回值为:"域名/IP"
System.out.println(InetAddress.getByName("www.baidu.com"));
//该对象调用getHostName()得到的是该对象的指定主机名
System.out.println(InetAddress.getByName("www.baidu.com").getHostName());
//该对象调用getHostAddress()得到的是该对象的IP
System.out.println(InetAddress.getByName("www.baidu.com").getHostAddress());
//getLocalHost()得到本机名(域名)的InetAddress对象
InetAddress ip2 = InetAddress.getLocalHost();
//打印该对象返回值为:"域名/IP"
System.out.println(InetAddress.getLocalHost());
//该对象调用getHostName()得到的是本机域名
System.out.println(InetAddress.getLocalHost().getHostName());
//该对象调用getHostAddress()得到的是本机ip
System.out.println(InetAddress.getLocalHost().getHostAddress());
}
}
域名解析
- 在正常情况下,访问一台服务器主机的时候,就需要输入这台服务器主机的IP地址,如下所示:http://10.0.1.1,但是在实际情况中,我们并不是按照此种方式来进行访问的。而是通过http://www.xxx.com这种形式进行访问。
- 这是因为在访问之前进行了域名解析,世界上服务器如此之多,人们在进行网络访问时,不可能记住每台机器的IP地址,因此国际互联组织采用了另外一种方式来进行处理,即为每台机器定义一个名称。把每个名称与IP地址进行配置,记录在一台服务器中(DNS:Domain Name System,域名解析服务器),当访问某个主机时,就可以通过名称的方式来访问这台主机,这就是所谓的常见的情况
socket套接字
- 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。Socket就是为网络服务提供的一种机制。通讯的两端都有Socket,网络通讯实际就是Socket之间的通讯。数据在两个Socket之间通过IO流进行数据传输。
TCP网络编程
-
TCP称为传输控制协议,是一种可靠的数据传输控制协议
-
两台主机如果要进行TCP通信,就必须要建立连接,连接后两台主机就会形成一个虚拟的链接通道,数据就会在通道之间进行传输
-
客户端用的是Socket,服务器端用的是ServerSocket
-
Socket常用构造方法
Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字 Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 //这个ip和端口号都是服务器的
-
Socket常用方法
- 网络字节流(用于TCP)
用在TCP里面,网络字节流区别于本地字节流
网络字节流是用于在服务器和客户端传输数据的,本地字节流是用于在同一个电脑的硬盘和内存之间传输数据的 ,TCP客户端和服务器端交互,必须使用网络字节流
InputStream getInputStream() 返回此套接字的输入流。 OutputStream getOutputStream() 返回此套接字的输出流。 void shutdownInput() 此套接字的输入流置于“流的末尾”。 void shutdownOutput() 禁用此套接字的输出流。 void close() 关闭此套接字。
- 网络字节流(用于TCP)
-
ServerSocket常用构造方法
ServerSocket(int port) 创建绑定到特定端口的服务器套接字。
-
ServerSocket常用方法
服务器是没有IO流&服务器同时和多个客户端进行交互,但是服务器可以获取到来请求的客户端的对象(通过accept()方法),这样就可以使用每个客户端Socket中提供的IO流和客户端交互,并且还避免了把消息发错客户端的情况Socket accept() 侦听并接受到此套接字的连接 void close() 关闭此套接字。
-
TCP通信的步骤:
面向连接的通信,客户端和服务器端必须经过三次握手,建立逻辑连接,才能通信(安全)
TCP的三次握手并不在下面步骤里面,(服务器端的启动时间必须早于客户端,)当我们创建客户端对象socket的时候,socket就会去请求服务器,和服务器经过三次握手建立连接通路(也就是说,三次握手不是我们自己写的代码),如果服务器没有启动,就会抛异常!!- 服务器端先启动,然后和系统要一个指定的端口号(ServerSocket的构造方法)
- 客户端启动,通过服务器端的ip和端口号建立联系(Server的构造方法)
注:服务器端不会主动请求客户端,必须使用客户端去请求服务器端 - 客户端和服务器端建立起了逻辑连接,之后二者通过网络字节流进行通信
- 客户端使用本地字节输入流把文件从客户端硬盘读进客户端内存(需要在fis里写出文件路径来源)
- 客户端使用网络字节输出流(socket.getOutputStream())将文件从客户端内存写出到服务器端内存(不需要写文件来源和目的地了,就是从内存到内存)
- 服务器端使用网络字节输入流(socket.getInputStream())将文件从客户端内存读进到服务器端内存
- 服务器端使用本地字节输出流将文件写出到硬盘(需要写目的地路径)
- 服务器端使用网络字节输出流(socket.getOutputStream())向客户端内存发消息(不需要写文件来源和目的地了,就是从内存到内存)
- 客户端使用网络字节输入流(socket.getInputStream())读进
-
示例代码
客户端代码里有个标注的地方,非常重要public class TCPDemoServer { public static void main(String[] args) throws Exception { ServerSocket server=new ServerSocket(8848); Socket socket = server.accept(); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); byte[]gyu=new byte[1024]; int len=-1; while ((len=is.read(gyu))!=-1){ System.out.println(new String (gyu,0,len)); } os.write("已收到,组织上同意你的行动方案".getBytes()); socket.close(); } } public class TCPDemoClient { public static void main(String[] args) throws Exception { Socket socket=new Socket("192.168.137.1", 8848); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); FileInputStream fis=new FileInputStream("data1.txt"); byte[]gyu=new byte[1024]; int len=-1; while ((len=fis.read(gyu))!=-1){ os.write(gyu);//客户端使用网络字节输出流把文件传给服务器端,下一步必须是socket.shutdownOutput();!!!! } socket.shutdownOutput(); !!!//就这一步,非常非常重要,没有这个程序就会报错,压根不能运行!!!! while ((len=is.read(gyu))!=-1){ System.out.println(new String(gyu,0,len)); } fis.close(); socket.close(); } }
-
测试结果
尚未佩妥剑,转眼便江湖 愿阅尽千帆,归来仍少年 每一个不曾起舞的日子,都是对生命的辜负 无日问津的日子,恰恰是登峰造极的好时光 Process finished with exit code 0 服务器代码 ===================================================== 客户端代码 已收到,组织上同意你的行动方案 Process finished with exit code 0
TCP加入多线程
-
前面Server和Client只是进行了简单的通信操作,服务器端接收到客户端连接之后,服务器端向服务端返回一个响应。现在考虑实现一个界面的C/S聊天室的应用,服务端应该为每一个客户端都进行处理。这种情况下就需要在其中加入多线程操作,因为服务端读取客户端数据时是线程阻塞的,如果要同时为其他客户端进行响应,则只能通过多线程的方式来进行处理,所以需要加入多线程操作。
-
下面来进行一个简单的多客户端与服务器端的交互操作,客户端向服务端发送消息,服务端随机选择回复语句。
服务端资料文件
小艾是机器人,小艾不吃东西,可是小艾也想知道食物的美味!
小艾也很喜欢看电影,小艾也很喜欢跟你一起看电影呢!
你能说的更清楚一点吗?小艾看不懂。
小艾最近也没有关注过,不能给你很好的提示。
小艾很生气,小艾不理你了!
你是天下第一帅!小艾好喜欢你啊!
(_) 姐姐你也很漂亮,可是再漂亮有什么用?又没有男朋友。
服务端代码
public class AIServer { class Chat implements Runnable{ private Socket socket = null; private List<String> checkList = new ArrayList<>(); private List<String> overList = new ArrayList<>(); public Chat(Socket socket) { this.socket = socket; try { BufferedReader reader = new BufferedReader(new FileReader("./src/data/chat.txt")); String line = null; while((line=reader.readLine()) != null) { checkList.add(line); } reader.close(); overList.add("886"); overList.add("再见"); overList.add("回聊"); overList.add("下次再聊"); overList.add("走了"); } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); writer.println("你好,小艾有什么能帮你的吗?"); String line = null; while((line=reader.readLine()) != null) { // 是否聊天结束 boolean flag = false; for(String content : overList) { if(line.contains(content)) { flag = true; break; } } if(flag) { writer.println("小艾很喜欢跟你聊天,么么哒!"); break; } if(line.contains("吃") && (line.contains("东西") || line.contains("什么"))) { writer.println(checkList.get(0)); }else if(line.contains("跟我") && line.contains("看电影")) { writer.println(checkList.get(1)); }else if(line.contains("好看") && line.contains("电影")) { writer.println(checkList.get(3)); }else if(line.contains("我") && (line.contains("帅不帅") || line.contains("很帅"))) { writer.println(checkList.get(5)); }else if((line.contains("我") || line.contains("姐姐")) || line.contains("漂亮")) { writer.println(checkList.get(6)); }else if(line.contains("你") && (line.contains("笨") || line.contains("丑"))) { writer.println(checkList.get(4)); }else { writer.println(checkList.get(2)); } } socket.close(); } catch (Exception e) { e.printStackTrace(); } } } public AIServer() { try { ServerSocket server = new ServerSocket(8000); while(true) { Socket socket = server.accept(); new Thread(new Chat(socket)).start(); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new AIServer(); } }
客户端代码
public class AIClient { public AIClient() { try { Socket socket = new Socket("192.168.1.79", 8000); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = null; PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); Scanner scan = new Scanner(System.in); while((line=reader.readLine()) != null) { System.out.println(line); if(line.contains("么么哒")) { break; } String content = scan.nextLine(); writer.println(content); } scan.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new AIClient(); } }
两人聊天
-
在上面的示例程序中,存在一些缺陷,下面将程序进行优化处理。1、让服务端只进行中转站的作用,记录客户端的Socket对象,使用Map进行存储,为每个Socket开启一个线程来执行。2、服务端将接收到信息,发送给另外一个Socket对象。
-
示例代码:服务端
public class ChatServer { private Map<String,Socket> map = new HashMap<>(); // 定义读取的任务 class ReadTask implements Runnable{ private int num; private String name; public ReadTask(int num) { this.num = num; if(num==1) { name = "张三"; }else { name = "李四"; } } @Override public void run() { try { Socket read = map.get(String.valueOf(num)); BufferedReader reader = new BufferedReader(new InputStreamReader(read.getInputStream())); PrintWriter writer = null; String line = null; Socket send = null; while((line=reader.readLine()) != null) { if(num == 1) { // 第1个人给第2个人发 if(map.size() != 1) { send = map.get("2"); } } else { // 第2个人给第1个人发 send = map.get("1"); } if(map.size() == 2) { writer = new PrintWriter(send.getOutputStream(), true); line = name+"说:"+line; }else { writer = new PrintWriter(read.getOutputStream(), true); line = "对方还未上线,请稍等!!!"; } writer.println(line); } // 结束了 map.remove(String.valueOf(num)); } catch (Exception e) { e.printStackTrace(); } } } public ChatServer() { System.out.println("...........................已开启服务端....................."); try { ServerSocket server = new ServerSocket(8000); int num = 1; while(true) { Socket socket = server.accept(); map.put(String.valueOf(num), socket); // 开始读的任务 new Thread(new ReadTask(num)).start(); num++; if(map.size() == 0) { break; } } server.close(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new ChatServer(); } }
客户端代码
public class ChatClient { class ReadTask implements Runnable{ private Socket socket = null; public ReadTask(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = null; while((line=reader.readLine()) != null) { System.out.println(line); } } catch (Exception e) { e.printStackTrace(); } } } public ChatClient() { try { Socket socket = new Socket("192.168.182.1", 8000); new Thread(new ReadTask(socket)).start(); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); Scanner scan = new Scanner(System.in); while(true) { String content = scan.nextLine(); writer.println(content); System.out.println("你说:"+content); if(content.contains("么么哒")) { break; } } // 通知结束 socket.isOutputShutdown(); scan.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new ChatClient(); } }
通信协议
-
客户端与服务器端连接之后,双方必须严格遵守约定的规则进行消息的发送,否则就无法保证交流。例如:客户端与服务器端发送聊天信息的时候,也可以传输文件。那么对于服务器端就必须明确客户端发送过来的到底是什么类型的数据,也就是所谓的消息类型。
网络通信最底层通信的还是byte类型的数据,那么为了区分消息类型,通常有如下三种策略:
- 间隔符:使用特殊符号的字符串将数据进行分隔,然后转换成byte进行传输。缺点:处理小功能可以,如果处理较为复杂的数据类型时,不灵活;尤其是处理二进制数据时,不确定数据包含何种信息。
- 定长字符串:将发送的数据都组装成一定长度的数据进行发送,数据不够的使用二进制0补充。缺点:可重复利用性较低。
- 消息头+消息体:比较常用的一种策略,可以对消息头和消息体自行定义为合适的Java类,其中共用的数据放入到消息头中,具体的数据放入到消息体中。例如:IP地址,发送的数据长度,发送的数据类型等。而不同类型的数据则可以定义成不同类型的Java类进行封装。
UDP网络编程
UDP通信
-
不需要利用IO流实现数据传输,它是以数据报包的形式传递的
-
UDP通信没有服务器的概念,只有发送端和接收端,启动的时候必须先启动接收端,这样发送端才能顺着接收端的ip和端口号找过去
-
UDP是一种无连接的通信方式,它的特点是传输速度快。
在Java中使用DatagramSocket和DatagramPacket两个封装类来完成UDP方式的通信。DatagramSocket表示用来发送和接收数据报包的套接字。数据报包这个封装类就是DatagramPacket。 -
每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻找它的目的地
-
DatagramSocket常用构造方法
DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口。 DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。
-
DatagramSocket常用方法
void receive(DatagramPacket p) 从此套接字接收数据报包。 void send(DatagramPacket p) 从此套接字发送数据报包。 void close() 关闭此数据报套接字。
-
DatagramPacket常用构造方法
DatagramPacket(byte[] buf, int length, InetAddress address, int port) //第三个参数是InetAddress类型的IP!!==>如果是本机的话: //InetAddress address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());!!!!!!!!! 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。//用于发送端 DatagramPacket(byte[] buf, int length) 构造 DatagramPacket,用来接收长度为 length 的数据包。//用于接收端
-
DatagramPacket常用方法
InetAddress getAddress() 返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
-
代码示例:
public class UDP_Send { public static void main(String[] args) throws Exception { DatagramSocket ds=new DatagramSocket(); int len=-1; byte[]gyu=new byte[1024]; FileInputStream fis = new FileInputStream("Name"); while ((len=fis.read(gyu))!=-1){ DatagramPacket dp=new DatagramPacket(gyu,0,len,InetAddress.getLocalHost(),9898); ds.send(dp); } // byte []gyu="一生有爱何惧风飞沙!~".getBytes(); //DatagramPacket dp=new DatagramPacket(gyu,0,len,InetAddress.getLocalHost(),9898); // ds.send(dp); ds.close(); } } //=========================================== public class UDP_receive { public static void main(String[] args) throws Exception { DatagramSocket ds=new DatagramSocket(9898); byte[]gyu=new byte[1024]; DatagramPacket dp=new DatagramPacket(gyu, 0,gyu.length); ds.receive(dp); System.out.println(new String(gyu,0,gyu.length)); ds.close(); } }
-
测试结果
喜欢唱歌 擅长跳舞 不爱说话,喜欢咬人 大风起兮云飞扬,威加海内兮归四方~ 时不利兮骓不逝,骓不利兮可奈何,虞兮虞兮奈若何~ Process finished with exit code 0 接收端 UDP_ReceiveClient =============================================================================== 发送端 UDP_SendClient Process finished with exit code 0
UDP通信
URL&URLConnection
-
Java网络支持提供了java.net包,该包下的URL和URLConnection等类提供了以编程方式访问Web服务的功能
-
URL(Uniform Resource Locator)对象,表示统一资源定位符,它是指向互联网“资源”的指针
-
URL格式
protocol(协议)😕/host(主机):port(端口)/resource-name (资源文件名称)
例如:http://www.langsin.com/index.html
网络三大基石:HTML,http,url -
URL常用构造方法
URL(String spec) 根据 String 表示形式创建 URL 对象。
-
URL常用方法
Object getContent() 获取此 URL 的内容。 String getFile() 获取此 URL 的文件名。 String getQuery() 获取此 URL 的查询部分。 URLConnection openConnection() 返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。 InputStream openStream() 打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。 String getHost() 获取此 URL 的主机名(如果适用)。 String getPath() 获取此 URL 的路径部分。 int getPort() 获取此 URL 的端口号 String getProtocol() 获取此 URL 的协议名称。 String getRef() 获取此 URL 的锚点(也称为“引用”)。
-
常用方法代码
public static void main(String[] args) throws Exception { URL url=new URL("https://www.kuangstudy.com/course?cid=1"); //获取协议 System.out.println("协议:"+url.getProtocol()); //获取域名/ip System.out.println("域名/ip:"+url.getHost()); //获取端口 System.out.println("端口:"+url.getPort()); //获取请求资源 System.out.println("请求资源1:"+url.getFile()); System.out.println("请求资源2:"+url.getPath()); //获取参数 System.out.println("参数:"+url.getQuery()); //获取锚点 System.out.println("锚点:"+url.getRef()); }
-
测试结果
协议:https 域名/ip:www.kuangstudy.com 端口:-1 请求资源1:/course?cid=1 请求资源2:/course 参数:cid=1 锚点:null Process finished with exit code 0
-
HttpURLConnection构造方法
protected HttpURLConnection(URL u) HttpURLConnection 的构造方法。
-
HttpURLConnection方法
String getRequestMethod() 获取请求方法。 void setRequestMethod(String method) 设置 URL 请求的方法, GET POST HEAD OPTIONS PUT DELETE TRACE 以上方法之一是合法的,具体取决于协议的限制。 void setRequestProperty(String key, String value) 设置一般请求属性。 //这个方法来自URLConnection类
最简单的网络爬虫( Web Spider)
-
网络爬虫
public class Spider { public static void main(String[] args) throws Exception { //获取URL URL url=new URL("https://www.jd.com"); //下载资源 InputStream is = url.openStream(); BufferedReader br=new BufferedReader(new InputStreamReader(is,"UTF-8")); String msg=null; while (null!=(msg=br.readLine())){ System.out.println(msg); } br.close(); } }
-
上述代码是直接将内容输出到控制台上面,也可以输出到指定文件夹
-
有时候爬虫的时候,会出现"403 for URL",但是只要浏览器可以访问的,我们都可以爬下来,只不过要费点力气而已啦
-
网络爬虫+模拟浏览器
public class Spider { public static void main(String[] args) throws Exception { //获取URL URL url = new URL("https://www.dianping.com"); //下载资源 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Mobile Safari/537.36");//这个参数的获取很关键!!! 找到网页==>开发者模式==>切到Network==>点击左上角整蛊排名右边的那个图标==>鼠标随便点一下网页上的东西==>开发者模式的中间左边"Name"下会出现很多东西,随便点一个==>"Headers"==>"RequestHeaders"==>把"user-agent"这个全部ctrl+C,然后到程序里ctrl+V.注:在程序里面把"user-agent:................"里的冒号":"去掉,换成 引号+逗号 "," BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String msg = null; while (null != (msg = br.readLine())) { System.out.println(msg); } br.close(); } }
-