网络编程(TCP/UDP)

Java网络编程

基本目录
    |-网络编程概述
    |-InetAddress类
    |-UDP程序设计
    |-TCP程序设计
   └-URL(URLConnection)

实例01 获取指定主机信息
实例02 UDP发送端
实例03 UDP接收端
实例04 UDP通信(用键盘录入方式传输数据)
实例05 UDP聊天
实例06 TCP服务器-转换大写字母
实例07 TCP上传文件
实例08 TCP上传图片到服务器
实例09 优化TCP上传图片
实例10 TCP客户端并发登录
实例11 一个简单的TCP服务器
实例12 获取URL的各个参数

一、网络编程概述

1、服务器和客户端

为了使两台计算机之间能够进行通信,必须为这两台计算机建立一个网络,
将这两台计算机进行连接,把一台作为客户端,一台作为服务器。

客户端:是指发送请求信息的计算机或程序。
服务器:是指能提供信息的计算机或程序。

但有时候两台计算机都是相互发送信息,相互接收信息的,所以很难区分。
为了保证能将两台或更多的计算机之间能进行通信,必须有某种相互遵守的条约,这就是协议。

例如互联网使用IP协议,这种协议使用4个字节来标识网络中的一台计算机。
在公司内部局域网中,每台机器都有一个IP地址如192.168.0.1,这就是IP协议地址,在一个网
段中它必须是唯一的。

2、TCP与UDP

TCP (Transmission Control Protocol)
是指传输控制协议,它和IP协议一起组成TCP/IP协议,TCP协议负责数据或文件的拆包与封
包,而IP协议负责发送和接收数据包。在简化的计算机网络OSI模型中,它完成第四层传输层所
指定的功能,
TCP是一种面向连接的、可靠的、基于字节流的运输层通信协议。也就是说只有建立了可靠的连
接才能进行通信,因为他们是结果三次“握手”的过程,是指先发送一次包,然后等待对方确认收
到并回复一次包,最后在开始发送数据包。

TCP协议就像生活中的打电话,A想要和B联系,A就先拨通B的号码,然后等待B接电话,如果B接
听了就表示收到消息了,然后A才会开始和B说话,这就使用了TCP通信。
还有如视频聊天、拨打网络电话、固定电话等都是TCP通信,双方都需要确认数据是否完全发送。

UDP (User Datagram Protocol)
是指用户数据报协议,UDP是与TCP同一层内另一个重要的传输协议。它也跟IP协议一起使用,
但他不对连接状态与数据丢失做检查,只保证数据发送出去,不管对方是否收到。

生活中常见的就是对讲机,使用的是UDP协议,需要时直接说话,如果对方听到就可以进行通信,
听不到,你也就白说了。还有如平时我们用的QQ聊天,MSN,发邮件等,他们都可以离线发送消息,
不管对方是否收到。

java对TCP和UDP协议提供了强有力的支持,分别引入了Socket类和DatagramSocket类,来解决
两个不同协议的网络编程。

3、端口与套接字

端口(Port)
这里说的端口并非真实存在的,如果把IP地址比作一间房子,端口就是出入这间房子的门。
真正的房子只有几个门,但是一个IP地址的端口可以有65536(即2^16)个之多,端口是
通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)。

其实计算机上的1-1023之间的端口都被系统占用了,因此在定义自己的端口时,不能使用这
一段端口号,应该使用1024-65535之间的任意端口,但也别使用其他软件已经使用的端口号。

套接字(Socket)
套接字,是支持TCP/IP的网络通信的基本操作单元,可以看作是不同主机之间的进程进行双
向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
简单的举例说明:Socket = Ip address + TCP/UDP port(IP地址+端口号)。

在java API中将套接字抽象化称为Socket类,所以程序只需创建该类的对象,就能使用套接字。
那么java就是使用Socket类的流对象进行数据传输的,该流分为输入流和输出流。

4、TCP与UDP的程序设计

TCP程序设计
java中的TCP网络程序设计是利用 Socket 类编写通信程序,设计TCP程序的过程是:服务器的套
接字等待客户端的连接请求,当服务器接收到请求后就可以通过相应的方法获取输入流和输出
流,从而实现相应的功能。

UDP程序设计
可利用DatagramSocket来编写面向UDP协议的程序,步骤如下:

