JavaSE实战——网络编程

    转载请声明出处:http://blog.csdn.net/zhongkelee/article/details/48712363

说在前面

    本篇主要介绍Java基础的网络编程部分,比较涉及到底层的socket流编程,主要是为了通过网络层来更加清楚了解网络传输数据的原理和过程。全文分两个部分,第一部分介绍一些基本的网络知识,第二部分介绍Java中的网络编程。

一、简单的网络知识

1.网络模型

    OSI/RM(Open System Interconnection Reference Model开放式系统互连基本参考模型),是由ISO(International Standards Organization国际标准化组织)提出的网络参考模型。OSI/RM模型的网络层同时支持面向连接和无连接的通信,但是传输层只支持面向连接的通信。

    TCP/IP(Transmission Control Protocol/Internet Protocol 传输控制协议/因特网互联协议)TCP/IP模型的网络层只提供无连接的服务,但是传输层上同时提供两种通信模式。

    网络参考模型简图:


    OSI/RM七层简述:


2.网络通信三要素

(1)IP地址:InetAddress

    用来标识网络上的一台独立的主机。

    特点:


    缺点:不易记忆,但可以将IP地址和域名对应起来,利用DNS解析就可以通过域名得到IP地址,只需要记住比较好记的域名就可以了。

    DNS解析过程


    特殊的IP地址:127.0.0.1(本地回环地址、保留地址),可用于简单的测试网卡是否故障,表示本机,主机名:localhost。


(2)端口号:port

    用于标识进程的逻辑地址,不同的进程都有不同的端口标识。要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。为了方便称呼这些数字,则将这些数字称为端口。(此端口是一个逻辑端口)

    有效端口:0~65535,其中0~1024是系统端口或者保留端口。


(3)传输协议

    通讯的规则,常见协议如:TCP、UDP协议。

   

3.网络通信步骤

    确定对端IP地址→ 确定应用程序端口 → 确定通讯协议 

    (1)封装过程:


    (2)解封装过程:


    总结:网络通讯的过程其实就是一个(源端)不断封装数据包和(目的端)不断拆数据包的过程。

    简单来说就是:发送方利用应用软件将上层应用程序产生的数据前后加上相应的层标识不断的往下层传输(封包过程),最终到达物理层通过看得见摸得着的物理层设备,例如:网线、光纤…等将数据包传输到数据接收方,然后接收方则通过完全相反的操作不断的读取和去除每一层的标识信息(拆包过程),最终将数据传递到最高层的指定的应用程序端口,并进行处理。

    TCP三次握手图解:


二、Java中的网络编程

1.IP地址

    Java中的IP地址对象使用的是java.net.InetAddress来封装。主要的功能是实现IP地址和主机名之间的相互解析。InetAddress是一个用于记录主机的类,其静态getHostByName(String msg)可以返回一个实例,其静态方法getLocalHost()也可以获得当前主机的IP地址,并返回一个实例。

package ustc.lichunchun.net.ip;

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

public class IPDemo {

	public static void main(String[] args) throws UnknownHostException {
		/*
		 * 演示IP对象。
		 */
		InetAddress ip = InetAddress.getLocalHost();//返回本地主机IP地址
		
		//获取其他主机信息。通过名称(IP字符串or主机名)来获取一个ip对象。
		ip = InetAddress.getByName("www.baidu.com");//119.75.217.109
		
		System.out.println(ip);//chunchun-PC/192.168.105.152
		
		String ip_str = ip.getHostAddress();
		String name = ip.getHostName();
		System.out.println(ip_str+":"+name);
		
		System.out.println("---------------------------");
		
		InetAddress[] ias = InetAddress.getAllByName("www.baidu.com");
		for(InetAddress ia: ias){
			System.out.println(ia);
		}
	}
}

