关于网络协议
TCP
TCP,传输控制协议(Transmission Control Protocol),是面向连接的通信协议。它提供两台计算机之间的可靠无差错的数据传输。应用程序通过 TCP 进行通讯时,数据源和接收目标之间会建立一个虚拟的连接,这个连接一旦建立,两台计算机之间就可以把数据作为双向字节流进行交换。其实基于 TCP 的通信传输的数据帧也不是可靠的,只是在发送 TCP 数据帧的时候,接收端每次接收到数据以后就会进行差错验证,如果没有任何问题,就会回馈发送端一个数据帧,告诉它接收成功,发送端就会继续发送第二个数据包。通过这样的问答方式就保证了 TCP 传输数据的无差错性。这种行为是靠驱动程序的底层协议来完成的。
UDP
UDP,用户数据报协议(User Datagram Protocol),是面向无连接的通信协议。它是一个无连接的通信协议,UDP 不保证用户数据的可靠性传输,但能向若干个目标发送数据,和接收若干个数据源的数据。如果一台主机向另一台主机发送 UDP 数据,这个数据会立即发送出去,而不管另一台主机是否准备好接收数据。当网络状态异常的时候,会造成 UDP 数据报的丢失。如果另外一台主机接收到了数据,它不会确认是否收到。
Java中的网络编程
Socket
是网络驱动层提供给应用程序编程接口和一种机制。我们可以把 Socket 比喻成是一个港口码头。应用程序只要把货物放到港口码头上,就算完成了货物的运送。对于接收方应用程序也要创建一个港口码头,只需要等待货物到达码头后将货物取走。
Socket 是在应用程序中创建的,它是通过一种绑定机制与驱动程序建立关系,告诉自己所对应的 IP 和 Port。在网络上传输的每一个数据帧,必须包含发送者的 IP 地址和端口号。创建完 Socket 以后,应用程序写入到 Socket 的数据,由 Socket 交给驱动程序向网络上发送数据,计算机从网络上收到与某个 Socket 绑定的 IP 和 Port 相关的数据后,由驱动程序再交给 Socket ,应用程序就可以从这个 Socket 中读取接收到的数据。网络应用程序就是这样通过 Socket 发送和接收的。
Socket 数据发送过程:
1、由应用程序产生一个 Socket。
2、应用程序调用 bind() 方法,将 Socket 的信息通知给驱动程序。这个 Socket 的信息主要是包括 IP 地址和端口号。
3、应用程序向 Socket 中写入数据数据。
4、驱动程序从 Socket 中取出数据,并通过网卡发送数据。
Socket 数据接收过程:
1、由应用程序产生一个 Socket。
2、应用程序调用 bind() 方法,将 Socket 的信息通知给驱动程序。
3、如果驱动程序收到的数据包中,包含了这个 Socket 所包含的 IP 和端口号,那么驱动程序就会把数据处理后传递给 Socket 中。
4、当 Socket 当中有数据时,应用程序就将数据从 Socket 中取走。
Java 中的网络编程类
- 位于 java.net 包中。
- 用于 UDP 通信的 Socket 对应的是 DatagramSocket 类。
- 用于 TCP 通信的 Socket 对应的是 ServerSocket 类。由于 TCP 程序分为客户端和服务器端,ServerSocket 是用于服务器端,Socket 类用于 TCP 通信的服务器和客户端。这个服务器端指的是专用于与客户端进行通信的 Socket ,ServerSocket 是用于接收客户端连接的 Socket。如果在创建 Socket 时没有指定端口号,系统就会为它分配一个其它网络程序还没有使用的端口号, 如果在创建 Socket 时没有指定 IP 地址,底层驱动程序会选择计算机上的任意一个 IP 地址作为发送源 IP 地址。对于一般只有一个 IP 地址的计算机不用指定 IP 地址。如果有多个网卡多个 IP 就应该指定特定IP 地址。
UDP 网络程序
一、DatagramSocket 类(用于创建收发 UDP 数据报的对象)
(1) 构造函数:
public DatagramSocket(); // 没有指定端口号和 IP 地址,接收方可以获取发送方使用的 IP 地址和端口号。
public DatagramSocket(int port); // 如果有可能先接收数据。
public DatagramSocket(int port, InetAdderss Iaddr); // 如果 UDP 程序运行在拥有多个 IP 地址的主机上。
(2)DatagramSocket 的 close() 方法:
当应用程序不再需要使用 DatagramSocket 对象的时候,应该调用 close() 方法,以通知驱动程序释放对应的资源,系统就可以将这个 DatagramSocket 所对应的端口号交给其它应用程序使用。
(3)send(DatagramPacket p) 方法:用于发送 DatagramSocket 对应的数据包
(4)receive(DatagramPacket p) 方法:用于接收 DatagramSocket 对应的数据包
二、DatagramPacket 类
如果把 DatagramSocket 比作港口码头,那么 DatagramPacket 就是发送和接收数据的集装箱。
(1)构造函数:
public DatagramPacket(byte[] buf, int length); // 指定了数据包的缓冲区,和缓冲区的大小。(创建接收数据的 DatagramPacket 对象)
public DatagramPacket(byte[] buf, int length, InetAddress address, int port); // 还指定了接收数据包目标计算机的地址和端口。(创建发送数据的 DatagramPacket 对象)
(2)getInetAddress 和 getPort 方法:
当用于接收数据包的 DatagramPacket 对象,接收到了网络上发送过来的数据后,对方的 IP 地址和端口号也是封装在 DatapramPacket 当中的。
(3)getData 和 getLength 方法:
当创建接收方的 DatagramPacket 对象时,因为无法预知对方发送的实际数据报的长度,一般都是定义了一个相对大的字节数组,来作为缓冲区,传递给 DatagramPacket 构造函数。getData() 方法就是获取内存缓冲区,也就是 buf 字节数组。getLength() 方法返回的是 DatagramPacket实际接收到的数据的长度。
三、InetAddress 类
是用于表示计算机 IP 地址的类。
(1)getByName() 静态方法。根据类似 “192.168.0.1” 这些字符串格式的计算机地址,返回一个 InetAddress 实例对象。
(2)getHostAddress() 用于返回一个 InetAddress 对象当中封装的以 “.” 分割的字符串形式的 IP 地址。
四、编程实例(1) - 编写发送端和接收端,并实现通信:
发送端:
- package cn;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- public class Send {
- public static void main(String[] args) throws Exception {
- // 发送数据
- System.out.println("请输入要发送的消息:");
- BufferedReader cbr = new BufferedReader(
- new InputStreamReader(System.in));
- String smsg = cbr.readLine();
- DatagramSocket sds = new DatagramSocket();
- DatagramPacket sdp = new DatagramPacket(smsg.getBytes(), smsg
- .getBytes().length, InetAddress.getByName("127.0.0.1"), 3000);
- sds.send(sdp);
- sds.close();
- }
- }
接收端:
- package cn;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- public class Receive {
- public static void main(String[] args) throws Exception {
- byte[] bytse = new byte[1024];
- DatagramSocket rds = new DatagramSocket(3000);
- DatagramPacket rdp = new DatagramPacket(bytse, 1024);
- rds.receive(rdp);
- String rmsg = new String(rdp.getData(), 0, rdp.getData().length);
- System.out.println("收到来自(" + rdp.getAddress() + ":" + rdp.getPort()
- + ")的消息:" + rmsg);
- rds.close();
- }
- }
运行结果:
编程实例(2)基于 UDP 的网络聊天程序,可以实现外网聊天:
我已经在我先前的博客中写了这个程序,地址:http://blog.csdn.net/DriverKing/archive/2011/06/26/6568793.aspx
TCP 网络程序
一、TCP 网络程序工作原理:
利用 UDP 两个通信的计算机是平等的,没有主从之分,而利用 TCP 两个进行通信的程序,是有主从关系的。一个称为服务器程序,另一个称为客户机程序。两者的功能和编写方法大不一样,
TCP 客户端与 TCP 服务器端程序的交互过程:
首先,由 TCP 服务器程序创建一个 ServerSocket 类,然后调用 accept() 方法等待客户端的连接。
其次,客户端程序创建一个 Socket 并请求与服务器建立连接。
接着,是服务器接收客户的连接请求,并创建一个与请求相关的新的 Socket 与客户机专线连接。
再次,建立连接的两个 Socket 在一个单独的线程(由服务器创建)上对话。
最后,服务器程序又开始等待新的连接请求,当新的连接请求到达时,它又会重复 2~5 的过程。
二、ServerSocket 类
(1)构造函数:
public ServerSocket(); // 不能直接使用,必须调用它实例对象的 bind() 方法与绑定端口号或 IP
public ServerSocket(int port); // 如果 port 等于 0 ,那么系统将自动分配一个未启用的端口号,作为 TCP 服务器最好确定端口号,这样客户机才能知道连接服务器的哪个端口。(一般用这个)
public ServerSocket(int port, int backlog); // 除了指定端口号外,还可以指定在服务器很忙的时候,可以与之保持连接请求的客户机数量。没有指定的话默认为 50。
public ServerSocket(int port, int backlog, InetAddress bindAddr); // 可以指定 ServerSocket 绑定的 IP 地址,适用于多个网卡或多个 IP。
(2)close 方法:
当程序不再使用 ServerSocket 对象时,应该调用 ServerSocket 的 close() 方法,将 ServerSocket 关闭,通知驱动程序释放所对应保留的资源。系统将会把这个端口号非配给其它应用程序使用。
(3)accept 方法:
程序调用 ServerSocket 对象的 accept() 方法,等待客户端的请求,一旦 accept() 方法接收了客户端的请求,这个方法就会返回一个与客户端建立了专线连接的 Socket 对象。
三、Socket 类
(1)构造函数:
public Socket(); // 不能与任何服务器建立连接,需要调用 Socket 类的 connect( SocketAddress endpoint) 方法才可以使用。(用于一个客户机连接多个服务器)。
public Socket(String host, int port);
public Socket (InetAddress address, int port); // 第二个和第三个会根据 IP 和端口号去连接服务器。
public Socket(String host, int port, InetAddress localAddr, int localPort);
public Socket(InetAddress address, int port, InetAddress localAddr, int localPort); // 第四个和第五个增加了本地 IP 地址和端口号的绑定。
(2) getInputStream 和 getOutputStream 方法
getOutputStream() 方法用于返回 Socket 输入流的对象; getOutputStream () 方法用于返回 Socket 输出流的对象。Socket 的某一端只要向它的输出流对象中写入数据,那么另一端就能从自己的输入流对象中读取到数据。
Java网络编程实例解析
四、编程(1)编写完善的的 TCP 服务器客户端程序
此程序已经在先前的博客中写过,地址:http://blog.csdn.net/DriverKing/archive/2011/06/27/6569414.aspx
(2)编写服务器一对多的聊天程序
服务器端:
- package cn;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- import java.net.ServerSocket;
- import java.net.Socket;
- public class TcpServer {
- public static void main(String[] args) throws Exception {
- ServerSocket ss = new ServerSocket(3000);
- while (true) {
- Socket socket = ss.accept();
- new Thread(new Task(socket)) {
- }.start();
- }
- }
- }
- class Task implements Runnable {
- Socket socket;
- public Task(Socket socket) {
- this.socket = socket;
- }
- public void run() {
- System.out.println(socket.getInetAddress().getHostAddress() + ":"
- + socket.getPort() + "--已经上线");
- new Thread(new ServerSend(socket)) {
- }.start();
- new Thread(new ServerReceive(socket)) {
- }.start();
- }
- }
- class ServerSend implements Runnable {
- Socket socket;
- public ServerSend(Socket socket) {
- this.socket = socket;
- }
- public void run() {
- try {
- BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
- socket.getOutputStream()));
- BufferedReader br = null;
- while (true) {
- System.out.println("请输入消息内容:");
- br = new BufferedReader(new InputStreamReader(System.in));
- String msg = br.readLine();
- bw.write(msg + "/r/n");
- bw.flush();
- if ("exit".equalsIgnoreCase(msg))
- break;
- }
- br.close();
- bw.close();
- socket.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- class ServerReceive implements Runnable {
- Socket socket;
- public ServerReceive(Socket socket) {
- this.socket = socket;
- }
- public void run() {
- try {
- BufferedReader br = new BufferedReader(new InputStreamReader(socket
- .getInputStream()));
- while (true) {
- String msg = br.readLine();
- if ("exit".equalsIgnoreCase(msg))
- break;
- System.out.println("("
- + socket.getInetAddress().getHostAddress() + ":"
- + socket.getPort() + "):" + msg);
- }
- br.close();
- socket.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
客户端:
- package cn;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- import java.net.Socket;
- public class TcpClient {
- public static void main(String[] args) throws Exception {
- Socket socket = new Socket("192.168.2.188", 3000);
- new Thread(new ClientSend(socket)) {
- }.start();
- new Thread(new ClientReceive(socket)) {
- }.start();
- }
- }
- class ClientSend implements Runnable {
- Socket socket;
- public ClientSend(Socket socket) {
- this.socket = socket;
- }
- public void run() {
- try {
- BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
- socket.getOutputStream()));
- BufferedReader br = null;
- while (true) {
- System.out.println("请输入消息内容:");
- br = new BufferedReader(new InputStreamReader(System.in));
- String msg = br.readLine();
- bw.write(msg + "/r/n");
- bw.flush();
- if ("exit".equalsIgnoreCase(msg))
- break;
- }
- br.close();
- bw.close();
- socket.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- };
- }
- class ClientReceive implements Runnable {
- Socket socket;
- public ClientReceive(Socket socket) {
- this.socket = socket;
- }
- public void run() {
- try {
- BufferedReader br = new BufferedReader(new InputStreamReader(socket
- .getInputStream()));
- while (true && !socket.isClosed()) {
- String msg = br.readLine();
- if ("exit".equalsIgnoreCase(msg))
- break;
- System.out.println("("
- + socket.getInetAddress().getHostAddress() + ":"
- + socket.getPort() + "):" + msg);
- }
- br.close();
- socket.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
运行效果:
五、在 TCP 网络连接上传递对象
(1)ObjectInputStream 和 ObjectOutputStream 可以从底层输入流中读取对象类型的数据,和将对象类型的数据写入到底层输出流。
(2)使用 Object InputStream 和 ObjectOutputStream 来包装底层网络字节流,TCP 服务器和 TCP 客户端之间就可以传递对象类型的数据。
编程实例 :通过网络传输 Java 对象:
- package cn;
- import java.io.ObjectOutputStream;
- import java.net.Socket;
- public class TcpClient {
- public static void main(String[] args) throws Exception {
- Socket socket = new Socket("192.168.2.188", 3000);
- Student stu = new Student("DriverKing_斌", 23, "男");
- ObjectOutputStream oos = new ObjectOutputStream(socket
- .getOutputStream());
- oos.writeObject(stu);
- oos.close();
- socket.close();
- }
- }
- package cn;
- import java.io.ObjectInputStream;
- import java.net.ServerSocket;
- import java.net.Socket;
- public class TcpServer {
- public static void main(String[] args) throws Exception {
- ServerSocket ss = new ServerSocket(3000);
- Socket socket = ss.accept();
- ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
- Student stu = (Student) ois.readObject();
- System.out.println(stu.getName() + "," + stu.getAge() + ","
- + stu.getSex());
- ois.close();
- socket.close();
- ss.close();
- }
- }
- // 输出
- // Microsoft Windows [版本 6.1.7600]
- // 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。
- //
- // D:/>java TcpServer
- // DriverKing_斌,23,男
- //
- // D:/>
URL(Uniform Resource Locator 统一资源定位符)
(1)URL 的基本组成:协议、主机名、端口号、资源名。例如:http://bolg.csdn.net/driverking
(2)相对 URL:一个相对的 URL 它不包括协议和主机,它表示 它的路径与当前文档或其它的 Internet 资源的主机名相同设置有相同的目录路径。
“/a.htm” 表示与主机上某种协议的根目录。“../a.htm” 表示表示所在目录的父目录。“a.htm” 表示它是当前目录下的子目录。
(3)URL 编码规则:例:将 “空格” 转换为 “+” 号。“0-9、a-z、A-Z” 保持不边,其它字符使用当前字符编码的内存中的十六进制格式表示。
HTTP 协议的会话过程
现在广泛使用的 是 HTTP 1.1 版本, HTTP 1.1 相对于 HTTP 1.0 来说最大特点就是支持持久性的连接。
(1) HTTP 1.0 的连接过程 :浏览器到 Web 服务器之间的所有通信,都是完全独立分开的请求与相应对。这种通信过程总是由 Web 浏览器发送请求,Web 服务器被动的对请求进行响应。一次响应的处理时间与浏览器打开的时间没有关系,服务器向浏览器传送了请求响应后,这个连接就终结了。浏览器与服务器的连接是短暂的,只是在浏览器发出请求和服务器返回结果的期间,而不是持续到浏览器程序关闭。即使在一个页面不停的请求数据,你的每一次请求都是与服务器建立一个短暂的连接,而不是共享同一个连接。因此 HTTP 1.0 有性能上的缺陷。
(2)HTTP 1.1 的连接过程 :当客户机发出第一次请求,然后服务器返回第一次请求的网页文档,浏览器在解析这个网页文档的过程中,发现又要去服务器请求第二次数据,那么它就会再次给服务器发出第二次请求,服务器就会返回第二次请求响应……第 n 次。并且浏览器不必等待一次的请求相应完毕,它就可以再一次的发送请求,这样大大的提高了网页文档的传送效率。客户机请求的信息和服务器响应的信息都称之为 HTTP 消息,HTTP 消息是有一定的格式和严格规定的。
一个完整的请求消息包括:(1)一个请求行(2)若干消息头(3)实体内容。例如:
GET www.baidu.com HTTP/1.1 (请求行,后面的内容都属于消息头部分)
Accept: */*
Accept-Language:en-us
Connectino: Keep-Alive
Host: localhost
……
这个 HTTP 请求消息中没有实体内容,但是紧接着消息头后,必须有一个空行,这是用来分割消息头和实体内容的,可以来表示消息头部分已经结束。
一个完整的响应消息包括:(1)一个状态行(2)若干个消息头(实体内容)。和请求消息一样,消息头和实体内容部分是可选的。消息头和实体内容之间必须有空行。例如:
HTTP/1.1 200 OK
Server:Microsoft-IIS/5.0
Date:Thu, 13 Jul 2000 05:46:53 GMT
Content-Length:2291(实体内容的长度,HTTP 1.1 必须包含该行)
Content-Type:text/html
Set-Cooike:……
……
<html>
<body>
……
(3)几个常用的 HTTP 消息头
- Connection 用于指定处理完本次请求/响应后,客户端与服务器是否继续保持连接。值可以为:Keep-Alive 或 close。HTTP 1.1 默认使用的是持久性连接。
- Accept-Language 用于指定客户机期望服务器返回的文档所使用的国家语言,可以指定多个以逗号(,)分隔的国家语言。
- Content-Length 用于表示实体内容的长度(字节数)
- Range 用于指定服务器只需返回文档中的部分内容及部分内容的范围。(对于较大文档的断点续传非常有用)。有这样几种格式:
1、Range: bytes=100-599(返回第100个字节和第599个字节之间的内容,包括了第100个和第599个,字节的顺序从 0 开始计算)
2、Range: bytes=100- (返回文档中的第100个字节以后的所有内容)
3、Range: bytes=-100 (返回文档中的最后100个字节的内容)
- Content-Range 是与 Range 一起使用的,用于指定服务器返回的部分实体内容的位置信息。只有客户机使用了 Range 请求后,服务器才会返回 Content-Range 消息。例如:
Content-Range: bytes 2543-4532/7898 (以 Byte 为单位的返回第 2543 - 4532之间的 的数据,7898表示总的实体内容的大小是 7898 个字节)
URL 类(表示 Internet 网络资源的 URL)
(1)构造函数:
public URL(String sprc); // 直接使用表示 Internet 网络资源的 URL 字符串来创建 URL 对象。
public URL(String protocol, String host, int port, String file); // 使用协议名,主机名,端口号和资源本身的名称。
public URL(String protocol, String host, int port, String file, URLStreamHandler handler); // 多了一个对这种资源进行具体处理的协议处理器对象。
public URL(URL context, String spec); // 是在一个已有的 URL 对象基础上,再加上一个相对的 URL 字符文本来创建一个新的 URL 对象。例如 context 指向的是一个目录资源,spec 是这个目录下的文件名,这两者的组合就成了一个指向这个文件名的完整的 URl。
(2)URL 类本身并不能获取网络资源中具体的内容,
(3)openConnection 方法和 URLConnection 对象
openConnection() 方会调用协议处理器,也就是 URLStreamHandler的 openConnetion() 方法,返回一个 URLConnection 对象, URLConnection对象代表与这个 URL 指定的网络资源的连接,程序调用 URLConnectio() 的方法,就可以获取网络资源中的具体内容。
(4)setURLStreamHandlerFactory(URLStreamhandlerFacoory fac) 静态方法
URLStreamhandlerFacoory 用于建立协议名和协议名对应处理器的关系。例如:在创建 URL 对象时指定的是 HTTP 那么它就应该调用 HTTP 协议处理器。
(5)StreamHandlerFactory 类的 createURLStreamHandler(String protocol)
在创建 URL 对象时,Java 虚拟机会根据其中出现的协议名,调用 StreamHandlerFactory 对象中的 createURLStreamHandler(String protocol) 方法,将协议名传递进去,在方法内部,它会根据你传进来的协议名,创建出相应的协议处理器对象,然后返回给 URL 对象。
URLConnection 和 HTTPURLConnection 类
URLConnection对象代表与这个 URL 指定的网络资源的连接,程序调用 URLConnectio() 的方法,就可以获取网络资源中的具体内容。URLConnection 本身并不能访问网络资源内容都是由它的子类完成的。 HTTPConnection 就是 URLConnection 的子类,
(1) URLConnection 的连接过程
在产生 URLConnection对象后, URLConnection对象并没有真正的与 URL 对象指定的网络资源进行连接, 在这之前 URLConnection可以调用各种 set 方法来设置两者之间的连接参数和属性。
(2)setRequestProperty() 用来设置各个协议的请求消息头。
(3)getHeaderFields() 用来获取 HTTP 的响应消息头。
(4)getInputStream 和 getOutputStream 分别返回用来读取 HTTP 响应实体内容的 InputStream 对象和 请求消息内容的 OutputStream 对象。
(5)一个 HTTP 连接可以被多个 HttpURLConnection 实例对象共享,调用 HttpURLConnection的 disconnect() 方法可以关闭底层共享网络。
编程实例:将访问 http://www.bing.com 站点的 HTTP 请求消息的 Accept-Language 头分别设置成日文和中文后,然后打印出 www.goolge.com 站点返回的所有响应消息头和网页内容
- package cn;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- // 将访问 http://www.bing.com 站点的 HTTP 请求消息的 Accept-Language
- // 头分别设置成日文和中文后,然后打印出 www.goolge.com
- // 站点返回的所有响应消息头和网页内容
- public class HTTPTest {
- public static void main(String[] args) throws Exception {
- System.out.println("获取日文:");
- getLanguage("ja");
- System.out.println("/n获取中文:");
- getLanguage("zh-cn");
- }
- public static void getLanguage(String language) throws Exception {
- URL url = new URL("http://www.bing.com");
- HttpURLConnection huc = (HttpURLConnection) url.openConnection();
- huc.setRequestProperty("Accept-Language", language);
- huc.connect();
- // 请求消息头
- Map<String, List<String>> request = huc.getHeaderFields();
- Set<String> keySet = request.keySet();
- Iterator<String> iterator = keySet.iterator();
- while (iterator.hasNext()) {
- String key = iterator.next();
- System.out.println(key + ":" + request.get(key));
- }
- // 返回的 http 响应消息头
- BufferedReader br = new BufferedReader(new InputStreamReader(huc.getInputStream()));
- String info = null;
- while ((info = br.readLine()) != null) {
- System.out.println(info);
- }
- br.close();
- huc.disconnect();
- }
- }
网络基本知识:
在java中网络程序有两种协议:TCP和UDP,TCP通过握手协议进行可靠的连接,UDP则是不可靠连接。
IP地址:用于标记一台计算机的身份证。
IP地址由网络地址(确定网络)和主机地址(网络中的主机)组成。
子网掩码:为了区分网络地址和主机地址。
IP地址分为A类地址、B类地址、C类地址(常用)、D类地址、E类地址。
127.0.0.1(localhost)是本机地址。
IPV4和IPV6
IPV4使用4个十进制数表示,即32位二进制。
SMTP是简单邮件传输协议,端口号是25.
telnet用于连接远程计算机或者因特网计算机提供的服务。每个服务都会设定一个端口。
给出类似 telnet ip port 即可和特定的服务进行通信
如果要连接因特网的服务,不仅要给出端口,还要给出计算机的名称,只有给出IP地址和端口号时,才能够请求服务,并接收到应答。
URL和URI
URI:统一资源标识符,用于标识一个web资源,包含了两个部分。
(1)URL:统一资源定位符。能够精确的定位数据的URI
(2)URN:统一资源名称。除了URL的URI
在java中URI和URL是分开的两个类,URI类专门用于解析,URL用于通信。
URL
1.URI分类
绝对和相对:
(1)绝对URI是指有确定的协议。比如http,ftp。后面以/进行分隔
(2)相对URI是没有scheme的。
透明和不透明:
(1)不透明URI是不能够被解析的URI。不透明URI是绝对URI。scheme后面的部分不是以/进行分割。
分层和不分层:
(1)分层是绝对透明URI或相对URI。
所有的网页端口都是80.
2.URI的作用:
(1)解析
URI的格式:
[scheme:]scheme-specific-part[#fragment]
scheme表示用的协议,可以是http\https\ftp\file等。
scheme-specific-part是其余部分。
进一步细分:
[scheme:][//authority][path][?query][#fragment]
常用方法:
- getScheme()获得scheme;
- getSchemeSpecificPart()
- getPath()
- getAuthority()
(2)相对标识符和绝对标识符的转换
resolve和relative函数。
示例代码:
任务1:取得特定网址的html代码。
任务2:分析地址信息。
任务3:绝对地址和相对地址转换
- package org.core;
- import java.net.*;
- import java.util.*;
- public class URLTest1 {
- public static void main(String[] args) throws Exception {
- URL url = new URL("http://www.ecnu.edu.cn");
- Scanner in = new Scanner(url.openStream());
- while (in.hasNextLine()) {
- String str = in.nextLine();
- System.out.println(str);
- }
- URI uri = new URI("http://blog.csdn.net/xiazdong");
- System.out.println(uri.getScheme());
- System.out.println(uri.getSchemeSpecificPart());
- System.out.println(uri.getAuthority());
- System.out.println(uri.getUserInfo());
- System.out.println(uri.getHost());
- System.out.println(uri.getPort());
- System.out.println(uri.getPath());
- System.out.println(uri.getQuery());
- System.out.println(uri.getFragment());
- String str = "/article/details/6705033";
- URI combined = uri.resolve(str);// 根据uri的路径把str变成绝对地址
- System.out.println(combined.getScheme()
- + combined.getSchemeSpecificPart());
- URI relative = uri.relativize(new URI(str));
- System.out.println(relative.getSchemeSpecificPart());
- }
- }
URL和URLConnection
URL的作用
1.如果想要获取某个网页的html源代码,比如http://blog.csdn.net/xiazdong 则只需要:
(1)URL url = new URL("http://blog.csdn.net/xiazdong");
(2)Scanner in = new Scanner(url.openStream());
即可.
2.获取消息头信息
- URLConnection connection = url.openConnection();
- connection.getHeaderFields()返回一个Map<String,List<String>>
- connection.getContentLength();
- connection.getContentType();
- connection.setDoOutput(true)获得输出流
- connection.getOutputStream();
- connection.getInputStream();
代码示例:
- package org.core;
- import java.net.*;
- import sun.misc.*;
- import java.util.*;
- import java.io.*;
- public class URLConnectionTest {
- public static void main(String[] args) throws Exception {
- String urlName = "http://java.sun.com";
- URL url = new URL(urlName);
- URLConnection connection = url.openConnection();
- Map<String, List<String>> map = connection.getHeaderFields();
- for (Map.Entry<String, List<String>> entry : map.entrySet()) {
- String key = entry.getKey();
- List<String> value = entry.getValue();
- System.out.println(key + ":" + value);
- }
- }
- }
在网页中如果要提交数据给web服务器,通常要把数据发送给web服务器,然后web服务器委派一个脚本对数据进行处理,返回一个相应。
通常发送数据的方法有两种:get和post。
(1)get方法是直接把数据跟在url的后面,以name=value进行传输,
每个数据之间用&进行分割,value中的空格用+替换,非字母数字用%替换,并后跟两个16进制数,这种编码方式称为URL编码。URLEncoder和URLDecoder
(2)post方法是通过URLConnection发送给服务器,编码方式和get一样。URLEncoder.encode(VALUE,"UTF-8");
一般在传输中文时会运用编码和解码。
示例:通过URLEncoder和URLDecoder编码和解码
略
InetAddress 根据域名得到IP地址或名称
没有构造方法,通过:
(1)InetAddress i1 = InetAddress.getByName(String)返回一个InetAddress实例。
(2)如果一个地址有多个ip地址,比如google,有3个ip地址,就调用InetAddress[] i2 = InetAddress.getAllByName(String);
InetAddress.getLocalhost()获得本机的InetAddress实例。
代码实例:
- package org.core;
- import java.net.InetAddress;
- public class InetAddressTest {
- public static void main(String[] args) throws Exception{
- InetAddress local = InetAddress.getLocalHost();
- System.out.println("本机地址:"+local.getHostAddress());
- System.out.println("本机名称:"+local.getHostName());
- InetAddress[] remote = InetAddress.getAllByName("www.google.com");
- for(InetAddress a : remote)
- {
- System.out.println("地址:"+a.getHostAddress());
- System.out.println("名称:"+a.getHostName());
- }
- }
- }
Socket(TCP)
Socket是一个用于机器之间通信的类。
Socket客户端:
(1)Socket s = new Socket(ip,port);打开一个套接字,发送请求
(2)InputStream istream = s.getInputStream();接收数据
(3)OutputStream ostream = s.getOutputStream();发送数据
需要用PrintWriter和Scanner进行包装,并且注意PrintWriter的自动缓冲。
Socket服务器:注意多个客户端同时访问服务器的问题:多线程
(1)ServerSocket server = new ServerSocket(port);创建一个端口
(2)Socket s = server.accept(); 只有当有客户端请求并连接,函数才会返回
(3)InputStream istream = s.getInputStream();接收数据
(4)OutputStream ostream = s.getOutputStream();发送数据
需要用PrintWriter和Scanner进行包装,并且注意PrintWriter的自动缓冲。
我们在使用PrintWriter时需要使用println()函数;
当服务器或客户端任意一方请求结束通信,则立刻停止。
问题1:在套接字中会发生阻塞的地方:
(1)实例化Socket时,会阻塞。
(2)在in.nextLine()类似操作时会阻塞。
解决方法:
(1)对于第一个问题,解决方法:
- Socket s = new Socket();建立无连接socket
- s.connect(new InetSocketAddress(host,port),timeout);设置超时。
(2)对于第二个问题,解决方法是设置s.setSoTimeout(long)设置超时时间
问题2:当客户端想要关闭套接字时,但却不能确定服务器是否还在发送数据,但是只要一关闭就立刻断开。
解决方法:
socket.shutdownOutput()关闭输出流
socket.shutdownInput()关闭输入流
半关闭示例代码:客户端发送hello给服务器,同时关闭输出流,服务器接收到后关闭输入流,等待5秒发送ECHO hello给客户端。
Client:
- import java.net.*;
- import java.io.*;
- import java.util.*;
- public class shutdownOutputClient {
- public static void main(String[] args)throws Exception {
- Socket s = new Socket("localhost",8819);
- Scanner in = new Scanner(s.getInputStream());
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- out.println("Hello");//输出hello
- s.shutdownOutput(); //关闭输出流
- System.out.println("关闭连接");
- while(in.hasNextLine()){
- System.out.println(in.nextLine());
- }
- s.close();
- }
- }
Server:
- import java.net.*;
- import java.io.*;
- import java.util.*;
- public class shutdownOutputServer {
- public static void main(String[] args)throws Exception {
- ServerSocket server = new ServerSocket(8819);
- Socket s = server.accept();
- Scanner in = new Scanner(s.getInputStream());
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- String str = in.nextLine();
- System.out.println(str);
- s.shutdownInput();
- System.out.println("关闭输入流");
- Thread.sleep(5000);
- out.println("Echo:"+str);
- s.close();
- }
- }
综合代码举例:实现一个简单的对等通信程序,通过多线程,一个线程接收数据,一个线程发送数据。
用户1:
- import java.util.*;
- import java.io.*;
- import java.net.*;
- public class Client{
- public static void main(String[]args)throws Exception{
- Socket s = new Socket("localhost",8819);
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- Thread t = new Thread(new Receive(s));
- t.start();
- //以下代码用于发送数据
- Scanner in = new Scanner(System.in);//键盘输入
- while(in.hasNextLine()){ //一直不断
- out.println(in.nextLine()); //发送键盘输入数据
- }
- }
- }
- class Receive implements Runnable //这个类用于接收数据
- {
- private Socket s;
- public Receive(Socket s)
- {
- this.s = s;
- }
- public void run()
- {
- try{
- Scanner in = new Scanner(s.getInputStream()); //in:接收数据
- String str = null;
- while(true)
- {
- str = in.nextLine();
- System.out.println("服务器说:"+str); //打印接收数据
- }
- }
- catch(Exception e){}
- }
- }
用户2:
- import java.util.*;
- import java.io.*;
- import java.net.*;
- public class Server{
- public static void main(String[]args)throws Exception{
- ServerSocket server = new ServerSocket(8819);
- Socket s = server.accept();
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- Thread t = new Thread(new Receive1(s));
- t.start();
- //以下代码用于发送数据
- Scanner in = new Scanner(System.in);//键盘输入
- while(in.hasNextLine()){ //一直不断
- out.println(in.nextLine()); //发送键盘输入数据
- }
- }
- }
- class Receive1 implements Runnable //这个类用于接收数据
- {
- private Socket s;
- public Receive1(Socket s)
- {
- this.s = s;
- }
- public void run()
- {
- try{
- Scanner in = new Scanner(s.getInputStream()); //in:接收数据
- String str = null;
- while(true)
- {
- str = in.nextLine();
- System.out.println("客户端说:"+str); //打印接收数据
- }
- }
- catch(Exception e){}
- }
- }
以上的程序属于C/S,需要同时维护客户端和服务器的代码。
B/S:浏览器和服务器,只需要维护一方代码即可。
聊天工具使用UDP非常多,因为我们通常也会遇到我们发给另一个人一条消息,另一个人却没有收到的情况。
DatagramPacket和DatagramSocket 数据报
代码举例:实现服务器发送数据报到客户端。
Client:
- package org.core;
- import java.net.*;
- import java.io.*;
- public class DatagramClient {
- public static void main(String[] args) throws Exception{
- byte[]buf = new byte[1024];
- DatagramPacket packet = new DatagramPacket(buf,1024);
- DatagramSocket client = new DatagramSocket(9000);
- client.receive(packet);
- String str = new String(buf,0,packet.getLength());
- System.out.println(packet.getAddress().getHostName()+":"+str);
- client.close();
- }
- }
Server:
- package org.core;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- public class DatagramServer {
- public static void main(String[] args)throws Exception {
- DatagramSocket server = new DatagramSocket(3000);
- String str = "hello world";
- DatagramPacket packet = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getLocalHost(),9000);
- server.send(packet);
- server.close();
- }
- }
QQ聊天应用
Server端
- package org.xiazdong.server;
- import java.awt.BorderLayout;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.io.PrintStream;
- import java.net.InetAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- import javax.swing.JButton;
- import javax.swing.JFrame;
- import javax.swing.JPanel;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- import javax.swing.JTextField;
- public class Server3 extends JFrame{
- static JTextArea area;
- JTextField field;
- JButton button;
- static PrintStream writer;
- public Server3(){
- this.setTitle("服务器");
- this.setSize(400,500);
- area = new JTextArea(25,30);
- area.setEditable(false);
- field = new JTextField(20);
- button = new JButton("提交");
- JPanel panel = new JPanel();
- JScrollPane sp = new JScrollPane(area);
- this.add(sp,BorderLayout.CENTER);
- panel.add(field);
- panel.add(button);
- this.add(panel,BorderLayout.SOUTH);
- this.setVisible(true);
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- button.addActionListener(new ActionListener(){
- @Override
- public void actionPerformed(ActionEvent e) {
- String text = field.getText();
- writer.println(text);
- area.append("我:"+text+"\n");
- field.setText("");
- }
- });
- }
- public static void main(String[] args) throws Exception {
- Server3 s = new Server3();
- ServerSocket server = new ServerSocket(8899);
- System.out.println("开始监听...");
- Socket socket = server.accept();
- InetAddress address = socket.getInetAddress();
- String name = address.getLocalHost().getHostName();
- System.out.println(name+"已连接");
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- writer = new PrintStream(socket.getOutputStream(), true);
- while (true) {
- String line = null;
- line = reader.readLine();
- if (line != null) {
- area.append("客户端:"+line+"\n");
- }
- }
- }
- }
Client端
- package org.xiazdong.client;
- import java.awt.BorderLayout;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.io.PrintStream;
- import java.io.PrintWriter;
- import java.net.Socket;
- import javax.swing.JButton;
- import javax.swing.JFrame;
- import javax.swing.JPanel;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- import javax.swing.JTextField;
- public class Client3 extends JFrame{
- static JTextArea area;
- JTextField field;
- JButton button;
- static PrintWriter writer;
- public Client3(){
- this.setTitle("客户端");
- this.setSize(400,500);
- area = new JTextArea(25,30);
- area.setEditable(false);
- field = new JTextField(20);
- button = new JButton("提交");
- JScrollPane sp = new JScrollPane(area);
- JPanel panel = new JPanel();
- this.add(sp,BorderLayout.CENTER);
- panel.add(field);
- panel.add(button);
- this.add(panel,BorderLayout.SOUTH);
- this.setVisible(true);
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- button.addActionListener(new ActionListener(){
- @Override
- public void actionPerformed(ActionEvent e) {
- String text = field.getText();
- writer.println(text);
- area.append("我:"+text+"\n");
- field.setText("");
- }
- });
- }
- public static void main(String[] args) throws Exception{
- Client3 c = new Client3();
- Socket socket = new Socket("127.0.0.1",8899);
- OutputStream out = socket.getOutputStream();
- BufferedReader reader1 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- writer = new PrintWriter(out,true);
- System.out.println("已经成功和服务器连接...");
- while(true){
- String line = reader1.readLine();
- area.append("服务器:"+line+"\n");
- }
- }
- }
PrintWriter的autoflush
如果PrintWriter writer = new PrintWriter(out,true);
则调用println()、printf()、format()函数时会自动刷新,其他函数都不会,比如write()、print()函数时不会自动刷新
网络编程常见异常
第1个异常是java.net.BindException:Address already in use: JVM_Bind。该异常发生在服务器端进行new ServerSocket(port)(port是一个0,65536的整型值)操作时。异常的原因是以为与port一样的一个端口已经被启动,并进行监听。此时用netstat –an命令,可以看到一个Listending状态的端口。只需要找一个没有被占用的端口就能解决这个问题。
第2个异常是java.net.ConnectException: Connection refused: connect。该异常发生在客户端进行new Socket(ip, port)操作时,该异常发生的原因是或者具有ip地址的机器不能找到(也就是说从当前机器不存在到指定ip路由),或者是该ip存在,但找不到指定的端口进行监听。出现该问题,首先检查客户端的ip和port是否写错了,如果正确则从客户端ping一下服务器看是否能ping通,如果能ping通(服务服务器端把ping禁掉则需要另外的办法),则看在服务器端的监听指定端口的程序是否启动,这个肯定能解决这个问题。
第3个异常是java.net.SocketException: Socket is closed,该异常在客户端和服务器均可能发生。异常的原因是己方主动关闭了连接后(调用了Socket的close方法)再对网络连接进行读写操作。
第4个异常是java.net.SocketException: (Connection reset或者Connect reset by peer:Socket write error)。该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个,第一个就是如果一端的Socket被关闭(或主动关闭或者因为异常退出而引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。简单的说就是在连接断开后的读和写操作引起的。
第5个异常是java.net.SocketException: Broken pipe。该异常在客户端和服务器均有可能发生。在第4个异常的第一种情况中(也就是抛出SocketExcepton:Connect reset by peer:Socket write error后),如果再继续写数据则抛出该异常。前两个异常的解决方法是首先确保程序退出前关闭所有的网络连接,其次是要检测对方的关闭连接操作,发现对方关闭连接后自己也要关闭该连接。
实例展示,使用socket上传文件
服务端:
- package scoket.file.server;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.net.*;;
- public class FileServer {
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- try {
- ServerSocket server = new ServerSocket(8888);
- Socket socket = new Socket();
- while(true){
- socket = server.accept();
- InputStream is = socket.getInputStream();
- OutputStream os = socket.getOutputStream();
- byte[] b = new byte[1024];
- //1、得到文件名
- int a = is.read(b);
- String filename = new String(b, 0, a);
- System.out.println("接受到的文件名为:"+filename);
- String houzhui = filename.substring(filename.indexOf("."), filename.length());
- String rand = String.valueOf((int) (Math.random() * 100000));
- filename = rand+houzhui;
- System.out.println("新生成的文件名为:"+filename);
- FileOutputStream fos = new FileOutputStream("f:\\"+filename);
- int length = 0;
- while((length=is.read(b))!=-1){
- //2、把socket输入流写到文件输出流中去
- fos.write(b, 0, length);
- }
- //fos.flush();
- fos.close();
- os.flush();
- os.close();
- is.close();
- socket.close();
- }
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
客户端
- package scoket.file.client;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.net.Socket;
- import java.net.UnknownHostException;
- public class FileCilent {
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- try {
- Socket client = new Socket("192.168.3.28", 8888);
- InputStream is = client.getInputStream();
- OutputStream os = client.getOutputStream();
- String filepath="e:\\MyServer.java";
- File file = new File(filepath);
- String filename = file.getName();
- System.out.println("send's file name:"+filename);
- //1、发送文件名
- os.write(filename.getBytes());
- FileInputStream fis = new FileInputStream(file);
- byte[] b = new byte[1024];
- int length = 0;
- while((length=fis.read(b))!=-1){
- //2、把文件写入socket输出流
- os.write(b, 0, length);
- }
- os.close();
- fis.close();
- is.close();
- System.out.println("send over");
- } catch (UnknownHostException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
Java 网络编程之 (基于 TCP 的远端文件传输)
服务器端:
- import java.io.BufferedInputStream;
- import java.io.BufferedOutputStream;
- import java.io.FileOutputStream;
- import java.net.ServerSocket;
- import java.net.Socket;
- //使用TCP协议写一个可以上传文件的服务器和客户端。
- public class UpLoad {
- public static void main(String[] args) throws Exception {
- ServerSocket ss = new ServerSocket(3000);
- Socket socket = ss.accept();
- new Thread(new Receive(socket)) {
- }.start();
- }
- }
- class Receive implements Runnable {
- private Socket socket;
- public Receive(Socket socket) {
- this.socket = socket;
- }
- public void run() {
- try {
- BufferedInputStream bis = new BufferedInputStream(socket
- .getInputStream());
- byte[] bFileName = new byte[255];
- int len = bis.read(bFileName);
- String fileName = new String(bFileName, 0, len).trim();
- byte[] bytes = new byte[1024];
- FileOutputStream fos = new FileOutputStream("d://" + fileName);
- BufferedOutputStream bos = new BufferedOutputStream(fos);
- len = 0;
- while ((len = bis.read(bytes)) != -1) {
- bos.write(bytes, 0, len);
- }
- bos.close();
- fos.close();
- bis.close();
- socket.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
客户端:
- import java.io.BufferedOutputStream;
- public class UpLoadClient {
- public static void main(String[] args) throws Exception {
- BufferedReader br = null;
- String path = null;
- String fileName = null;
- String ip = null;
- while (true) {
- System.out.println("请输入文件路径:");
- br = new BufferedReader(new InputStreamReader(System.in));
- path = br.readLine();
- File file = new File(path);
- fileName = path.substring(path.lastIndexOf("//") + 1);
- if (file.isDirectory() || !file.exists())
- System.out.println("路径不正确!");
- else
- break;
- }
- System.out.println("请输入服务器地址:");
- br = new BufferedReader(new InputStreamReader(System.in));
- ip = br.readLine();
- System.out.println("确认上传:" + path + "文件吗(y/n)?");
- br = new BufferedReader(new InputStreamReader(System.in));
- String result = br.readLine();
- if ("n".equalsIgnoreCase(result)) {
- br.close();
- return;
- }
- Socket socket = new Socket(ip, 3000);
- FileInputStream fs = new FileInputStream(path);
- byte[] bytes = new byte[1024];
- BufferedOutputStream bos = new BufferedOutputStream(socket
- .getOutputStream());
- while (true) {
- if (fileName.getBytes().length < 255)
- fileName += "/u0000";
- else
- break;
- }
- bos.write(fileName.getBytes());
- bos.flush();
- int len = 0;
- while ((len = fs.read(bytes)) != -1) {
- bos.write(bytes, 0, len);
- }
- bos.close();
- fs.close();
- br.close();
- socket.close();
- System.out.println("文件上传完毕!");
- }
- }
Java 网络编程之 (TCP传递对象)
- import java.io.ObjectInputStream;
- import java.net.ServerSocket;
- import java.net.Socket;
- public class TcpService {
- public static void main(String args[]) throws Exception {
- ServerSocket ss = new ServerSocket(3000);
- Socket socket = ss.accept();
- ObjectInputStream oos = new ObjectInputStream(socket.getInputStream());
- Student stu = (Student) oos.readObject();
- oos.close();
- ss.close();
- System.out.println(stu.getName() + "," + stu.getAge() + ","
- + stu.getSex());
- }
- }
- import java.io.ObjectOutputStream;
- import java.net.Socket;
- public class TcpClients {
- public static void main(String[] args) throws Exception {
- Student stu1 = new Student("张三", 18, "男");
- Socket socket = new Socket("127.0.0.1", 3000);
- ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
- oos.writeObject(stu1);
- oos.close();
- socket.close();
- }
- }
- import java.io.Serializable;
- public class Student implements Serializable {
- public Student(String name, int age, String sex) {
- this.setName(name);
- this.setAge(age);
- this.setSex(sex);
- }
- private String name;
- private int age;
- private String sex;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getSex() {
- return sex;
- }
- public void setSex(String sex) {
- this.sex = sex;
- }
- }
运行结果:
Java 网络编程之 (TCP服务器架构)
实现 一台服务器对多个客户机的响应
服务器:
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.io.OutputStreamWriter;
- import java.net.ServerSocket;
- import java.net.Socket;
- public class TcpServer {
- public static void main(String[] args) throws Exception {
- if (args.length == 0) {
- System.out.println("未输入端口号,格式:java TcoServer 端口号");
- return;
- }
- System.out.println("服务器已开启,等待客户机响应。。。");
- ServerSocket serverSocket = new ServerSocket(Integer.parseInt(args[0]));
- while (true) {
- Socket socket = serverSocket.accept();
- System.out.println(socket.getInetAddress().getHostAddress() + ":"
- + socket.getPort() + " 已连接!");
- new Thread(new Send(socket)) {
- }.start();
- }
- }
- }
- // 发送数据
- class Send implements Runnable {
- public Send(Socket socket) {
- this.socket = socket;
- }
- private Socket socket;
- private InputStream in;
- private OutputStream out;
- private BufferedReader br;
- private BufferedWriter bw;
- public void run() {
- try {
- in = socket.getInputStream();
- out = socket.getOutputStream();
- br = new BufferedReader(new InputStreamReader(in));
- bw = new BufferedWriter(new OutputStreamWriter(out));
- while (true) {
- String msg = br.readLine();
- if ("exit".equals(msg)) {
- System.out.println(socket.getInetAddress().getHostAddress()
- + ":" + socket.getPort() + "已断开!");
- break;
- }
- StringBuffer sb = new StringBuffer(msg);
- msg = sb.reverse().toString();
- bw.write("服务器响应:-->" + msg + "/r/n");
- bw.flush();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- try {
- br.close();
- bw.close();
- socket.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
客户端:
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.io.OutputStreamWriter;
- import java.net.Socket;
- public class TcpClient {
- public static void main(String[] args) throws Exception {
- if (args.length < 2) {
- System.out.println("请输入端口号以及IP地址,格式:java TcoClient IP 端口号");
- return;
- }
- Socket socket = new Socket(args[0], Integer.parseInt(args[1]));
- InputStream in = socket.getInputStream();
- OutputStream out = socket.getOutputStream();
- BufferedReader br = new BufferedReader(new InputStreamReader(in));
- BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
- BufferedReader console = new BufferedReader(new InputStreamReader(
- System.in));
- while (true) {
- System.out.println("请输入:");
- String msg = console.readLine();
- bw.write(msg + "/r/n");
- bw.flush();
- if ("exit".equalsIgnoreCase(msg))
- break;
- System.out.println(br.readLine() + "/n");
- }
- bw.close();
- br.close();
- socket.close();
- }
- }
运行效果:
Java网络编程之计息URL
在 http1.1 协议中,都是基于请求和响应的模式,又是基于 TCP 来进行数据交换的。在 http1.1 中,请求方向远端服务器发送一个请求,请求的内容包括 http 头部信息和可选的内容。然后服务器接到请求,将数据通过 TCP 以流的形式传递到客户端,然后浏览器获取 htm 文档后将其转换为我们所看到的页面。
基于这个原理,我想到了我们是否可以自己编写一个客户端,然后将请求以 socket 输出流的形式写过去呢?结果实验证明我陈功啦,哈哈,这个思路和源码我绝对没有参照任何人,也绝没有百度谷歌。我好像感觉我找到了学习 Java 正确思路……小激动一下!
晒晒源码:
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- import java.io.PrintWriter;
- import java.net.Socket;
- public class GetHtml {
- public static void main(String[] args) throws Exception {
- Socket socket = new Socket("www.it315.org", 80);
- PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket
- .getOutputStream()));
- pw.println("GET //contact.htm HTTP//1.1");
- pw.println("Host:");
- pw.println("");
- pw.flush();
- BufferedReader br = new BufferedReader(new InputStreamReader(socket
- .getInputStream()));
- char[] ch = new char[1024];
- String receive = null;
- StringBuffer sb = new StringBuffer();
- while (br.read(ch) != -1) {
- sb.append(ch);
- }
- receive = sb.toString();
- br.close();
- pw.close();
- socket.close();
- System.out.println(receive);
- }
- }
UDP网络聊天程序
基于 UDP 的网络聊天
- 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.SocketException;
- public class Chart {
- private static final int PORT = 2999; // 程序占用端口号
- public static void main(String[] args) throws Exception {
- receiveMsg();
- sendMsg();
- }
- private static void sendMsg() throws Exception {
- BufferedReader br = null;
- String receiveIp = null; // 接收方ip
- String message = null; // 消息
- byte[] bytes = null;
- while (true) {
- // 输入接收方地址
- System.out.println("请输入接收方的 IP 地址:");
- br = new BufferedReader(new InputStreamReader(System.in));
- receiveIp = br.readLine();
- // 输入消息内容
- System.out.println("请输入消息内容:");
- br = new BufferedReader(new InputStreamReader(System.in));
- message = br.readLine();
- bytes = message.getBytes();
- // 发送消息
- DatagramSocket ds = new DatagramSocket();
- DatagramPacket dp = new DatagramPacket(bytes, bytes.length,
- InetAddress.getByName(receiveIp), PORT);
- ds.send(dp);
- }
- }
- private static void receiveMsg() throws SocketException {
- new Thread() {
- String message = null;
- String sendIp = null;
- byte[] bytes = null;
- DatagramSocket ds = null;
- DatagramPacket dp = null;
- public void run() {
- while (true) {
- bytes = new byte[1024];
- try {
- ds = new DatagramSocket(PORT);
- } catch (SocketException e1) {
- e1.printStackTrace();
- }
- dp = new DatagramPacket(bytes, 1024);
- try {
- ds.receive(dp);
- } catch (IOException e) {
- e.printStackTrace();
- }
- message = new String(bytes, 0, bytes.length).trim();
- sendIp = dp.getAddress().getHostAddress();
- System.out.println("(" + sendIp + " 发来消息):" + message);
- ds.close();
- }
- }
- }.start();
- }
- }
运行结果:
可以实现外网聊天呦 ^_^ 有点小成就感
Java中的网络编程总结
说明:网络编程的核心还是通过流来进行数据传输
一,InetAddress类
InetAddress类表示互联网协议(IP)地址.
本类没有构造函数来直接实例化对象,但是可以通过该类的静态方法获取本类对象。同时我们也可以通过该对象获取其IP地址与主机名。
注:IP地址每段数值范围在0-255之间,占一个字节。
端口:有效端口值为0-65535,其中0-1024为系统使用或作为紫铜保留端口。
示例1:获取本地主机IP信息
- import java.net.*;
- public class Test {
- public static void main(String[] args) throws Exception{
- //获取本地主机IP对象
- InetAddress i=InetAddress.getLocalHost();
- //获取本机IP地址
- System.out.println(i.getHostAddress());
- //获取本机名
- System.out.println(i.getHostName());
- }
- }
运行结果;
- 192.168.1.106
- huaer-PC
示例2:获取指定主机名的IP信息
- import java.net.*;
- public class Test {
- public static void main(String[] args) throws Exception{
- //根据主机名获取IP对象
- InetAddress i=InetAddress.getByName("www.baidu.com");
- //获取IP地址
- System.out.println(i.getHostAddress());
- //获取主机名
- System.out.println(i.getHostName());
- }
- }
运行结果:
- 61.135.169.105
- www.baidu.com
二,UPD协议数据传输
UPD协议特点:
传输速度快,容易丢包,是不可靠协议。
面向无连接(不需要连接,发送方只负责发送数据,不负责数据一定被接收)
数据被封包,每个数据包的大小限制子64K以内。
UPD的运用:
UPD数据发送:
思路:
1.建立UPDSocket服务,
2.提供数据,并将数据封装到数据包中,
3.通过Socket服务的发送功能,将数据发送出去,
4.关闭资源。
- import java.net.*;
- public class Test {
- public static void main(String[] args) throws Exception{
- //建立UPDSocket数据服务
- DatagramSocket ds=new DatagramSocket();
- //获取要发送的数据
- byte[] data="UDP数据传输".getBytes();
- //将数据封装到数据包中
- DatagramPacket dp
- =new DatagramPacket(data,data.length,InetAddress.getByName("192.168.1.106"),10000);
- //发送数据
- ds.send(dp);
- //关闭资源
- ds.close();
- }
- }
UPD数据接收:
思路:
1.定义UPDSocket服务,通常会监听一个端口,即这个程序思念高一数字标识,便于明确哪些数据被该程序接收,如不指定端口,系统会默认自动分配。
2.定义一个数据包,用于要存储接收到的数据。并且数据包对象有更多功能可以用来提取数据中的不同数据信息。
3.通过Socket服务的receive方法将收到的数据存入已经定义好的数据包中。
4.通过数据包对象的特有功能处理数据,如:将这些不同的数据取出。
5.关闭资源。
- import java.net.*;
- public class Test {
- public static void main(String[] args) throws Exception{
- //建立UPDSocket数据服务
- DatagramSocket ds=new DatagramSocket(10000);
- //获取要发送的数据
- byte[] buf=new byte[1024];
- //将数据封装到数据包中
- DatagramPacket dp
- =new DatagramPacket(buf,buf.length);
- //发送数据
- ds.receive(dp);
- //获取数据包中发送端主机IP对象
- InetAddress i=dp.getAddress();
- //获取发送端IP地址
- String ip=i.getHostAddress();
- //获取端口
- int port=dp.getPort();
- //获取数据长度
- int len=dp.getLength();
- //获取数据包中的数据
- byte[] data=dp.getData();
- //关闭资源
- ds.close();
- }
示例3:UPD通信
获取键盘录入的数据,并用发送端发送至接收端。
- import java.io.*;
- import java.net.*;
- //UDP发送端
- class UDPSend{
- public static void main(String[] args) throws Exception{
- //创建Socket服务
- DatagramSocket ds = new DatagramSocket();
- //创建键盘输入流
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- //读取键盘录入数据
- String line = null;
- while((line=br.readLine())!=null){
- //如果遇到"byebye",就跳出循环
- if("byebye".equals(line))
- break;
- //定义缓冲区
- byte[] buf = line.getBytes();
- //创建数据包,
- DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.106"),10001);
- //发送数据包
- ds.send(dp);
- }
- ds.close();//关闭资源
- }
- }
- //UDP接收端
- class UDPRece2{
- public static void main(String[] args) throws Exception{
- //创建Socket服务
- DatagramSocket ds = new DatagramSocket(10001);
- while(true){
- //创建缓冲区
- byte[] buf = new byte[1024];
- //创建数据包,
- DatagramPacket dp = new DatagramPacket(buf,buf.length);
- //用数据报包接收数据
- ds.receive(dp);
- //获取数据包中的信息IP和数据
- String ip = dp.getAddress().getHostAddress();
- String data = new String(dp.getData(),0,dp.getLength());
- System.out.println(ip+":"+data);
- }
- }
- }
示例4:
建立一个简单的聊天程序,为了持续发送和接收数据,并且发送端和接收端需要同时进行,所以需要用到多线程功能。
- import java.io.*;
- import java.net.*;
- //建立发送端
- class SendSocket implements Runnable{
- private DatagramSocket ds;
- //在构造函数中加入Socket服务
- SendSocket(DatagramSocket ds){
- this.ds = ds;
- }
- //重写发送端线程run方法
- public void run(){
- try{
- //创建键盘录入的输入流
- BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
- String line = null;
- //读取键盘沮洳数据
- while((line=bufr.readLine())!=null){
- //设置跳出循环标记
- if("886".equals(line))
- break;
- byte[] buf = line.getBytes();
- //将数据封装到数据包中
- DatagramPacket dp =
- new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.106"),10001);
- //发送数据包
- ds.send(dp);
- }
- //关闭资源
- ds.close();
- }
- catch (Exception e){
- throw new RuntimeException("发送失败");
- }
- }
- }
- //定义接收端
- class ReceSocket implements Runnable{
- private DatagramSocket ds;
- //在构造函数中加入Socket服务
- public ReceSocket(DatagramSocket ds){
- this.ds = ds;
- }
- //重写接收端线程的run方法
- public void run(){
- try{
- while(true){
- byte[] by = new byte[1024];
- //创建数据包
- DatagramPacket dp = new DatagramPacket(by,by.length);
- //接收数据
- ds.receive(dp);
- //获取数据包中的ip地址
- String ip = dp.getAddress().getHostAddress();
- //获取数据包中数据
- String data = new String(dp.getData(),0,dp.getLength());
- System.out.println(ip+":"+data);
- //ds.close();为了持续接收数据,一般接收端不关闭
- }
- }
- catch (Exception e){
- throw new RuntimeException("接收失败");
- }
- }
- }
- class SocketDemo {
- public static void main(String[] args) throws Exception{
- //创建发送端和接收端的对象
- DatagramSocket sendSocket = new DatagramSocket();
- DatagramSocket receiveSocket = new DatagramSocket(10001);
- //创建两个线程,
- Thread tsend=new Thread(new SendSocket(sendSocket));
- Thread treceive=new Thread(new SendSocket(receiveSocket));
- //开始线程
- tsend.start();
- treceive.start();
- }
- }
三,TCP协议数据传输
TCP协议特点:
1.面向连接,建立连接,形成传输数据的通道。
2.在连接中进行大量数据传输。
3.通过三次握手(判断)完成连接。
4.必须建立连接,效率稍低。
示例1:建立简易TCP客户端和服务端
- import java.net.*;
- import java.io.*;
- class TestSocket{
- //创建客户端
- Socket s=new Socket("192.168.1.106",10086);
- //获取客户端的输出流
- OutputStream os=s.getOutputStream();
- //通过输出流发送数据
- os.write("发送客户端信息".getBytes());
- //关闭客户端
- s.close();
- }
- class TestServerSocket{
- //建立服务端
- ServerSocket ss=new ServerSocket(10086);
- //获取连接过来的客户端
- Socket s=ss.accept();
- //获取客户端的输入流
- InputStream in=s.getInputStream();
- //创建缓冲区
- byte[] buf=new byte[1024];
- //接收客户端发送的数据
- int len=in.read(buf);
- System.out.println(new String(buf,0,len));
- //获取客户端IP
- String ip=s.getInetAddress().getHostAddress();
- System.out.println("ip="+ip);
- //关闭资源
- s.close();
- ss.close();
- }
示例2:
由客户端向服务端发送文件,若发送成功,服务端提示发送成功。
- import java.io.*;
- import java.net.*;
- //客户端
- class TextSocket{
- public static void main(String[] args) throws Exception {
- Socket s = new Socket("192.168.1.106",10010);
- //读取文件
- BufferedReader br = new BufferedReader(new FileReader("123.txt"));
- //创建客户端输出路
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- //发送信息
- String line = null;
- while((line=br.readLine())!=null){
- out.println(line);
- }
- //关闭客户端输出流
- s.shutdownOutput();
- //建立客户端输入流
- BufferedReader bufr
- = new BufferedReader(new InputStreamReader(s.getInputStream()));
- //接收信息
- String str = bufr.readLine();
- System.out.println(str);
- //关闭资源
- br.close();
- s.close();
- }
- }
- //服务端
- class TextServerSocket{
- public static void main(String[] args) throws Exception {
- //创建服务端
- ServerSocket ss = new ServerSocket(10010);
- //获取连接至服务端的客户端
- Socket s = ss.accept();
- //获取客户端主机信息
- String ip = s.getInetAddress().getHostAddress();
- System.out.println("来自IP:"+ip);
- BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
- //创建用于接收文件的目的
- PrintWriter out = new PrintWriter(new FileWriter("333.txt"),true);
- String line = null;
- //接收客户端发送至服务端的信息
- while((line=bufIn.readLine())!=null){
- if("over".equals(line))
- break;
- out.println(line);
- }
- PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
- //给客户端反馈信息
- pw.println("上传成功");
- //关闭资源
- out.close();
- s.close();
- ss.close();
- }
四,URl类
该类为封装网页地址所指向资源的类,通过本类可以获取该网站的各种信息。
- import java.net.*;
- public class Test {
- public static void main(String[] args) throws Exception{
- //根据网址获取URL对象
- URL url=new URL("http://www.baidu.com");
- //获取对象各种信息
- sop("URL的协议:"+url.getProtocol());
- sop("URL的IP地址:"+url.getHost());
- sop("URL端口号:"+url.getPort());
- sop("URL文件路径:"+url.getPath());
- sop("URL文件名称:"+url.getFile());
- sop("URL查询部分:"+url.getQuery());
- }
- public static void sop(String s){
- System.out.println(s);
- }
- }
运行结果:
- URL的IP地址:www.baidu.com
- URL端口号:-1
- URL文件路径:
- URL文件名称:
- URL查询部分:null