发送端程序编写:
1)调用DatagramSocket()创建一个数据包套接字。
2)调用DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port)建立发送的UDP包。
3)调用DatagramSocket类的send()方法发送UDP包。
4)关闭数据包套接字。

接收端程序编写:
1)调用DatagramSocket(int port)创建一个数据包套接字,并绑定到指定端口。
2)调用DatagramPacket(byte[] buf,int length),建立一个字节数组以接收UDP包。
3)调用DataSocket类的receive()接收UDP包。
4)关闭数据包套接字。

二、IP地址 InetAddress

InetAddress 此类表示互联网协议 (IP) 地址,是与IP相关的类。
IP 地址是 IP 使用的 32 位或 128 位无符号数字,
它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。

InetAddress 的实例包含 IP 地址,
还可能包含相应的主机名(取决于它是否用主机名构造或者是否已执行反向主机名解析)。

主机名解析

主机名到IP地址的解析:
通过使用本地机器配置信息和网络命名服务,如域名系统DNS和网络信息服务NIS来实现。
要使用的特定命名服务默认情况下是本地机器配置的那个,对于任何主机名称,都返回其相应的IP地址。

反向名称解析:
意味着对于任何IP地址,都返回与IP地址关联的主机。

InetAddress缓存

InetAddress类具有一个缓存,用于存储成功及不成功的主机名解析。
默认情况下,当为了防止 DNS 哄骗攻击安装了安全管理器时,正主机名解析的结果会永远缓存。
当未安装安全管理器时,默认行为将缓存一段有限(与实现相关)时间的条目。
不成功主机名解析的结果缓存非常短的时间(10 秒)以提高性能。

此类常用的方法:
getHostName() 获取此IP地址的主机名。
getHostAddress() 返回IP地址字符串(以文本表现形式)。
SgetCanonicalHostName()获取此IP地址的完全限定域名

静态方法:(返回:static InetAddress)
getLocalHost() 返回本地主机的InetAddress对象
getByName(String host) 在给定主机名的情况下获取主机的IP地址
getByAddress(byte[] addr)在给定原始IP地址的情况下,返回InetAddress对象 
getByAddress(String host, byte[] addr) 根据提供的主机名和IP地址创建InetAddress对象

需求;获取指定主机信息

实例1

import java.net.*;
class InetAddressDemo{
	public static void sop(Object obj){System.out.println(obj);}
	public static void main(String[] args) throws Exception{
		//获取本地主机
		InetAddress i = InetAddress.getLocalHost();
		sop("本地主机:"+i);
		sop("完全限定域名:"+i.getCanonicalHostName());	//获取此IP地址的完全限定域名
		sop("IP地址的哈希码:"+i.hashCode());			//返回此 IP 地址的哈希码。
		sop("IP地址的主机名:"+i.getHostName());		//获取此 IP 地址的主机名
		sop("IP地址的字符串:"+i.getHostAddress());		//返回 IP 地址字符串(以文本表现形式)。
		
		//获取网络上指定主机的IP地址
		InetAddress ia = InetAddress.getByName("www.baidu.com");
		sop("\n正在获取“www.baidu.com”的信息");
		sop("完全限定域名:"+ia.getCanonicalHostName());	//获取此IP地址的完全限定域名
		sop("IP地址的哈希码:"+ia.hashCode());			//返回此 IP 地址的哈希码。
		sop("IP地址的主机名:"+ia.getHostName());		//获取此 IP 地址的主机名
		sop("IP地址的字符串:"+ia.getHostAddress());	//返回 IP 地址字符串(以文本表现形式)。	
		sop("是否能连接主机:"+ia.isReachable(5000));	//测试指定时间内(毫秒)是否可以达到该地址
		sop("将IP转为字符串:"+ia.toString() );			//将此 IP 地址转换为 String。
		sop(ia.isSiteLocalAddress());					//检查 InetAddress 是否是站点本地地址的实用例行程序
	}
}
/*
结果(只粘贴www.baidu.com部分的信息)
正在获取“www.baidu.com”的信息
完全限定域名:115.239.210.27
IP地址的哈希码:1945096731
IP地址的主机名:www.baidu.com
IP地址的字符串:115.239.210.27
是否能连接主机:false
将IP转为字符串:www.baidu.com/115.239.210.27
false
*/


