21.网络编程 (第二十三天)

一   概述

数据通信的原理其实就是数据传输,之前学的都是单机版的传输,

现在连上网络的数据传输,就是网络通信了。

那么,如果本机需要跟网络另一台主机进行通信,就需要那台主机。

先看一下关于网络编程中需要了解的概念:

1、网络模型

  • OSI参考模型

  • TCP/IP参考模型

2、网络通讯要素:IP地址、端口号、传输协议(通讯规则)。

3、网络通讯过程

      (1)、通过IP找到对方

       (2)、数据要发送到对方指定的应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用数字进行了标识。

                为了方便称呼这个数字,就叫做端口。(注意是逻辑上的端口,不是物理上的)。

                在360的流量条,那个网络连接,就可以看到应用程序对应的本地端口。 0~65535,0~1024一般操作系统用了。

       (3)、定义通讯规则,称为协议。国际组织定义了通用的协议,即TCP/IP。局域网、广域网都可以使用。

                另外,可以在 网络中心 - 本地连接 自己安装协议,两台机只要协议一样就能通信了。

                      所以有些机构有自己的通信协议,外部就很难入侵它的网络。

二   概述2

  • 端口数字范围:0-65535
  • 系统程序一般端口范围:0-1024
  • 本机默认IP地址:127.0.0.1
  • 四段的IP地址也不够用了,IPv6出现。IPv6地址除了包含数字,还包含字母(与IPv4的区别)。
  • 凡是网络应用程序,都必须有数字标识,而且可以更改。

三   网络模型

在网络传输过程中,针对不同程序、不同功能,按照层次对其进行划分。每个层次都有自己所做的事情。

计算机网络是指由通信线路互相连接的许多自主工作的计算机构成的集合体,各个部件之间以何种规则进行通信,就是网络模型研究的问题。

网络模型一般是指OSI七层参考模型和TCP/IP四层参考模型。这两个模型在网络中应用最为广泛。

OSI是Open System Interconnect的缩写,即ISO开放系统互连参考模型,一般都叫OSI参考模型。

OSI数据传输过程:从应用层(顶层),一直向下,数据封包。

然后发送到对方那里后,一直向上进行数据拆包,最后拆完数据要看走哪个端口。

将每一层的特有信息加入,称为“数据封包”。解析数据,每层都在读自己能够识别的数据,也叫数据拆包。

我们做软件主要负责的是传输层和网际层。网际层常见协议:IP、传输层常见协议:TCP/UDP。应用层协议有HTTP和FTP。

四   IP地址

上面说过,网络通讯要素有IP地址、端口号、传输协议。那如何能够在程序中体现这三个要素呢?

只要把他们变成对象并且起个名字能够很方便地体现了。现在来认识一下:

1、PPT内容(网络通讯要素)

  • IP地址:网络中的设备标识;不易记忆,可用主机名;本地回环地址:127.0.0.1 主机名:localhost

  • 端口号:用于标识进程的逻辑地址,不同进程的标识;有效端口:0~65535,其中0~1024系统使用或保留端口

  • 传输协议:通讯的规则; 常见协议:TCP/UDP

2、具体说明一下

网址本质上是IP地址,为了方便记忆,将数字变成好记的字母,称为主机名,例如www.baidu.com,即网址。

127.0.0.1默认名:localhost,在cmd小黑窗ping localhost和ping 127.0.0.1的结果是一样的。

InetAddress类表示互联网协议的IP地址,通过静态的getLocalHost()方法获得InetAddress对象,再通过getHostAddress(),获得本机IP地址;getHostName()获得本机名。

获取任意一台主机的IP地址对象:通过主机名获得IP地址对象,static InetAddress getByName(String host)。

如果IP地址和主机名没有在网络上,即没有构成映射关系,本机名仍然是IP地址。

以获取IP地址为主,因为本机名还需要解析。主机名可以是IP地址也可以是网址。

因为可能有多台主机,所以返回的IP地址不唯一,通过getAllByName(String host),返回IP地址构成的数组。

常用方法:

  • static InetAddress getLocalHost() 返回本地主机。

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

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

  • static InetAddress getByName(String host) 在给定主机名的情况下确定主机的 IP 地址。

3、示例代码

public class Demo {

	public static void main(String[] args) throws UnknownHostException {
		InetAddress i = InetAddress.getLocalHost();
		System.out.println(i.toString());
	}
}
import java.net.*;

public class Demo {