2.Socket套接字--通信的端点

    Java中的套接字就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。(管道流

构造函数:
    Socket();
    Socket(InetAddress address, int port)throws UnknownHostException, IOException
    Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException
    Socket(String host, int port)throws UnknownHostException, IOException
    Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException
    除去第一种不带参数的之外,其它构造函数会尝试建立与服务器的连接。如果失败会抛出IOException错误。如果成功,则返回Socket对象。

Socket方法:
    getInetAddress(); 远程服务端的IP地址
    getPort(); 远程服务端的端口
    getLocalAddress(); 本地客户端的IP地址
    getLocalPort(); 本地客户端的端口
    getInputStream(); 获得输入流
    getOutStream(); 获得输出流
    值得注意的是,在这些方法里面,最重要的就是getInputStream()和getOutputStream()了。

Socket状态:
    isClosed(); 连接是否已关闭,若关闭,返回true;否则返回false
    isConnect(); 如果曾经连接过,返回true;否则返回false
    isBound(); 如果Socket已经与本地一个端口绑定,返回true;否则返回false

3.UDP用户数据报协议--DatagramSocket/DatagramPacket

UDP传输实现步骤:

1.建立UDP传输的发送端和接收端。

2.提供数据,并将数据封装到数据包中。

3.调用Socket服务的发送、接收功能,将数据包发出去或者接收回来。

4.关闭Socket服务。

注意点:

1.只要是网络传输,必须有Socket。

2.数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。

3.直接操作UDP不可能,对于Java语言应该将UDP封装成对象,易于我们的使用,这个对象就是DatagramSocket,它封装了UDP传输协议的socket对象。DatagramSocket具备发送和接受功能,在进行UDP传输时,需要明确一个是发送端,一个是接收端。

4.因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对象,这个数据包对象就是DatagramPacket,通过这个对象中的方法,就可以获取到数据包中的各种信息。

5.发送端与接收端是两个独立的运行程序。

需求一:建立UDP的发送端和接收端

1.建立UDP发送端,思路:

A.建立可以实现UDP传输的socket服务。
B.明确具体发送的数据。
C.通过socket服务将具体的数据发送出去。 
D.关闭服务。

package ustc.lichunchun.net.udp;

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

public class UDPSendDemo {

	public static void main(String[] args) throws IOException {
		/*
		 * 需求:建立UDP的发送端。
		 * 思路: 
		 * 1.建立可以实现UDP传输的socket服务。
		 * 2.明确具体发送的数据。
		 * 3.通过socket服务将具体的数据发送出去。 
		 * 4.关闭服务。
		 */
		System.out.println("UDP发送端启动......");

		// 1.创建UDP服务。
		DatagramSocket ds = new DatagramSocket(8888);

		// 2.明确数据。
		String str = "注意啦,UDP来啦!";

		// 3.发送数据。将数据封装到数据包中。
		// 3.1将数据封装到数据包对象中。数据包会明确目的地址和端口。
		byte[] buf = str.getBytes();
		DatagramPacket dp = new DatagramPacket(buf, buf.length,
				InetAddress.getByName("192.168.105.134"), 10000);

		// 3.2发送。
		ds.send(dp);
		
		// 4.关闭。
		ds.close();
	}
}
2.建立UDP接收端,思路:

A.创建Socket服务。明确一个端口。
B.收数据。
C.将其中所需要的数据取出来,ip, data, port。
D.关闭资源。

package ustc.lichunchun.net.udp;

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

public class UDPReceDemo {

	public static void main(String[] args) throws IOException {
		/* 
		 * 创建UDP的接收端。
		 * 思路:
		 * 1.创建Socket服务。明确一个端口。
		 * 2.收数据。
		 * 3.将其中所需要的数据取出来,ip, data, port。
		 * 4.关闭资源。
		 */
		System.out.println("UDP接收端启动了...........");
		
		//1.创建Socket服务。
		DatagramSocket ds = new DatagramSocket(10000);
		
		//2.使用Socket的接收方法,接收数据。需要将收到的数据存储到数据包中。
		//可以通过数据包对象的方法对收到的数据进行解析。
		//2.1创建一个数据包。
		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf, buf.length);
		
		//2.2接收。
		ds.receive(dp);//阻塞式方法。
		
		//3.通过数据包对象解析收到的数据,使用数据包的方法。
		String ip = dp.getAddress().getHostAddress();
		int port = dp.getPort();
		//获取文字数据。
		String str = new String(dp.getData(), 0, dp.getLength());
		System.out.println(ip+":"+port+"------"+str);
		//192.168.105.120:8888------注意啦,UDP来啦!
		
		//4.关闭资源。
		ds.close();
	}
}

需求二:通过UDP协议,利用多线程技术完成一个聊天程序。

package ustc.lichunchun.net.udp.chat;

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

public class ChatTest {

	public static void main(String[] args) throws IOException {
		/*
		 * 通过UDP协议完成一个聊天程序。 一个负责发送数据的任务,一个负责接收数据的任务。
		 * 两个任务需要同时执行。 可以使用多线程技术。
		 */
		
		//创建socket服务。
		//发送端
		DatagramSocket send = new DatagramSocket(8888);
		//接收端
		DatagramSocket rece = new DatagramSocket(10004);
		
		new Thread(new Send(send)).start();
		new Thread(new Rece(rece)).start();
	}
}

//负责发送的任务。通过UDP的socket发送。
class Send implements Runnable {

	//任务对象一建立,需要socket对象。
	private DatagramSocket ds;

	public Send(DatagramSocket ds) {
		super();
		this.ds = ds;
	}