三、UDP程序设计

类 DatagramSocket

此类表示用来发送和接收数据报包的套接字。

数据报套接字是包投递服务的发送或接收点。
每个在数据报套接字上发送或接收的包都是单独编址和路由的。
从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。

在DatagramSocket上总是启用UDP广播发送。
为了接收广播包,应该将 DatagramSocket 绑定到通配符地址。
在某些实现中,将 DatagramSocket 绑定到一个更加具体的地址时广播包也可以被接收。

构造方法:
DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr) 创建数据报套接字,将其绑定到指定的本地地址。
DatagramSocket(SocketAddress bindaddr) 创建数据报套接字,将其绑定到指定的本地套接字地址。

构造方法中的参数:
port - 要使用的端口。
laddr - 要绑定的本地地址
bindaddr - 要绑定的本地套接字地址,对于未绑定的套接字为 null。

方法
close() 关闭此数据报套接字。
getPort() 返回此套接字的端口。
isBound() 返回套接字的绑定状态。
isClosed() 返回是否关闭了套接字。
disconnect() 断开套接字的连接。
isConnected() 返回套接字的连接状态。
getLocalPort() 返回此套接字绑定的本地主机上的端口号。
getInetAddress() 返回此套接字连接的地址。
send(DatagramPacket p) 从此套接字发送数据报包
getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null。
receive(DatagramPacket p) 从此套接字接收数据报包。
connect(SocketAddress addr) 将此套接字连接到远程套接字地址(IP 地址 + 端口号)。
connect(InetAddress address, int port) 将套接字连接到此套接字的远程地址。
------------------------------------------------------------------------------------------------------------------

类 DatagramPacket

此类表示数据报包。

数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。
从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。

构造方法:
DatagramPacket(byte[] buf, int length)
    构造 DatagramPacket,用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int offset, int length)
    构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。
DatagramPacket(byte[] buf, int length, SocketAddress address)
    构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
    构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。

方法:
getPort() 返回某台远程主机的端口号。int 
getData() 返回数据缓冲区。byte[] 
getLength() 返回将要发送或接收到的数据的长度。int 
getOffset() 返回将要发送或接收到的数据的偏移量。int 
getAddress() 返回某台机器的IP地址。InetAddress 
getSocketAddress() 获取要将此包发送到的或发出此数据报的远程主机的SocketAddress。

setData(byte[] b) 为此包设置数据缓冲区。
setLength(int len) 为此包设置长度。
setPort(int iport) 设置要将此数据报发往的远程主机上的端口号。
setAddress(InetAddress iaddr) 设置要将此数据报发往的那台机器的 IP 地址。
setData(byte[] buf,int off,int len) 为此包设置数据缓冲区。
setSocketAddress(SocketAddress address) 设置要将此数据报发往的远程主机的SocketAddress。

1、UDP发送端

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

构造函数:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
创建数据包,将指定长度的包发送到指定主机上的指定端口号。

实例2

import java.net.*;
class UdpSend{
	public static void main(String[] args) throws Exception{
		//创建UDP套接字绑定指定端口。不指定将随机分配。
		DatagramSocket ds = new DatagramSocket(8888);
		//创建数组缓冲区,并指定数据
		byte[] buf = "hello,你好".getBytes();
		//创建数据包对象--把缓冲区中的数据发送到指定主机的指定端口号。
		DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.100"),10000);		
		//通过套接字的send方法把数据包发送出去,然后关闭套接字。
		ds.send(dp);
		System.out.println("发送成功");//测试		
		ds.close();
	}
}


2、UDP接收端

需求:接收udp协议传输的数据并处理。
思路:
1、定义UDP的 socket服务。监听一个端口,其实就是给这个接收网络应用程序定义数字标识,
 方便明确使用什么应用程序来处理数据。
2、定义一个数据包,因为要存储接收到的字节数据。
3、通过socket服务的receive方法将接收到的数据存入已定义好的数据包中。
4、通过数据包对象的特有功能,将这些不同的数据提取出来。打印在控制台。
5、关闭资源。

通过String类的构造方法来获取数据包中的数据:
 String(byte[] bytes, int offset, int length)
 通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。

实例3