	public static void main(String[] args) throws UnknownHostException {

		InetAddress i = InetAddress.getLocalHost();// static InetAddress getLocalHost() 返回本地主机。

		System.out.println(i.toString());
		System.out.println("Address:" + i.getHostAddress());// getHostAddress() 返回 IP地址字符串(以文本表现形式)。
		System.out.println("Name:" + i.getHostName()); // getHostName() 获取此 IP 地址的主机名。
														// 
		System.out.println("======================================");

		InetAddress ia = InetAddress.getByName("CGJ"); //注意这里改成你当前操作计算机的名字
		System.out.println("Address:" + ia.getHostAddress());// 最终还是以地址为主最方便,名称为主还需要一个解析过程。
		System.out.println("Name:" + ia.getHostName());
		System.out.println("======================================");

		InetAddress[] baidu = InetAddress.getAllByName("www.baidu.com");
		for (int ii = 0; ii < baidu.length; ii++) {
			System.out.println(baidu[ii]);
		}
	}
}

输出:

CGJ/19.120.34.25
Address:19.120.34.25
Name:CGJ
======================================
Address:19.120.34.25
Name:CGJ
======================================
www.baidu.com/180.97.33.107
www.baidu.com/180.97.33.108

上面的程序,是拿到任意一台主机IP地址最常见的操作。

注意:

  • 网络编程要import java.net.*包,
  • InetAddress 类没有构造方法。
  • getLocalHos t会抛  UnknownHostException(未知主机异常) 异常。
  • 端口号就是个数字,所以没有必要封装对象。

五   TCP和UDP

UDP(面向无连接 ,举例:寄普通邮件,有限制,数据封包,一次不超过64K,速度快,不可靠)

  • 将数据及源和目的封装成数据包中,不需要建立连接

  • 每个数据包的大小限制在64K内

  • 因无连接,是不可靠协议

  • 不需要建立连接,速度快

由于不需要建立连接。也就是说,对方是否在线不重要。

相当于你寄普通的非挂号信,对方有没有收到你也不知道,明确对方的地址和端口后打成包发过去就是了。

具体例子例如:QQ聊天、QQ的离线发送、网络视频会议、桌面共享。

TCP(面向连接(对方必须在)、举例:打电话、下载)

  • 建立连接,形成传输数据的通道

  • 在连接中进行大数据量传输

  • 通过三次握手完成连接,是可靠协议

  • 必须建立连接,效率会稍低

需要建立连接,形成传输数据的通道。也就是说,通讯时对方必须在;单方面断开的话,数据停止传输。

在连接中进行大数据传输,不需要封装数据包。每次都必须建立连接,消耗资源,效率比较低。

三次握手:“在吗?”“在。”“我知道了。”通过三次握手,建立传输通道,确认双方是否都在线。

六   Socket

讲Java的网络编程,其实就是讲Socket的编程。

  • Socket就是为网络服务提供的一种机制

  • 通信的两端都有Socket

  • 网络通信其实就是Socket间的通信

  • 数据在两个Socket间通过IO传输

Socket是为网络服务提供的一种机制,意思是“插座”,

可以理解为港口,上下货的地方,有了这个上下货的地方,数据才能传输(通信两端的端点)。

网络通信的实质就是Socket间的通信,两端都有Socket。

数据在2个Socket之间通过IO传输。想要通信的话,必须要有Socket。网卡、网线是物理传输介质。

玩Socket关键是记住流程,代码可以查文档。

七   UDP - 发送端

每个协议都有自己不同的建立Socket的方式,即传输数据的方式。

UDP建立Socket的方式对应2个对象,即DatagramSocketDatagramPacket。依据数据的方向,分为发送端和接受端。

  • DatagramSocket:此类表示用来发送和接收数据报包的Socket。2个端点都需要有这个对象。通过receive()和send()接受和发送数据。
  • DatagramPacket:此类表示数据报包。用于无连接的包投递服务。既可以接受、提取数据,也可以封装数据。
  •                 包里面有数据、源地址、端口和目的地址、端口,因为数据比较多,所以封装成对象操作比较方便。
  •                 发送数据包一定要带地址,即InetAddress对象。通过构造函数中参数的不同,分为接受型数据包和发送型数据包。
  •                 “DatagramPacket凡是带地址的,都是用来发送数据包的。”

玩Socket关键是记住流程,必须要掌握的Socket工作流程:

发送端思路:1.建立Socket服务。2.提供数据被封装到数据包中。3.通过Socket的发送功能发送。4.关闭资源。
 

import java.io.IOException;
import java.net.*;