	@Override
	public void run() {
		//具体的发送数据的任务内容。
		//1.要发送的数据来自哪里?键盘录入。
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		//1.读取数据。
		String line = null;
		try {
			while ((line = bufr.readLine()) != null) {
				//2.将数据封装成字节数组,封装到数据包中。
				byte[] buf = line.getBytes();
				DatagramPacket dp = new DatagramPacket(buf, buf.length,
						InetAddress.getByName("192.168.105.255"), 10004);
				//255是广播地址,0是网络段, 1-254是可以分配的ip地址。
				//该网络段192.168.105.1~192.168.105.254的254台主机都能收到数据。
				
				//3.将数据包发出去。
				ds.send(dp);
				
				if ("over".equals(line)) {
					break;
				}
			}
			ds.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

//负责接收的任务
class Rece implements Runnable {
	private DatagramSocket ds;

	public Rece(DatagramSocket ds) {
		super();
		this.ds = ds;
	}

	@Override
	public void run() {
		//接收的具体的任务内容。
		while (true) {
			//1.因为接收的数据最终都会存储到数据包中,而数据包必须有字节数组。
			byte[] buf = new byte[1024];
			
			//2.创建数据包对象。
			DatagramPacket dp = new DatagramPacket(buf, buf.length);
			
			//3.将收到的数据存储到数据包中。
			try {
				ds.receive(dp);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			//4.获取数据。
			String ip = dp.getAddress().getHostAddress();
			String data = new String(dp.getData(), 0, dp.getLength());
			System.out.println(ip + ":" + data);
			//如果笔记本连接了无线和有线两个端口,会打印两次。
			
			if("over".equals(data)){//左边是常量,这样不会报错。否则,data是空的话会报空指针异常。
				System.out.println(ip+".....离开了聊天室");
			}
		}
	}
}

4.TCP传输控制协议--Socket/ServerSocket

面向连接的TCP传输的两个端点建立连接后会有一个传输数据的通道,这个通道称为流,而且是建立在网络基础上的流,称之为Socket流。该流中既有读取,也有写入。

TCP传输实现步骤:

1.建立TCP传输的客户端(Socket)和服务端(ServerSocket).

2.建立连接后,通过Socket中的IO流进行数据的传输。

3.关闭Socket服务。

注意点:

1.TCP的两个端点:客户端和服务端。客户端对应的对象是Socket,服务端对应的对象是ServerSocket。

2.TCP和UDP不一样的是,必须先开启服务端,服务端不打开,客户端就无法访问,而UDP随意。

3.关闭Socket流的时候,要先关客户端,再关服务端。

Socket和ServerSocket的交互,我想下面一张图已经很清楚的表达了:


需求一:建立TCP的客户端和服务端。

1.建立TCP客户端,思路:

A.客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常。这个对象在创建时,就已经可以对指定ip和端口进行连接(三次握手)。
B.如果连接成功,说明客户端与服务端建立了通道,Socket流已经产生,而Socket流是底层建立好的。既然是流,说明这里既有输入,又有输出。那么通过Socket IO流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream(),getOutputStream()获取即可。
C.与服务端通讯结束后,关闭Socket服务。

package ustc.lichunchun.net.tcp;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class TCPClient {

	public static void main(String[] args) throws IOException, IOException {
		/*
		 * TCP客户端的建立。
		 * 思路:
		 * 1.建立TCP客户端服务。
		 * 	1.1因为是面向连接,所以必须有连接才可以进行通信。
		 * 	1.2在创建客户端时,就必须明确目的地址和端口号。
		 * 	1.3这个对象在创建时,就已经可以对指定ip和端口进行连接(三次握手)。
		 * 2.一旦连接建立,就有了传输数据的通道。就可以在通道中进行数据传输。
		 *   这个传输其实就是通过流实现的。这个流就是Socket IO流。
		 * 3.只要获取Socket IO流中的写动作就可以将数据写到Socket流中发给服务端。
		 * 4.关闭资源。
		 */
		System.out.println("客户端启动......");
		
		//1.客户端主动创建用于连接通信的客户端Socket流对象。明确目的地址和端口。
		Socket s = new Socket("192.168.105.134", 10002);
		
		//获取Socket流  远程连接的地址。
		System.out.println(s.getInetAddress().getHostAddress()+"......connected");
		//服务端IP:192.168.105.134......connected
		
		//2.获取Socket流中的输出流。将数据发送给服务端。(这里获取到的读取、写入流类似于管道流)
		OutputStream out = s.getOutputStream();
		
		//3.通过输出流写数据。(TCP传输的数据都是字节流)
		out.write("又要注意啦,TCP来了!".getBytes());
		
		//4.关闭资源。
		s.close();
	}
}
2.建立TCP服务端,思路:

A.建立服务端的Socket服务,服务端需要明确它要处理的数据是从哪个端口进入的,通过ServerSocekt(),并监听一个端口。
B.当有客户端访问时,要明确是哪个客户端,可通过ServerSocket的accept()方法获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。没有连接就会一直等,所以这个方法是一个阻塞式方法。
C.当该客户端访问结束,关闭该客户端服务。

package ustc.lichunchun.net.tcp;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {

	public static void main(String[] args) throws IOException {
		/*
		 * 创建TCP服务器端。
		 * 思路:
		 * 1.创建Socket服务器端服务。服务器端为了让客户端可以连接上,必须提供端口。监听一个端口。
		 * 2.获取客户端对象。通过客户端的Socket流和对应的客户端进行通信。
		 * 3.获取客户端的Socket流的读取流。(类似于管道流)
		 * 4.读取数据并显示在服务器端。
		 * 5.关闭资源。注意:要先关客户端,再关服务端。(但是开的时候,是先开服务端,再开客户端。)
		 * 
		 * 注意:TCP和UDP不一样的是,必须先开启服务端,服务端不打开,客户端就无法访问,而UDP随意。
		 */
		System.out.println("服务器端启动......");
		
		//1.创建服务器端对象。
		ServerSocket ss = new ServerSocket(10002);
		
		//2.侦听并获取连接过来的客户端对象。
		Socket s = ss.accept();
		
		//获取远端连接地址。
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+".......connected");
		//客户端IP为:192.168.105.158.......connected
		//http://www.cnblogs.com/rond/p/3565113.html-->一图以毙之!
		
		//3.通过客户端对象获取Socket流的读取流。(这里获取到的读取、写入流类似于管道流)
		InputStream in = s.getInputStream();
		
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		String str = new String(buf, 0, len);
		
		System.out.println(str);
		//又要注意啦,TCP来了!
		
		s.close();
		ss.close();
		
	}
}

需求二:实现TCP客户端服务端互访。

客户端:

package ustc.lichunchun.net.tcp2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class TCPClient2 {

	public static void main(String[] args) throws UnknownHostException,IOException {
		Socket client = new Socket("192.168.105.134", 10003);
		new Thread(new Client(client)).start();
	}
}

class Client implements Runnable {
	Socket s;

	public Client(Socket s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {
		System.out.println(s.getInetAddress().getHostAddress()+"...connnected");
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		String line = null;
		try {
			while((line=bufr.readLine())!=null){
				OutputStream out = s.getOutputStream();
				out.write(line.getBytes());
				if("over".equals(line)){
					break;
				}
				InputStream in = s.getInputStream();
				byte[] buf = new byte[1024];
				int len = in.read(buf);
				System.out.println(new String(buf,0,len));
			}
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
服务端:
package ustc.lichunchun.net.tcp2;

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

public class TCPServer2 {

	public static void main(String[] args) throws IOException {
		ServerSocket ss = new ServerSocket(10003);
		new Thread(new Server(ss.accept())).start();
	}
}
class Server implements Runnable{
	Socket s;
	
	public Server(Socket s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {
		System.out.println(s.getInetAddress().getHostAddress()+"...connnected");
		while(true){
			try {
				InputStream in = s.getInputStream();
				byte[] buf = new byte[1024];
				int len = in.read(buf);
				String str = new String(buf,0,len);
				if("over".equals(str)){
					System.out.println(s.getInetAddress().getHostAddress()+" is leaving");
					break;
				}
				System.out.println(str);
				BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
				String line = bufr.readLine();
				OutputStream out = s.getOutputStream();
				out.write(line.getBytes());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		try {
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

需求三:用TCP创建一个英文大写转换服务器。

需求分析:
客户端输入字母数据,发送给服务端,服务端收到后显示在控制台,并将该数据转成大写返回给客户端。直到客户端输入over,转换结束。需求中有客户端和服务端,所以使用TCP传输。

客户端:

package ustc.lichunchun.net.tcp.test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
/*
 * 思路:
 * 客户端:
 * 1,需要先有socket端点。
 * 2,客户端的数据源:键盘。
 * 3,客户端的目的:socket.
 * 4,接收服务端的数据,源:socket。
 * 5,将数据显示在打印出来,目的:控制台.
 * 6,在这些流中操作的数据,都是文本数据。
 * 
 * 转换客户端:
 * 1,创建socket客户端对象。
 * 2,获取键盘录入。
 * 3,将录入的信息发送给socket输出流。
 */
public class TransClient {
	
	public static void main(String[] args) throws UnknownHostException, IOException{
		//1.创建Socket客户端对象。
		Socket s = new Socket("192.168.101.132", 10001);
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"...is connected");
		
		//获取键盘录入。
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		//Socket输出流。
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		//Socket输入流,读取服务端返回的大写数据。
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		String line = null;
		while((line = bufr.readLine())!=null){
			if("over".equals(line)){
				break;
			}
			out.println(line);
			//读取服务端返回的一行大写数据。
			String upperStr = bufIn.readLine();
			System.out.println(upperStr);
		}
		s.close();
	}
}

服务端:

package ustc.lichunchun.net.tcp.test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/*
 * 转换服务端。
 * 分析:
 * 1,ServerSocket服务。
 * 2,获取socket对象。
 * 3,源:socket,读取客户端发过来的需要转换的数据。
 * 4,目的:显示在控制台上。
 * 5,将数据转成大写发给客户端。
 */
public class TransServer {
	
	public static void main(String[] args) throws IOException{
		//1.建立ServerSocket服务,并监听相应端口。
		ServerSocket ss = new ServerSocket(10001);
		//2.获取Socket对象。
		Socket s = ss.accept();
		//3.获取IP。
		System.out.println(s.getInetAddress().getHostAddress()+"...connected");
		
		//4.获取Socket读取流,并装饰。
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		//5.获取Socket输出流,并装饰。
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		
		String line = null;
		while((line = bufIn.readLine())!=null){
			System.out.println(line);
			out.println(line.toUpperCase());
		}
		s.close();
		ss.close();
	}
}

5.TCP上传文本和图片

需求一:上传文本

需求:读取本地文本数据,发送给服务器端,服务器端接收完毕后,回馈"上传成功"字样。

问题:由于readLine()方法是阻塞式的,如果客户端不发送结束标记,会导致死锁的发生,客户端服务端两端都会等待。

解决:两端等待的情况,说明有阻塞式方法,没有读取到结束标记。发送特定字符串或者当前时间戳作为结束标记,但是容易重复、比较麻烦。可以使用Socket的禁用输出流的方法shutdownOutput(),实际上就是向服务器端发送了一个结束标记。

客户端:

package ustc.lichunchun.net.tcp.uploadtext;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class UploadTextClient {

	public static void main(String[] args) throws UnknownHostException, IOException {
		/*
		 * 上传文本的客户端。 读取本地文本数据,发送给服务器端,服务器端接收完毕后,回馈"上传成功"字样。
		 */
		
		System.out.println("上传文本客户端启动......");
		
		//客户端socket
		Socket s = new Socket("192.168.105.134", 10004);
		
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"......connected");
		
		//1.确定数据源。本地文本文件。
		BufferedReader bufr = new BufferedReader(new FileReader("client.txt"));
		
		//2.确定目的。socket输出流。
		//BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);//打印流:自动刷新,且保证数据原样性(读到啥就写啥)。
		
		String line = null;
		while((line = bufr.readLine())!=null){
			out.println(line);
		}
		
		//防止死锁方式一:给服务端发送结束标记。
		//out.println("over");//容易重复。
		
		//防止死锁方式二:获取当前时间戳,发送文件数据前,先发送该戳给服务端作为结束标记。
		
		//防止死锁方式三:使用socket的禁用输出流方法。(实际上就是向服务器端发送了一个结束标记)
		s.shutdownOutput();
		
		
		//3.通过socket读取流获取服务端返回的数据。 
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		String lineIn = bufIn.readLine();//阻塞式。
		System.out.println(lineIn);
		
		//4.关闭。
		bufr.close();
		s.close();
	}
}

服务端:

package ustc.lichunchun.net.tcp.uploadtext;

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

public class UploadTextServer {

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

		/*
		 * 上传文本的服务端。接受文本数据,并存储到文件中。服务端接收完毕后,回馈"上传成功"字样。
		 */
		
		System.out.println("上传文本服务器端启动......");
		
		//1.服务器端对象。
		ServerSocket ss = new ServerSocket(10004);
		
		//2.获取客户端。
		Socket s = ss.accept();
		
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"......connected");
		
		//3.获取读取流。确定源,网络socket。
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		//4.确定目的,文件。(输出流所关联的目的地,如果不存在,会自动创建;如果存在,则覆盖。)
		PrintWriter pw = new PrintWriter(new FileWriter("server.txt"),true);
		
		//5.频繁读写。
		String line = null;
		while((line = bufIn.readLine())!=null){
			//if("over".equals(line))
				//break;
			pw.println(line);
		}
		
		//6.给客户端返回信息。
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		out.println("上传成功");
		
		//7.关闭。
		pw.close();
		s.close();
		ss.close();
	}
}

需求二:上传图片

需求:上传图片至服务器,并回馈“上传图片成功”字样。

问题1:服务器端多个文件重名问题。

解决:利用“IP+序号”对文件进行编号,详见下述代码块中的getFile()方法。

问题2:多个客户端同时上传导致的并发访问问题。

解决:ServerSocket监听特定端口,Server端:将来访的每个客户端都封装到了一个单独线程中。

客户端:

package ustc.lichunchun.net.tcp.uploadpic;

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 UploadPicClient {

	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("上传图片客户端开启......");
		
		//创建Socket客户端。
		Socket s = new Socket("192.168.105.134",10006);
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"...connected");
		
		File picFile = new File("1.jpg");
		FileInputStream fis = new FileInputStream(picFile);
		OutputStream out = s.getOutputStream();
		byte[] buf = new byte[1024];
		int len = 0;
		while((len = fis.read(buf))!=-1){
			out.write(buf, 0, len);
		}
		//告诉服务器写完了。
		s.shutdownOutput();
		
		//读取服务端数据。
		InputStream in = s.getInputStream();
		byte[] bufIn = new byte[1024];
		int lenIn = in.read(bufIn);
		String str = new String(bufIn, 0, lenIn);
		System.out.println(str);
		
		fis.close();
		s.close();
	}
}

服务端:

package ustc.lichunchun.net.tcp.uploadpic;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class UploadPic implements Runnable {
	Socket s;
	
	public UploadPic(Socket s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + "...connected");
		File file = getFile("server_pic", ip);
		InputStream in;
		try {
			in = s.getInputStream();
			FileOutputStream fos = new FileOutputStream(file);
			byte[] buf = new byte[1024];
			int len = 0;
			while ((len = in.read(buf)) != -1) {
				fos.write(buf, 0, len);
			}
			
			//回馈客户端数据。
			OutputStream out = s.getOutputStream();
			out.write("上传图片成功".getBytes());
			fos.close();
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	//服务器端文件重名问题的解决。
	private static File getFile(String dir, String ip) {
		File pic_dir = new File(dir);
		if (!pic_dir.exists()) {
			pic_dir.mkdir();
		}
		int count = 1;
		File file = new File(pic_dir, ip + "(" + count + ").jpg");
		while (file.exists()) {//注意使用的是while循环,不是if。
			count++;
			file = new File(pic_dir, ip + "(" + count + ").jpg");
		}
		return file;
	}
}
程序入口:
package ustc.lichunchun.net.tcp.uploadpic;

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

public class UploadPicServer {

	public static void main(String[] args) throws IOException {
		System.out.println("上传图片服务端开启......");
		
		//服务端对象。
		ServerSocket ss = new ServerSocket(10006);
		while (true) {
			Socket s = ss.accept();
			
			//利用多线程技术解决并发访问问题。 
			new Thread(new UploadPic(s)).start();
		}
		// ss.close();
	}
}

6.Tomcat的安装和配置

Tomcat服务器由Apache提供,开源免费,底层内置的就是ServerSocket,并监听端口。最新的JSP/Servlet规范总是能在Tomcat服务器中体现出来。这里我们使用的是解压版的Tomcat7,它支持Servlet3.0。

1.绿色安装、启动关闭、访问Tomcat

A.安装:解压即可。注意需要先配置JAVA_HOME,因为Tomcat启动需要JDK;并且解压路径中没有中文和空格。

B.启动Tomcat:进入Tomcat的bin目录,双击startup.bat即可。


C.关闭Tomcat:进入Tomcat的bin目录,双击shutdown.bat即可。

D.访问Tomcat主页:

    http://locahost:8080/index.jsp --> localhost表示本机

    http://127.0.0.1//index.jsp --> 127.0.0.1本机默认IP

    http://192.168.101.132/index.jsp --> 192.168.101.132是我的真实IP


2.Tomcat目录结构


A.bin:二进制可执行文件,例如启动和关闭Tomcat的东西。

B.conf:配置文件,其中有四大配置文件,server.xml、context.xml、web.xml、tomcatusers.xml。(<Connector port="80"简化

C.logs:Tomcat会自动产生日志文件,都存放在这个目录下。

D.lib:Tomcat需要的jar都放在这里了。

E.temp:Tomcat在运行时可能会产生临时文件,临时文件就存放在这个目录。当关闭Tomcat后,这个目录下的东西可以删除。

F.webapps:这个目录下的每一个文件夹都是一个web应用程序。我们今后也会写web应用程序,然后也放到这个目录下部署。

H.work:这个目录可以删除。它是在Tomcat运行时自动生成的,里面存放的是webapps中动态资源生成的一些.java和.class文件。

3.标准的JavaWeb应用的目录结构

A.在webapps下创建项目目录,起名为hello1

B.创建hello1/WEB-INF目录:这个目录用户无法通过浏览器直接访问,可以把一些受保护的文件放到这个目录下。

C.创建hello1/WEB-INF/classes目录:当前项目的class文件。

D.创建hello1/WEB-INF/lib目录:当前项目所需jar包。

E.创建hello1/WEB-INF/web.xml文件:当前项目的“部署的描述符文件”,就是一个配置文件。(<Welcome-file-list>添加index.jsp简化

F.创建hello1/index.html文件:当前项目的页面,不能把它放到WEB-INF下,不然用户无法访问。

4.Tomcat上两种创建外部web应用的方法

方法一:

A.找到conf/server.xml文件

B.在<Host>中添加如下配置:<Context path="/xxx" docBase="F:/hello1"/>,其中path指定项目名称,docBase指定项目真实的存放路径。

C.使用http://localhost:8080/xxx/index.html访问即可。

方法二:

A.在/conf/catalina/localhost/目录下创建一个xml文件,命名为yyy.xml。

B.在文件中添加如下内容:<Context docBase="F:/hello1"/>

C.使用http://localhost:8080/yyy/index.html即可。

5.配置虚拟主机

需求:希望可以使用http://www.baidu.com来访问我们所做的项目。

解决

A.修改/conf/server.xml的<Connector port="8080"...>为默认端口号80,<Connector port="80"...>。

B.在C:\Windows\System32\drivers\etc\hosts文件中添加如下内容:

    127.0.0.1    www.baidu.com

    那么以后在本机上使用www.baidu.com就等同于使用127.0.0.1,使用了域名映射技术。

C.一个主机都会有一个存放应用程序的目录,例如localhost这个主机就有一个webapps这个目录,在一个应用程序目录中最多可以创建一个名为ROOT的应用程序,这个应用在访问时可以忽略项目名称。

D.配置一个虚拟主机,为其指定自己的应用程序目录,然后在自己的应用程序目录下创建ROOT应用。


    这其中,name指定主机名称,appBase指定当前主机存放应用的目录。

    一个主机的应用目录中可以创建一个名为ROOT的项目,访问它时可以没有项目名称。

E.修改WEB-INF项目下的web.xml文件,添加<Welcome-file-list>index.html以简化URL地址栏的书写。


注意:只有修改过C:\Windows\System32\drivers\etc\hosts文件的本机可以通过http://www.baidu.com来访问,其他电脑是不可以的!

6.安装MyEclipse10, 并配置Tomcat7和其绑定

注意

A.JavaWeb项目是在Tomcat上运行,我们使用MyEclipse来编写JavaWeb项目,但最终发布到Tomcat的webapps目录下。也就是说,MyEclipse是我们开发的环境,Tomcat才是运行环境!

B.MyEclipse自带了一个Tomcat,强烈建议不要使用它,所以我们在下面的配置介绍中,关闭MyEclipse自带的Tomcat,并且配置我们自己的Tomcat。

配置

A.MyEclipse10-->Window-->Preferences-->MyEclipse-->Servers-->Tomcat-->Tomcat 7.x-->JDK:配置成自己安装好的版本

B.MyEclipse10-->Window-->Preferences-->MyEclipse-->Servers-->Tomcat-->Tomcat 7.x-->Launch:Run mode

C.MyEclipse10-->Window-->Preferences-->MyEclipse-->Servers-->Tomcat-->Tomcat 7.x:Enable

D.MyEclipse10-->Window-->Preferences-->MyEclipse-->Servers-->Tomcat-->Tomcat 7.x:Tomcat home dictionary选择自己安装Tomcat的路径

接下来,点击如下图标即可通过MyEclipse启动自定义的Tomcat:



然后,我们的目标是,将自己做的项目发布到Tomcat的webapps下。实际上就是将自己项目中的WebRoot目录复制到webapps目录下,并将文件名更改为自己的项目名称。请按下面的图示进行操作即可:









。。。。。重启。。。。。。


还有一种部署自定义项目的方法,如下图所示:




7.模拟Tomcat服务器IE浏览器

常见的客户端和服务端有哪些呢?
客户端:浏览器。
服务端:Tomcat(内置了ServerSocket,对外提供了ip地址和默认端口8080,只要开启,外界就可以访问)

如果请求多图片的网页,请求的次数很多,有多少资源(html文件,图片文件,css文件,js文件等),就需要请求多少次。

浏览器中当然也是内置了如Socket一样的客户端程序。用java模拟:Socket s = new Socket("192.168.1.253",8080);

可是浏览器到底向服务器发送了什么样的请求呢?怎么验证?  将tomcat服务器换掉,自定义一个服务器接收浏览器的发送的数据。这样就知道浏览器发送的是什么。


A.自定义服务器 -- 获取HTTP协议的请求头消息。

package ustc.lichunchun.net.my;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServer {

	public static void main(String[] args) throws IOException {
		/*
		 * 创建一个服务器,为了获取浏览器发送过来的数据。
		 */
		ServerSocket ss = new ServerSocket(9090);
		Socket s = ss.accept();
		System.out.println(s.getInetAddress().getHostAddress()+"......connected");
		
		//接受浏览器的数据
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		String str = new String(buf, 0, len);
		System.out.println(str);
		
		
		//给浏览器一个回馈
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		out.println("<font size='7' color='green'>欢迎光临,你访问的是自定义的服务器!</font>");
		
		s.close();
		ss.close();
	}
}
通过上述代码,可以得到浏览器给服务器发送的请求消息如下:
GET / HTTP/1.1    //请求行。请求方式  请求的资源路径  http协议版本。
请求头消息中的属性信息。
Accept: application/x-shockwave-flash, image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)
Host: 192.168.1.253:9090
Connection: Keep-Alive
空行
//请求体。

B.演示模拟浏览器,获取Tomcat的信息 -- 获取HTTP的应答(响应)头消息。

package ustc.lichunchun.net.my;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class MyBrowser {

	public static void main(String[] args) throws UnknownHostException, IOException {
		/*
		 * 模拟浏览器。发送一些http的消息给Tomcat服务器,并获取服务器反馈的信息。
		 * 
		 * 这里是操作传输层的数据,所以会收到并打印出HTTP协议应答头的数据。
		 */
		Socket s = new Socket("192.168.105.189", 8080);
		
		//获取输出流,给服务器发送数据。
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		
		out.println("GET /examples/index.html HTTP/1.1");
		out.println("Accept: */*");
		out.println("Host: 192.168.105.189:8080");
		out.println("Connection: close");
		out.println();
		
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = 0;
		while((len = in.read(buf))!=-1){
			String str = new String(buf, 0, len);
			System.out.println(str);
		}
		s.close();
	}
}
通过上述代码,可以获取到Tomcat服务器返回给浏览器的的应答信息:
HTTP/1.1 200 OK   //应答行   http协议版本  应答状态码  应答描述信息
应答的属性信息。
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"467-1374891778953"
Last-Modified: Sat, 27 Jul 2013 02:22:58 GMT
Content-Type: text/html
Content-Length: 467
Date: Sat, 27 Jul 2013 02:51:46 GMT
Connection: close
空行
应答体。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
 <HEAD>
  <TITLE> New Document </TITLE>
  <META NAME="Generator" CONTENT="EditPlus">
  <META NAME="Author" CONTENT="">
  <META NAME="Keywords" CONTENT="">
  <META NAME="Description" CONTENT="">
 </HEAD>

 <BODY>
	<h1>欢迎光临</h1>

	<font color="red">这是一个web测试页面!</font>

	<font color="red">大家也可以通过 192.168.1.253:8080/myweb/2.html来访问</font>

 </BODY>
</HTML>
8.URL和URLConnection

URL(Uniform Resource Locator):统一资源定位符。

URLConnection:可以通过URLConnecton类对象与远程服务器建立连接,并获取远程资源的一些属性信息。

    |--HttpURLConnection:将http请求和应答在应用层使用Http协议解析,请求和应答消息头不会显示出来。而Socket是在传输层。

    |--JarURLConnection

我们希望像浏览器那样,只要通过地址栏输入的一串字符串,就可以知道需要连接到的远程服务器IP地址、端口号、请求的资源路径,获取到的服务器端流资源等内容。所以需要解析url字符串中的数据,使用Java封装好的URL对象即可。

重点:URLConnection的openConnection()方法:返回一个URLConnection 对象,它表示到 URL 所引用的远程对象的连接。

package ustc.lichunchun.net.url;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

public class URLDemo {

	public static void main(String[] args) throws IOException {
		/*
		 * 解析url中的数据,使用URL对象。
		 * 
		 * 这里是操作应用层的数据,HTTP应答头的数据已被HttpURLConnection对象识别并解析掉了,不再显示。
		 */
		String str_url = "http://192.168.101.132:8080/examples/index.html?name=lichunchun";
		
		URL url = new URL(str_url);
		
		/*
		System.out.println("getProtocol:"+url.getProtocol());
		System.out.println("getHost:"+url.getHost());
		System.out.println("getPort:"+url.getPort());
		System.out.println("getPath:"+url.getPath());
		System.out.println("getFile:"+url.getFile());
		System.out.println("getQuery:"+url.getQuery());
		*/
		
		//通过openConnection();获取到远程资源的连接对象。
		URLConnection conn = url.openConnection();
		System.out.println(conn);
		
		//URLConnection内置了Socket。
		//调用连接对象的读取方法,准备读取资源。
		InputStream in = conn.getInputStream();
		
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		String str = new String(buf,0,len);
		System.out.println(str);
	}
}

9.HTTP协议版本

HTTP1.0:一次连接只能有一次请求应答。
HTTP1.1:一次连接可以有多次请求应答。

10.网络架构

C/S Client  Server
特点:
A.客户端和服务端都需要编写。
B.客户端需要维护。
C.客户端可以分担部分运算。
如大型运算,比如网络游戏。

B/S Browser Server
特点:
A.只需要编写服务端。客户端其实就是已有的浏览器。
B.客户端不需要维护的。
C.运算全在服务器端。

转载请声明出处:http://blog.csdn.net/zhongkelee/article/details/48712363

源码下载


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于JavaSE基础项目的实战,可以按照以下步骤进行: 1. 首先,确保你已经安装好了Java开发环境(JDK)和集成开发环境(IDE),比如Eclipse或者IntelliJ IDEA。 2. 创建一个新的Java项目,可以选择命令行项目或者图形化界面项目,具体根据你的需求来决定。 3. 在项目中创建所需的Java类,根据你的实战需求来设计类的结构和功能。 4. 在项目中使用Java的基础知识,比如变量、数据类型、运算符、控制流语句等,来实现你的项目功能。 5. 如果你的项目需要网络编程,可以使用Java的网络编程API,比如Socket和ServerSocket类,来实现网络通信功能。 6. 在项目开发过程中,可以使用调试工具来帮助你定位和解决问题,比如断点调试、日志输出等。 7. 最后,测试和运行你的项目,确保它能够按照预期的方式工作。 需要注意的是,以上步骤只是一个基本的指导,具体的实战项目可能会有不同的需求和实现方式。你可以根据自己的实际情况进行调整和扩展。同时,如果你在实战过程中遇到问题,可以参考Java的官方文档、在线教程或者向社区寻求帮助。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* *3* [JavaSE实战——网络编程](https://blog.csdn.net/zhongkelee/article/details/48712363)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值