import java.net.*;
class UdpRece{
	public static void main(String[] args) throws Exception{
		//创建套接字,绑定到指定端口
		DatagramSocket ds = new DatagramSocket(10000);
		while(true){
			//创建数组缓冲区
			byte[] buf = new byte[1024]	;
			//创建数据包,接收缓冲区中的全部数据
			DatagramPacket dp = new DatagramPacket(buf,buf.length);		
			//通过套接字ds的receive方法将收到的数据存入数据包dp中。阻塞式方法。
			ds.receive(dp);	
			//获取数据包中的IP地址、数据、端口号
			String ip = dp.getAddress().getHostAddress();
			String data = new String(dp.getData(),0,dp.getLength());
			int port = dp.getPort();
			//打印到控制台
			System.out.println("发送者IP:"+ip);
			System.out.println("发送者端口:"+port);
			System.out.println("发送的内容:"+data);
		}
		//ds.close();//不能在while循环外部关闭
	}
}


3、UDP通信(用键盘录入方式传输数据)

实例4

import java.io.*;
import java.net.*;
//UDP发送端
class UDPSend2{
	public static void main(String[] args) throws Exception{	
		//创建数据报套接字,默认绑定到任何可用的端口
		DatagramSocket ds = new DatagramSocket();		
		//将键盘录入的字节转换成字符,然后存入字符流缓冲区。
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		//循环读取字符缓冲区br中的一行数据
		String line = null;
		while((line=br.readLine())!=null){
			//如果遇到"886",就跳出循环
			if("886".equals(line))						
				break;
			//定义缓冲区,存储读到的数据
			byte[] buf = line.getBytes();
			//创建数据报包,将缓冲区中的全部数据发送到指定主机上的指定端口号
			DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.100"),10001);
			//从此套接字发送数据报包
			ds.send(dp);
		}
		ds.close();
	}
}
//UDP接收端
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);
			//从数据包中获取ip地址和数据
			String ip = dp.getAddress().getHostAddress();
			String data = new String(dp.getData(),0,dp.getLength());
			System.out.println(ip+":"+data);
		}
	}
}


4、UDP聊天

需求:用UDP方式编写一个聊天程序,需要发送端和接收端同时执行。
分析:
同时执行就要用到多线程技术,一个线程控制收,一个线程控制发。
因为收和发动作是不一致的,所以要定义两个run方法。
而且这两个方法要封装到不同的类中。

实例5

import java.io.*;
import java.net.*;
//发送端
class SendSocket implements Runnable{
	private DatagramSocket ds;
	public 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[] b = line.getBytes();
				//将缓冲区中数据发送懂指定主机的指定端口上。
				DatagramPacket dp =
					new DatagramPacket(b,b.length,InetAddress.getByName("192.168.0.100"),10001);
				//将数据包发送到套接字中
				ds.send(dp);
			}
			ds.close();
		}
		catch (Exception e){
			throw new RuntimeException("发送失败");
		}
	}
}
//接收端
class ReceSocket implements Runnable{
	private DatagramSocket ds;
	//构造数据报套接字
	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);
			}
		}
		catch (Exception e){
			throw new RuntimeException("接收失败");
		}
	}
}
class SocketDemo {
	public static void main(String[] args) throws Exception{
		//创建发送和接收服务的对象
		DatagramSocket sendSocket = new DatagramSocket();
		DatagramSocket receSocket = new DatagramSocket(10001);
		//创建两个线程,同时执行
		new Thread(new SendSocket(sendSocket)).start();
		new Thread(new ReceSocket(receSocket)).start();
	}
}


四、TCP程序设计

类 ServerSocket

服务器套接字

服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
服务器套接字的实际工作由 SocketImpl 类的实例执行。应用程序可以更改创建套接字实现的套接
字工厂来配置它自身,从而创建适合本地防火墙的套接字。

构造方法:
ServerSocket()
   创建非绑定服务器套接字。
ServerSocket(int port)
   创建绑定到特定端口的服务器套接字。
ServerSocket(int port, int backlog)
   利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
ServerSocket(int port, int backlog, InetAddress bindAddr)
   使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。

构造方法参数:
    port - 本地 TCP 端口
 backlog - 侦听 backlog
bindAddr - 要将服务器绑定到的 InetAddress

方法:
 void close() 关闭此套接字。