/*
 需求:通过UDP传输方式,将一段文字数据发送出去。
 思路:
 1.建立UDP Socket服务(找邮局)
 2.提供数据,并将数据封装到数据包中。
 3.通过Socket服务的发送功能,将数据包发出去。
 4.关闭资源*/

public class Demo {
	public static void main(String[] args) throws IOException {
		// 1.创建UDP服务,通过DatagramSocket对象
		DatagramSocket ds = new DatagramSocket();
		// 2.确定数据,并封装成数据包
		byte[] buf = "udp ge men lai le".getBytes();
		DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("<span style="font-family:SimSun;">127.0.0.1</span>"), 100<span style="font-family:SimSun;">86</span>);
		// 3.通过Socket服务将已有的数据包发送出去,通过send方法
		ds.send(dp);
		// 4.关闭资源
		ds.close();
	}
}

具体步骤见上面程序:
(1)  创建UDP服务,即创建DatagramSocket对象。例如,DatagramSocket ds = newDategramSocket();。
     发送端可以指定端口,也可以使用系统随机分配的端口。端口如果没有被释放,会存在内存中。
(2) 确定数据,并将数据封装成包。格式:DatagramPacket(byte[] buf,int length, InetAddress address,int port)。
     例如,DatagramPacket dp = newDatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10086);
(3) 通过Socket中的send()方法,将数据发送出去。ds.send(dp);
(4) 关闭资源。ds.close();
(5) 处理异常。

发送端与接收端是两个独立运行的程序,应该放在2个文件中。
 

八   UDP - 接收端

上面发送的数据不知道到哪去了,这是因为接收端没开,要想进行通信,需要两个端点才行,那么现在来说接收端。

这么多不同的数据内容,想提取出来,可以把接收到的字节全都存到数据包DatagramPacket里面,因为里面有对应的功能可以提取里面的信息。

思路:

(1) 定义UDP的Socket服务。通常会监听一个端口,就是给这个网络程序定义一个数字标识,也就是门牌号。

     如果不定义,系统会随机指定标识,无法接受数据。BindException,绑定异常。

     一个Socket服务只能使用一个端口,不能侵占别人的端口,所以Socket建立一次就足够了。程序被关闭,端口被释放,这个端口才能被再次使用。

(2) 定义一个数据包,为了存储接受到的字节数据。数据包对象中有更多功能可以提取字节数据中不同的数据信息。

(3) 通过Socket服务中的receive()方法,将接受到的数据存入已经定义好的数据包中。

(4) 通过数据包对象的特有功能,将不同的信息取出,打印在控制台上。

(5) 关闭资源。

示例代码:

import java.io.IOException;
import java.net.*;

class UDPRece {

	public static void main(String[] args) throws IOException {
		// 1.创建UDP Socket,建立端点。
		DatagramSocket ds = new DatagramSocket(10086);
		// 2.定义数据包,用于存储数据
		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf, buf.length);
		// 3.通过服务的rece方法将收到数据存入数据包中
		ds.receive(dp);
		// 4.通过数据包的方法获取其中的数据
		String ip = dp.getAddress().getHostAddress();
		String data = new String(dp.getData(), 0, dp.getLength());

		int port = dp.getPort();
		System.out.println(ip + "::" + data + "::" + port);
		// 5.关闭资源
		ds.close();
	}
}

具体步骤如下:

(1) 创建Socket服务,建立端点。例如,DatagramSocket ds = newDatagramSocket(10086);加上数字标识,便于接受数据。

(2) 定义数据包,储存数据,例如,DatagramPacket dp = newDatagramPacket(buf,buf.length);字节数组最大可以定义到1024*64。

(3) 通过receive()方法将接受到的数据存入数据包中。例如,ds.receive(dp);。该方法为阻塞式方法,如果没有接受到数据,会自动等待。

(4) 通过数据包中的方法获得相应的数据,例如,String data = newString(dp.getData(),0,dp.getLength());。getPort(),获得发送端的数字标识。

(5) 关闭资源,可选。如果想要接受数据,需要一直开机。

net包和io包经常合用。将发送数据和接受数据的程序合用后,构成一个聊天软件。

每一端都可以发送数据并接受数据,也就是说,每一端都有2个线程。

一个Socket只能提供一种服务,要么发送,要么接受。2个Socket走2个线程,可以保证收发同时。

九   UDP - 键盘录入方式数据

发送和接收两端都讲了,注意如果写了while(true){……}接收端一直开着也没有问题,因为receive是阻塞式的,
但不能把DatagramSocket端点的创建也放到while true里面,因为会抛BindException,地址已经被绑定了。
当然也不能用其他程序已经绑定的端口,而且如果没有绑定但端口还没释放,也有可能出现BindException。
那么现在来思考,如何用键盘录入写一句发一句呢?

1、发送端程序

需求:通过UDP阐述方式,将一段文字数据发送出去

思路:

(1) 建立udpsocket服务

(2) 提供数据,并将数据封装到数据包中

(3) 通过socket服务的发送功能,将数据包发送出去

(4) 关闭资源

import java.net.*;
import java.io.*;

class UDPSend2 {
	public static void main(String[] args) throws Exception {
		DatagramSocket ds = new DatagramSocket();
		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("localhost"), 10001);
			ds.send(dp);
		}
		ds.close();
	}
}

2、接收端程序

需求:定义一个应用程序,用于接收UDP协议传输的数据并处理

思路:

(1) 创建udpsocket服务,通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识,方便于明确哪些数据过来该应用可以处理

(2) 定义一个数据包,因为要存储接收到的字节数据,因为数据包对象中有更多功能可以提取字节数据中的不同数据信息

(3) 通过socket服务的receive方法将收到的数据存入到已定义好的数据包中

(4) 通过数据包对象的特有功能。将这些不同的数据取出,打印在控制台上。

(5) 关闭资源

import java.net.*;
import java.io.*;

class UDPRece2 {
	public static void main(String[] args) throws Exception {
		DatagramSocket ds = new DatagramSocket(10001);
		while (true) {
			byte[] buf = new byte[1024];
			DatagramPacket dp = new DatagramPacket(buf, buf.length);
			ds.receive(dp);
			String ip = dp.getAddress().getHostAddress();
			String data = new String(dp.getData(), 0, dp.getLength());
			System.out.println(ip + "::" + data);
		}
	}

}

十   UDP聊天

练习:编写一个聊天程序。

思路:将发送数据和接受数据的程序合用后,构成一个聊天软件。

       每一端都可以发送数据并接受数据,也就是说,每一端都有2个线程。

       一个Socket只能提供一种服务,要么发送,要么接受。2个Socket走2个线程,可以保证收发同时。

发送端代码:

import java.net.*;
import java.io.*;

class UDP_Send {
    public static void main(String[] args) throws Exception {
        DatagramSocket ds = new DatagramSocket();
        byte[] buf = new byte[1024];
        String line = null;
        DatagramPacket dp = null;
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        while ((line = bufr.readLine()) != null) {
            if (line.equals("886")) {
                break;
            }
            dp = new DatagramPacket(line.getBytes(), line.length(), InetAddress.getByName(""), 10000);
            ds.send(dp);
        }
        ds.close();
    }
}

接收端代码:

import java.net.*;
import java.io.*;

class  UDP_Rece
{
	public static void main(String[] args) throws Exception
	{
		DatagramSocket ds = new DatagramSocket(10000);
		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf,buf.length);
		while(true){
			ds.receive(dp);
			String ip = dp.getAddress().getHostAddress();
			String data = new String(dp.getData(),0,dp.getLength());
			System.out.println(ip+":"+data);
		}
	}
}

有收数据的部分,和发数据的部分。这两部分需要同时执行。那就需要用到多线程技术。

一个线程控制收,一个线程控制发。因为收和发动作是不一致的,所以要定义两个run方法。

而且这两个方法要封装到不同的类中。

十一   TCP传输

说完UDP,现在来了解TCP。

1、PPT内容

  • Socket和ServerSocket
  • 建立客户端和服务器端
  • 建立连接后,通过Socket中的IO流进行数据的传输
  • 关闭Socket
  • 同样,客户端和服务器端是两个独立的应用程序

2、内容讲解

TCP传输“面向连接、可靠、大数据量”。

两端分别是客户端Socket和服务器端ServerSocket

建立连接后,使用IO流进行数据的传输。客户端,空参数的对象需要使用connect()连接服务器端。

通过查阅Socket对象,发现该对象建立时,就可以去连接指定的主机。

因为TCP是面向连接的,所以在建立Socket服务时,需要有服务器端存在。形成通路后,通过该通路传输数据。

连接成功后,会建立通路,通路里面已经有Socket的网络流存在,通过getInputStream()和getOutputStream()可以获得输入流和输出流,

从而完成对数据的操作。有了通路,也就有了流(Socket内部已经封装完毕)。“TCP难在服务端。”