Socket accept() 侦听并接受到此套接字的连接。
boolean isBound() 返回ServerSocket的绑定状态。
boolean isClosed() 返回ServerSocket的关闭状态。
String toString() 作为String返回此套接字的实现地址和实现端口。 
int getLocalPort() 返回此套接字在其上侦听的端口。
InetAddress getInetAddress() 返回此服务器套接字的本地地址。
SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。 
void bind(SocketAddress endpoint) 将ServerSocket绑定到特定地址(IP 地址和端口号)。 
void bind(SocketAddress endpoint, int backlog) 将ServerSocket绑定到特定地址(IP 地址和端口号)。

方法中的参数:
endpoint - 要绑定的 IP 地址和端口号。
backlog - 侦听 backlog 长度。

类 Socket

此类实现客户端套接字(也可以就叫“套接字”)。
套接字是两台机器间通信的端点。 套接字的实际工作由 SocketImpl 类的实例执行。
应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。

构造方法:
Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字
Socket(InetAddress address, int port)
   创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
   创建一个套接字并将其连接到指定远程地址上的指定远程端口。 
Socket(String host, int port)
   创建一个流套接字并将其连接到指定主机上的指定端口号。 
Socket(String host, int port, InetAddress localAddr, int localPort)
   创建一个套接字并将其连接到指定远程主机上的指定远程端口

构造方法参数:
     port - 端口号。
  address - IP 地址。
localAddr - 要将套接字绑定到的本地地址
localPort - 要将套接字绑定到的本地端口
     host - 主机名,或者为 null,表示回送地址。

方法:
        void close() 关闭此套接字。
     boolean isBound() 返回套接字的绑定状态。
     boolean isClosed() 返回套接字的关闭状态。
      String toString() 将此套接字转换为 String。
         int getPort() 返回此套接字连接到的远程端口。
         int getLocalPort() 返回此套接字绑定到的本地端口。
        void shutdownOutput() 禁用此套接字的输出流。
 InetAddress getInetAddress() 返回套接字连接的地址。
 InputStream getInputStream() 返回此套接字的输入流。
OutputStream getOutputStream() 返回此套接字的输出流。
void connect(SocketAddress endpoint) 将此套接字连接到服务器。
--------------------------------------------------------------------------------

1、大写字母转换服务器

需求:
客户端给服务端发送文本数据(字母),服务端将数据转换成大写字母返回给客户端。
而且客户端可以不断的进行文本转换,当客户端输入结束标记如"over"时,关闭服务端。

分析:
客户端:既然是操作设备上的数据,那么就可以使用IO技术,并按照IO的操作规律来思考。
源:键盘录入。
目标:网络设备,即网络输出流。
操作的是文本数据,可以使用字符流。

步骤:
1)建立Socket服务。
2)获取键盘录入的数据。
3)将数据发送给服务端。
4)获取服务端返回的大写数据。
5)关闭资源。

都是文本数据,可以使用字符流进行操作,为提高效率,加入缓冲。
客户端和服务端都使用阻塞式方法。

服务端:
源:Socket读取流。
目标:Socket输出流。

实例6

import java.io.*;
import java.net.*;
//客户端
class TransClient{
	public static void main(String[] args) throws Exception { 
		//创建客户端套接字,绑定到指定主机的指定端口。
		Socket s = new Socket("192.168.0.100",10005);

		//从键盘录入数据,转换为字符流,存入字符读取流缓冲区bufr。
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		
		//BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		
		//直接使用打印流接收套接字中的输出流。true:println方法将刷新输出缓冲区
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		
		//获取套接字中的输入流,转换成字符流,存入字符读取流缓冲区bufIn。
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		String line = null;
		//从字符读取流缓冲区bufIn读取一行数据
		while((line=bufr.readLine())!=null){
			if("over".equals(line))	
				break;
			//bufOut.write(line);				//把数据发给服务端。
			//bufOut.newLine();					//添加行结束标记
			//bufOut.flush();					//刷新缓冲区
			//使用打印流技术就省了以上三句。
			//用打印流的println方法把读到的数据打印到字符打印流中。此方法自动换行自动刷新缓冲区。
			out.println(line);
			//获取缓冲区bufIn中的一行数据,打印到控制台。
			System.out.println("服务器返回的数据:"+bufIn.readLine());
		}
		bufr.close();
		s.close();
	}
}
//服务端
class TransServer{
	public static void main(String[] args) throws Exception {
		//创建服务器套接字,绑定到指定端口。
		ServerSocket ss = new ServerSocket(10005);
		
		//侦听并接受到此套接字的连接。返回新套接字s,accept方法在接收到连接之前一直阻塞。
		Socket s = ss.accept();
		
		//从套接字中获取连接进来的IP
		String ip = s.getInetAddress().getHostAddress();
		System.out.println("IP="+ip);//测试

		//获取套接字输入流中的数据,转换成字符流,存到字符读取流缓冲区。
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		//把数据写入套接字输出流,存到写入流缓冲区。
		//BufferedWriter bufOut= new BufferedReader(new OutputStreamWriter(s.getOutputStream()));
		
		//用打印流技术替换上一句,字节流字符流都能接收,而且自动刷新。
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);		
		String line = null;
		//从字符读取流缓冲区bufIn读取一行数据
		while((line=bufIn.readLine())!=null){
			//把读到的数据转换成大写打印到字符打印流中。就是发给客户端
			out.println(line.toUpperCase());
		}
		s.close();
		ss.close();
	}
}


2、TCP上传文件

实例7

import java.io.*;
import java.net.*;
//客户端
class UpLoadText{
	public static void main(String[] args) throws Exception {
		Socket s = new Socket("192.168.0.100",10010);
		BufferedReader bufr = new BufferedReader(new FileReader("NoteBook.java"));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		
		String line = null;
		while((line=bufr.readLine())!=null){
			out.println(line);
		}
		s.shutdownOutput();//禁用客户端的输出流,相当于给流中加入结束标记-1。
		
		//创建输入流,获取服务端的反馈信息,打印到控制台
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		String str = bufIn.readLine();
		System.out.println(str);
		bufr.close();
		s.close();
	}
}
//服务端
class TextServer{
	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("NoteBook_upload.java"),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();
	}
}


3、TCP上传图片到服务器

流程分析:
1)建立服务端点
2)读取客户端已有的图片数据
3)通过Socket输出流将数据发给服务端
4)读取服务端反馈信息
5)关闭资源

实例8

import java.io.*;
import java.net.*;
class PicClient{
	public static void main(String[] args) throws Exception{
		Socket s = new Socket("192.168.0.100",10011);
		FileInputStream fis = new FileInputStream("F:\\laola.bmp");
		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 num = in.read(bufIn);
		System.out.println(new String(bufIn,0,num));
		fis.close();
		s.close();
	}
}
//服务端
class PicServer{
	public static void main(String[] args) throws Exception{
		ServerSocket ss = new ServerSocket(10011);
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println("来自IP:"+ip);

		InputStream in = s.getInputStream();
		FileOutputStream fos = new FileOutputStream("laola_Demo.bmp");
		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();
		ss.close();
	}	
}

4、优化TCP上传图片

以上这个服务端有个局限性,当A客户连接上以后,被服务端获取到,服务端执行具体流程,
这时候B客户连接,只有等待,因为服务端还没有处理完A客户的请求,还要循环回来执行下
一次accept方法,所以暂时获取不到B客户对象。

为了可以让多个客户端同时冰法访问服务端,那么服务端最好就是将每个客户端封装到一个
单独的线程中,这样就可以同时处理多个客户端请求。

如何定义线程呢?
只要明确每个客户端要在服务端执行的代码即可,将该代码存入run方法中。

实例9

import java.io.*;
import java.net.*;
class PicClient{
	public static void main(String[] args) throws Exception{
		//测试:用利用主函数传递文件名称
		if(args.length!=1){
			System.out.println("请选择一个图片文件");
			return ;
		}
		//判断是否是文件或是否存在
		File file = new File(args[0]);
		if(!(file.exists()&&file.isFile())){
			System.out.println("该文件不存在或不是文件");
			return ;
		}
		//判断文件名不不是.bmp格式
		if(!file.getName().endsWith(".bmp") && !file.getName().endsWith(".jpg")){
			System.out.println("图片格式只支持jpg/bmp");
			return ;
		}
		//判断文件大小
		if(file.length()>1024*1024*10){
			System.out.println("文件太大,必须小于10M");
			return ;
		}
		//以上判断都满足,就创建ServerSocket对象
		Socket s = new Socket("192.168.0.100",10012);
		FileInputStream fis = new FileInputStream("F:\\laola.bmp");
		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 num = in.read(bufIn);
		System.out.println(new String(bufIn,0,num));
		fis.close();
		s.close();
	}
}