3、示例代码

import java.net.*;
import java.io.*;

class TcpClient{
    public static void main(String[] args) throws UnknownHostException, IOException{
        //创建客户端的Socket服务。指定目的主机和端口。
        Socket s = new Socket(InetAddress.getLocalHost(),10005);
        //为了发送数据,应该获取Socket流中的输出流。
        OutputStream out = s.getOutputStream();
        out.write("A B C D E".getBytes());
        s.close();
    }
}

客户端建立,具体步骤如下:

(1)   创建客户端的Socket服务,并指定需要连接的目的主机和端口。例如,Socket s = new Socket(InetAddress.getLocalHost(),10005);

(2)   为了发送数据,获取Socket流中的输出流,即OutputStream,数据会写入输出流,并随网络自动发送到对应的主机上。

      例如,OutputStreamout = s.getOutputStream();

(3)   写入后关闭Socket服务。

为了防止服务器端将数据发到错误的客户端,服务器端本身没有流对象,它会使用客户端的流对象和客户端通信,也就是拿到了客户端对象。

import java.io.*;
import java.net.*;

class TcpServer {
    public static void main(String[] args) throws IOException {
        //建立服务端Socket服务。并监听一个端口。
        ServerSocket ss = new ServerSocket(10005);
        //通过accept方法获取连接过来的客户端对象。
        Socket s= ss.accept();
        InputStream in = s.getInputStream();
        String ip = s.getInetAddress().getHostAddress();
        byte[] buf = new  byte[1024];
        //获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据。
        int len = in.read(buf);
        System.out.println(new String(buf,0,len));
        s.close();//关闭客户端。
    }
}

定义服务端接收数据。具体步骤如下:

(1)  建立服务器端的Socket服务,并监听一个端口。例如,ServerSocket ss = newServerSocket(10005);

(2)  通过accept()方法获得连接过来的客户端对象。例如,Socket so =ss.accept();。注意一定要获得客户端的IP地址,方便日后使用。

(3)  获取客户端发送的数据,使用客户端的读取流来读取。例如,InputStream in =s.getInputStream();

(4)  操作后关闭客户端。

十二   TCP传输2

刚才只是客户端给服务器端发了信息,现在要发完信息,需要服务器端给客户端发反馈信息。

import java.net.*;
import java.io.*;

/*
 演示TCP传输的客户端和服务器端的互访。
 需求:客户端给服务器端发送数据,服务器端收到后,给客户端反馈信息。
 */

/*
 客户端:
 1.建立Socket服务。指定要连接的主机和端口。
 2.获取Socket流中的输出流,将数据写到该流中,通过网络发送给服务端。
 3.获取Socket流中的输入流,将服务器端反馈的数据获取,并打印。(注意:服务器端反馈信息,它要收一下才行)
 4.关闭客户端资源
 */

class TCPClient2 {
	public static void main(String[] args) throws Exception {
		Socket s = new Socket(InetAddress.getByName("localhost"), 10004);
		OutputStream out = s.getOutputStream();
		out.write("服务器端,你好!".getBytes());
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf); // 这里读的是Socket流中的数据,如果服务器端没有发,它就在等。
		System.out.println(new String(buf, 0, len));
		s.close();
	}
}

/*
服务端:
1.建立服务端的socket服务,ServerSocket();并监听一个端口
2.获取连接过来的客户段对象
     通过ServerSocket 的 accept方法,所以这个方法是阻塞式的
3.客户端如果发过来数据,服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据
4.关闭服务.(可选)
*/
class TCPServer2 {
	public static void main(String[] args) throws Exception {
		ServerSocket ss = new ServerSocket(10004);
		Socket s = ss.accept();
		InputStream in = s.getInputStream();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + " ....connected.");
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf, 0, len));
		OutputStream out = s.getOutputStream();
		Thread.sleep(10000); // 客户端有阻塞式的read方法让它等着读数据
		out.write("服务器收到了,谢谢.".getBytes());
		ss.close();
	}
}

Q:为什么客户端发完,不是立刻读呢?

A:因为有阻塞式的read方法让它等着读数据。

十三   TCP练习

 需求:建立一个文本转换服务器。

 客户端给服务端发送文本,服务端会将文本转换成大写再返回给客户端。

 而且客户端可以不断地进行文本转换,当客户端输入OVER时,转换结束。


 分析:一样可以用IO的源和目的的思想


 客户端:

 既然是操作设备上的数据,那么就可以用IO技术,并按照IO的操作规律来思考。

 源:键盘录入

 目的:网络设备,网络输出流

 而且操作的是文本数据,可以选择字符流。


 步骤:

 1.建立服务

 2.获取键盘录入

 3.将数据发给服务端

 4.后去服务端返回的大写数据

 5.结束,关资源


 都是文本数据,可以使用字符流进行操作,同时为了提高效率,加入缓冲技术。