//服务端
class PicThread implements Runnable{
	private Socket s;
	PicThread(Socket s){
		this.s = s;
	}
	public static void main(String[] args) throws Exception{	
		ServerSocket ss = new ServerSocket(10012);
		//循环判断,连进来一个客户端就新建一个线程
		while(true){
			Socket s = ss.accept();
			new Thread(new PicThread(s)).start();
		}
		//服务器一般是不关闭的
		//ss.close();
	}
	public void run(){
		//定义局部变量
		int count = 1;
		String ip = s.getInetAddress().getHostAddress();
		try{
			System.out.println(ip+"请求连接");
			InputStream in = s.getInputStream();
			//定义上传后的图片名称
			File file = new File(ip+"("+count+").jpg");
			while(file.exists())
				file = new File(ip+"("+(count++)+").jpg");
			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 (Exception e){
			throw new RuntimeException(ip+" 上传失败");
		}
	}	
}

 

5、TCP客户端并发登录

客户端通过键盘录入用户名,服务端对这个用户名进行效验
如果该用户存在,在服务端显示 XXX已登录,并在客户端显示 xxx欢迎光临。
如果该用户不存在,在服务端显示 XXX,尝试登录,并在客户端显示 xxx该用户不存在。

最多登录三次就不给登录了。

实例10

import java.io.*;
import java.net.*;
class LoginClient{
	public static void main(String[] args) throws Exception{
		Socket s = new Socket("192.168.0.100",10013);
		//读取键盘录入的数据
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);	
		//读取服务器返回的信息
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		for(int x=0;x<3;x++){
			//如果读到空,就跳出循环。
			String line = bufr.readLine();
			if(line==null)
				break ;
			out.println(line);
			String info = bufIn.readLine();
			System.out.println(info);
			if(info.contains("0"))
				break ;
			//如果服务端返回信息中包含”欢迎“就表示登录成功,结束登录循环次数
			if(info.contains("欢迎"))
				break ;
		}
		bufr.close();
		s.close();
	}
}

//服务端
class UserThread implements Runnable{
	private Socket s;
	UserThread(Socket s){
		this.s = s;
	}
	public void run(){
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+" 已连接进来");
		try{
			for(int x=0;x<3;x++){
				//读取客户端发过来的用户名
				BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
				String name = bufIn.readLine();
				BufferedReader bufr = new BufferedReader(new FileReader("UserInfo.txt"));
				PrintWriter out = new PrintWriter(s.getOutputStream(),true);
				String line = null;

				//定义一个是否存在的标记
				boolean flag = false;
				//从数据库文件中一行一行读取,进行判断
				while((line=bufr.readLine())!=null){
					//如果该name存在数据库中
					if(line.equals(name)){
						//标记为true,表示存在就登录成功
						flag = true;
						break ;
					}
				}
				//如果用户名存在
				if(flag){
					//在服务端发送提示
					System.out.println(name+" 已登录");
					//在客户端发送提示
					out.println(name+" 欢迎光临");
					break ;
				}
				//不存在
				else{
					//在服务端发送提示
					System.out.println(name+" 正在尝试登录");
					//在客户端发送提示
					out.println(name+"用户名不存在 ");
				}
			}
			//关闭客户端连接
			s.close();
		}
		catch (Exception e){
			throw new RuntimeException(ip+"校验失败");
		}
	}

}
class LoginServer{
	public static void main(String[] args) throws Exception{	
		ServerSocket ss = new ServerSocket(10013);
		while(true){
			Socket s = ss.accept();
			new Thread(new UserThread(s)).start();
		}
	}
}


6、一个简单的服务器

实例11

import java.io.*;
import java.net.*;
class Server{
	public static void main(String[] args) throws Exception{
		ServerSocket ss = new ServerSocket(11000);
		Socket s = ss.accept();
		//获取客户端的IP
		System.out.println(s.getInetAddress().getHostAddress());
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		//向客户端发送数据
		out.println("<font color='red' size='7'>客户端你好</font>");
		s.close();
		ss.close();
	}	
}