import java.net.*;
import java.io.*;

class TransClient {
	public static void main(String[] args) throws Exception {
		Socket s = new Socket(InetAddress.getByName("localhost"), 10005);
		// 定义读取键盘数据的流对象
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		// 定义目的,将数据写入到Socket输出流,发给服务端。
		BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		// 源和目的有了,接下来干嘛?一顿狂读狂写啊
		// 定义一个Socket读取流,读取服务端返回的大写信息。
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		String line = null;
		while ((line = bufr.readLine()) != null) {
			if ("over".equals(line)) {
				break;
			}
			bufOut.write(line); // 注意这里写到缓冲区了,要刷新!
			bufOut.newLine();
			bufOut.flush();
			String str = bufIn.readLine();
			System.out.println("Server:" + str);
		}
		bufr.close();
		s.close();
	}
}

/*
 * 服务端: 源:Socket读取流 目的:Socket输出流 都是文本:装饰
 */

class TransServer {
	public static void main(String[] args) throws Exception {
		ServerSocket ss = new ServerSocket(10005);
		Socket s = ss.accept();
		// 读取Socket读取流中的数据
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		// 目的:Socket输出流,将大写数据写入到Socket输出流,并发送给客户端
		BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		String line = null;
		while ((line = bufIn.readLine()) != null) { // readLine读到回车符才会返回数据,客户端那边会当你没有结束标记,一直狂等
			bufOut.write(line.toUpperCase());
			bufOut.newLine();
			bufOut.flush();
		}
		s.close();
		ss.close();
	}
}


该例子出现的问题。 现象:客户端和服务端都在莫名地等待。 为什么呢?

因为客户端和服务端都有阻塞式的方法,这些方法没有读到结束标记,那么就会一直等待,而导致两端都在等待。

遇到类似这种情况,就查阻塞式方法。

十四   TCP复制文件

需求:实现使用TCP传输复制文件

import java.net.*;
import java.io.*;

class TextClient {
	public static void main(String[] args) throws Exception {
		Socket s = new Socket("127.0.0.1", 10006);
		BufferedReader bufr = new BufferedReader(new FileReader("Demo.java"));
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		String line = null;
		while ((line = bufr.readLine()) != null) {
			out.println(line);
		}
		// out.println("over");//人工做标记,停止
		s.shutdownOutput();// 关闭客户端的输出流,相当于给流中加入一个结束标记,相当于给流中加入一个结束标记 -1
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s
				.getInputStream()));
		String info = bufIn.readLine();
		System.out.println(info);
		bufr.close();
		bufIn.close();
	}
}

class TextServer {
	public static void main(String[] args) throws Exception {
		ServerSocket ss = new ServerSocket(10006);
		Socket s = ss.accept();
		System.out.println(s.toString());
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s
				.getInputStream()));
		FileWriter fw = new FileWriter("Demo.txt");
		BufferedWriter bufw = new BufferedWriter(fw);
		String line = null;
		while ((line = bufIn.readLine()) != null) {
			// if("over".equals(line))
			// break;//人工做标记,停止
			bufw.write(line);
			bufw.newLine();
			bufw.flush();
		}
		PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
		pw.println("上传成功");
		bufIn.close();
		bufw.close();
		pw.close();
	}
}

说明:

客户端的结束标识会发送到服务器端,从而结束服务器端的流。

建立端口需要try,可能会出现各种异常;建立各种流也需要处理异常。

客户端传输文件,内部需要判断文件是否存在,外部需要先把文件名字发出去,

以便于服务器端可以建立同名的文件。如果文件存在,加上标识后存储。

文件的上传,最关键的问题是结束标记。利用“时间戳”作为结束标识,

在客户端,以当时的时间作为结束标记,如果服务器端读到了这个时间,关闭流。

发送基本数据类型,使用时间戳比较方便。System.currentMillions()。

使用更多的是,通过Socket中的shutdownInput()和shutdownOutput()关闭流,内部也是使用标记。

客户端写入数据结束后,给服务器端发送一个标记,

即shutdoenOutput(),关闭客户端的输出流,相当于给流中加入结束标记-1,通知写入结束。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值