五、URL (URLConnection)

URL(Uniform Resource Locator)的缩写。
URL类代表一个统一资源定位符,被称为网页地址,它是指向互联网“资源”的指针。
资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。

通常URL可分成几个部分:
示例:http://www.wgxin.com:80/index.shtml

上面的URL示例指示使用的协议为 http(超文本传输协议)
并且该信息驻留在一台名为 www.wgxin.com 的主机上。
主机上的信息名称为:index.shtml
主机上此名称的准确含义取决于协议和主机。
该信息一般存储在文件中,但可以随时生成,该URL的这一部分称为路径。

URL 可选择指定一个“端口”,它是用于建立到远程主机 TCP 连接的端口号。
如果未指定该端口号,则使用协议默认的端口。例如,http 协议的默认端口为 80。
还可以指定一个备用端口,如下所示:
http://www.wgxin.com:80/index.shtml

构造方法:

根据 String 表示形式创建 URL 对象。
URL(String spec)

根据指定 protocol、host、port 号和 file 创建 URL 对象。          
URL(String protocol, String host, int port, String file)

根据指定的 protocol、host、port 号、file 和 handler 创建 URL 对象。      
URL(String protocol, String host, int port, String file, URLStreamHandler handler)

根据指定的 protocol 名称、host 名称和 file 名称创建 URL。    
URL(String protocol, String host, String file)

通过在指定的上下文中对给定的 spec 进行解析创建 URL。          
URL(URL context, String spec)

通过在指定的上下文中用指定的处理程序对给定的 spec 进行解析来创建 URL。          
URL(URL context, String spec, URLStreamHandler handler)

构造方法参数:
spec - 将作为URL解析的String。
host - 主机名称。
port - 主机端口号。
file - 主机上的文件
handler - URL 的流处理程序。
context - 要在其中解析规范的上下文。
protocol - 要使用的协议名称。

方法:
 String getRef()  获取此 URL 的锚点(也称为“引用”)。
 String getFile() 获取此 URL 的文件名。
 String getHost() 获取此 URL 的主机名(如果适用)。
 String getPath() 获取此 URL 的路径部分。
    int getPort() 获取此 URL 的端口号。
    int hashCode() 创建一个适合哈希表索引的整数。
 String getQuery() 获取此 URL 的查询部分。
 Object getContent() 获取此 URL 的内容。
 String getUserInfo() 获取此 URL 的 userInfo 部分。
 String getProtocol() 获取此 URL 的协议名称。
 String getAuthority() 获取此 URL 的授权部分。
    int getDefaultPort() 获取与此 URL 关联协议的默认端口号。 
 Object getContent(Class[] classes) 获取此 URL 的内容。
boolean equals(Object obj) 比较此 URL 是否等于另一个对象。

需求:获取URL的各个参数

实例12

import java.net.*;
class URLDemo{	
	public static void sop(Object obj){
		System.out.println(obj);
	}
	public static void main(String[] args) throws Exception{
		//该URL ?后面的参数是加上去的	
		URL url = new URL("http://www.wgxin.com/index.shtml?name=zhangsan&age=20#30");
		sop("URL授权部分:"+url.getAuthority());
		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());
		sop("URL的锚点:"+url.getRef());
		sop("与URL关联协议的默认端口号:"+url.getDefaultPort());
		sop("userInfo部分:"+url.getUserInfo());
		sop("构造此URL的字符串表示形式:"+url.toExternalForm());
		sop("返回与此URL等效的URI:"+url.toURI());
		//sop("URL的内容:"+url.getContent()); //这是无效的URL,所以使用该方法会报错
	}
}
/*
运行结果:
URL授权部分:www.wgxin.com
URL的协议:http
URL的IP地址:www.wgxin.com
<pre class="java" name="code">URL端口号:-1
URL文件路径:/index.shtmlURL文件名称:/index.shtml?name=zhangsan&age=20URL查询部分:name=zhangsan&age=20URL的锚点:30与URL关联协议的默认端口号:80userInfo部分:null构造此URL的字符串表示形式:http://www.wgxin.com/index.shtml?name=zhangsan&age=20#30返回与此URL等效的URI:http://www.wgxin.com/index.shtml?name=zhangsan&age=20#30*/
 
 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值