------
Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
还记得以前在学校的时候,学习网络编程,用UDP写个聊天室,然后在教室里通过局域网接收同学们的消息,然后也向其他人发送消息,玩得不亦乐乎。如今,再次拾起旧日情怀,重新学习网络编程的知识,只是时光变迁,已是七年之后的事了,那时是学的C#,而现在却是Java,那时正值青春年少,羞涩旧少年,如今年岁渐长,已为人夫人父。唏嘘。。。。。
好了,闲话少叙,上面只是个人的一点感叹而已,人得向前看。。。。。下面进入正题
当我们要写某个友人寄东西时,应该知道那人的具体地址,比如具体到四楼410,可这还不够,因为这里同可能不止一个人,所以还要加上收件人。而当我们在网络上要联系哪个人时,要保证网络之间能够畅通,就是要给友人寄东西一样,要确保邮件能送到,网络也是一样。我们寄东西通常选择快递或邮局,比如中通,顺风,快递之间动作不一样,比如顺风一般三天内必到,甚至隔天就到,而其他快递就慢多了。。。而网络之间也是一样,这就有了网络协议和端口的概念。
两台电脑之间要通讯:
1、找到对方 IP
2、数据要发送到对方指定的应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。
为了方便称呼这个数字,叫做端口。逻辑端口。
3、定义通信规则。这个通讯规则称为协议。国际组织定义了通用协议:TCP/IP
2、数据要发送到对方指定的应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。
为了方便称呼这个数字,叫做端口。逻辑端口。
3、定义通信规则。这个通讯规则称为协议。国际组织定义了通用协议:TCP/IP
如下:
而一个网络应用程序通常会对应一个或多个标识,即接口
所以,从上面可以看出,网络通信有三大要素
网络通信三要素:IP地址,端口号,传输协议
一、IP地址
a、它是网络中的设备标识
b、不易记忆,可用主机名表示,两者存在映射关系
c、本机回环地址:127.0.0.1,主机名为:localhost。
IP地址:java中对应的是InetAddress类,存在于java.net包中。
InetAddress类:
(一)无构造函数,可通过getLocalHost()方法获取InetAddress对象,此方法是静态的,返回本类对象。
InetAddress i = InetAddress.getLocalHost();
(二)方法:
1)static InetAddress getByName(String host):获取指定主机的IP和主机名。(最好用ip地址去获取,主机名需要解析)
2)static InetAddress[] getAllByName(String host):在给定主机名的情况下,根据系统上配置的名称服务返回IP地址所组成的数组。返回对象不唯一时,用此方法。
3)String getHostAddress():返回IP地址字符串文本形式,以IP地址为主。
4)String getHostName():返回IP地址主机名。
(三)如何获取任意一台主机的IP地址对象:
1)功能:返回InetAddress对象
2)对于任意主机,需要指定传入主机名的参数
注意:如果IP地址和对应的主机名,这种映射关系没有在网络上,就不会解析成功,返回的还是指定的IP。
二、端口号
用于标识进程的逻辑地址,不同进程的标识
有效端口:0~65535,其中0-1024系统使用或保留端口
三、传输协议
通讯的规则
常见协议:TCP,UDP
代码:
import java.net.*;
class IpDemo
{
public static void main(String[] args) throws Exception
{
//通过InetAddress类的静态方法获取对象
InetAddress i=InetAddress.getLocalHost();
//获取主机的IP地址
System.out.println(i.getHostAddress());
//获取主机名
System.out.println(i.getHostName());
System.out.println(i.toString());
//获取任意一台主机
InetAddress ia=InetAddress.getByName("www.baidu.com");
sop("IpAddress:"+ia.getHostAddress());//获取主机地址
sop("Name:"+ia.getHostName());//获取主机名
InetAddress[] ias=InetAddress.getAllByName("www.baidu.com");//获取所有主机名
for(InetAddress ad:ias)
{
sop(ad.toString());
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
其中,端口范围是0~65535,其中0~1024一般被系统程序保留。
网络参考模型:
一般来说开发处于传输层和网际层,应用层为:FTP和HTTP协议等,传输层为:UDP和TCP等,网际层为:IP。
通常用户操作的是应用层,而编程人员需要做的是传输层和网际层,用户在应用层操作的数据,经过逐层封包,最后到物理层发送到另一个模型中,再进行逐层解包,如图:
通常用户操作的是应用层,而编程人员需要做的是传输层和网际层,用户在应用层操作的数据,经过逐层封包,最后到物理层发送到另一个模型中,再进行逐层解包,如图:
传输协议(TCP和UDP)
UDP特点:
A、将数据及源和目的封装成数据包中,不需要建立连接
B、每个数据包的大小限制在64K内
C、因无连接,是不可靠协议
D、不需要建立连接,速度快。
TCP特点:
A、建立连接,形成传输数据的通道。
B、在连接中进行大数据量传输
C、通过三次握手完成连接,是可靠协议
D、必须建立连接,效率稍低
说到UDP和TCP,就不能不说另一个对象:Socket
Socket就是为网络服务提供的一种机制。
通信的两端都有Socket。网络通信其实就是Socket 间的通信。数据在两个Socket间通过IO传输。
通信的两端都有Socket。网络通信其实就是Socket 间的通信。数据在两个Socket间通过IO传输。
下面我们先来说UDP
从UDP的特点可以看到,是将数据封装成包,那么,这里就涉及到了两个类,DatagramSocket和DatagramPacket
DatagramSocket:用来发送和接收数据报包的套接字。
DatagramPacket:此类表示数据报包。
而接收端要将数据发送出去,客户端要接收数据,都会用到DatagramSocket的两个方法
void send(DatagramPacket p) 从此套接字发送数据报包。
void receive(DatagramPacket p) 从此套接字接收数据报包。
void receive(DatagramPacket p) 从此套接字接收数据报包。
而数据报包的定义:DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号,其常见方法有:
byte[] getData() 返回数据缓冲区。
int getLength() 返回将要发送或接收到的数据的长度。
int getPort() 返回某台远程主机的端口号
InetAddress getAddress() 返回某台机器的 IP 地址
int getLength() 返回将要发送或接收到的数据的长度。
int getPort() 返回某台远程主机的端口号
InetAddress getAddress() 返回某台机器的 IP 地址
如下:
import java.net.*;
/*
需求:通过UDP传输方式,将一段文字数据发出去
思路:
1、建立UDP的Socket 服务
2、提供数据,并将数据封装到数据包中
3、通过Socket服务的发送功能,将数据包发出去
4、关闭资源
*/
class UdpSend
{
public static void main(String[] args) throws Exception
{
method_Send();
}
public static void method_Send() throws Exception
{
//1、创建UDP服务,通过DatagramSocket对象
DatagramSocket ds=new DatagramSocket();
//2、确定数据,并封装成数据包。DatagramPacket(byte[] byte,int length,InetAddress address,int port)
byte[] byt="UDP~我来了。。。".getBytes(); //将字符串转换成字节数据
DatagramPacket dp=new DatagramPacket(byt,byt.length,InetAddress.getByName("127.0.0.1"),10000);
//3、通过Socket服务,将已有的数据包发送出去。通过Send 方法
ds.send(dp);
//4、关闭资源
ds.close();
}
}
/*
需求:定义一个应用程序,用于接收UDP协议传输的数据并处理。
定义UDP的接收端
思路:
1、定义udp的Socket服务。通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识,
方便于明确哪些数据过来该应用程序可以处理。
2、定义一个数据包,用于存储接收到的字节数据。
3、通过Socket服务的receive方法将收到的数据存入已定义好的数据包中。
4、通过数据包对象的特有功能。将这些不同的数据取出。打印在控制台上。
5、关闭资源
*/
class UdpRece
{
public static void main(String[] args) throws Exception
{
method_Receive();
}
public static void method_Receive() throws Exception
{
//1、创建udp 的Socket服务。建立端点
DatagramSocket ds=new DatagramSocket(10000);
while(true)
{
//2、定义数据包,用于存储数据
byte[] byt=new byte[1024];
DatagramPacket dp=new DatagramPacket(byt,byt.length);
//3、通过服务的receive方法将接收到的数据存入数据包中
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+":"+port+"--->"+data);
}
//ds.close();
}
}
再看下面两个UDP的例子
/*
创建一个发送端和一个接收端,其他,发送端从键盘发送数据
*/
import java.net.*;
import java.io.*;
class MyUdpSend
{
public static void main(String[] args)
{
method_Send();
}
//发送端
public static void method_Send()
{
BufferedReader br=null;
DatagramSocket ds=null;
try
{
//创建一个Socket服务
ds=new DatagramSocket();
//从键盘接收数据
br=new BufferedReader(new InputStreamReader(System.in));
byte[] byt=new byte[1024];
String line=null;
while((line=br.readLine())!=null)
{
if("over".equals(line))
break;
//将字符串转换成字节数组
byt=line.getBytes();
//创建数据包对象
DatagramPacket dp=
new DatagramPacket(byt,byt.length,InetAddress.getByName("127.0.0.1"),10001);
//将准备好的数据包发送出去
ds.send(dp);
}
}
catch(Exception e)
{
sop(e.toString());
}
finally
{
try
{
if(br!=null)
br.close();
}
catch(Exception e)
{
sop(e.toString());
}
finally
{
try{
if(ds!=null)
ds.close();
}
catch(Exception e)
{
sop(e.toString());
}
}
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
class MyUdpRece
{
public static void main(String[] args)
{
method_Rece();
}
public static void method_Rece()
{
try
{
//接收端的Socket服务
DatagramSocket ds=new DatagramSocket(10001);
while(true)
{
byte[] byt=new byte[1024];
//定义一个空的数据包,用于存入接收的数据
DatagramPacket dp=new DatagramPacket(byt,byt.length);
ds.receive(dp);//接收数据
//根据数据包对象获取发送端的IP地址和数据
String ip=dp.getAddress().getHostAddress();
String data=new String(dp.getData(),0,dp.getLength());
int port=dp.getPort();//获取端口
sop(ip+":"+port+"-->"+data);
}
}
catch(Exception e)
{
sop(e.toString());
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
结果:
另一个例子(创建一个UDP聊天室) 唉,可惜就我一人,没法试验。。。难现当年场景。。。。。
/*
实现一个聊天室功能,使用UDP和多线程
*/
import java.net.*;
import java.io.*;
class ChatDemo
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds=new DatagramSocket(8080);
DatagramSocket ds1=new DatagramSocket(10005);
new Thread(new Send(ds)).start();
new Thread(new Send(ds1)).start();
}
}
/*
发送端:
1、定义一个发送类,实现Runnable接口
2、构造函数接收一个DatagramSocket对象
3、重写run方法,在run方法里,定义数据包,将数据封装进数据包里,定义一个读取流,从键盘获取数据
4、通过Socket服务将数据包发送出去
5、关闭资源
*/
class Send implements Runnable
{
private DatagramSocket ds;
Send(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
BufferedReader br=null;
try
{
//从键盘接收数据
br=new BufferedReader(new InputStreamReader(System.in));
//准备数据,从键盘接收数据
String line=null;
byte[] byt=new byte[1024];
while((line=br.readLine())!=null)
{
byt=line.getBytes();
DatagramPacket dp=new DatagramPacket(byt,byt.length,InetAddress.getByName("127.0.0.1"),10005);
ds.send(dp);
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
finally
{
try
{
if(br!=null)
br.close();
}
catch(Exception E)
{
System.out.println(E.toString());
}
}
}
}
/*
接收端
1、定义接收类,实现 Runnable接口
2、构造函数接收一个DatagramSocket对象
3、重写run方法,定义一个DatagramPacket数据包对象,定义一个字节数组,用于存放接收的数据
4、通过Socket服务接收数据,并将数据存入数据包中
5、关闭资源
*/
class Rece implements Runnable
{
private DatagramSocket ds;
Rece(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
try
{
byte[] byt=new byte[1024];
DatagramPacket dp=new DatagramPacket(byt,byt.length);
//接收数据
ds.receive(dp);
String ip=dp.getAddress().getHostAddress();
int port=dp.getPort();
String data=new String(dp.getData(),0,dp.getLength());
System.out.println(ip+":"+port+"--->"+data.toUpperCase());
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
TCP传输
A、Socket和 ServerSocket
B、建立客户端和服务端
C、建立连接后,通过Socket中的IO流进行数据的传输。
D、关闭Socket
同样,客户端和服务端是两个独立的应用程序
客户端
通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机。因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功。形成通路后,在该通道进行数据的传输。
通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机。因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功。形成通路后,在该通道进行数据的传输。
服务端
1、建立服务端的Socket服务.ServerSocket,并监听一个端口
2、获取连接过来的客户端对象。通过ServerSocket的accept方法,没有连接就会等, 所以这个方法是阻塞式的。
3、客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流,来读取发过来的数据,并打印在控制台上。
4、关闭服务端(可选) 关闭客户端对象
1、建立服务端的Socket服务.ServerSocket,并监听一个端口
2、获取连接过来的客户端对象。通过ServerSocket的accept方法,没有连接就会等, 所以这个方法是阻塞式的。
3、客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流,来读取发过来的数据,并打印在控制台上。
4、关闭服务端(可选) 关闭客户端对象
如下:
/*
客户端
通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机。
因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,
并连接成功。形成通路后,在该通道进行数据的传输。
步骤:
1、创建Socket服务,并指定要连接的主机和端口。
*/
import java.io.*;
import java.net.*;
class TcpClient
{
public static void main(String[] args)
{
Socket s=null;
try
{
//创建客户端的Socket服务。指定目的主机和端口
s=new Socket("127.0.0.1",10003);
//为了发送数据,应该获取Socket流中的输出流
OutputStream os=s.getOutputStream();
os.write("TCP,Im Coming".getBytes());
}
catch(Exception e)
{
System.out.println(e.toString());
}
finally
{
try{
if(s!=null)
s.close();
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
}
/*
服务端
1、建立服务端的Socket服务.ServerSocket,并监听一个端口
2、获取连接过来的客户端对象。通过ServerSocket的accept方法,没有连接就会等,
所以这个方法是阻塞式的。
3、客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流
来读取发过来的数据,并打印在控制台上。
4、关闭服务端(可选) 关闭客户端对象
*/
class TcpServer
{
public static void main(String[] args)
{
try
{
//建立服务端Socket服务,并监听一个端口
ServerSocket ss=new ServerSocket(10003);
//通过accept方法获取连接过来的客户端对象
Socket s=ss.accept();
//获取客户端信息
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
//获取客户端发送过来的数据,要使用客户端对象的读取流来读取数据
byte[] byt=new byte[1024];
InputStream is=s.getInputStream();
int leng=is.read(byt);
System.out.println(new String(byt,0,leng));
}
catch(Exception e)
{
System.out.println(e.toString());
}
finally
{
}
}
}
Tcp建立连接需要经过三次握手,但上面只经过了两次。
客户端请求连接,服务端建立连接后,服务端就获取了客户端的Socket对象,这样,服务端就能使用客户端的Socket对象的输入输出流。
其流程如下 1、 客户端Socket ---------------------------连接-------------------------------------->服务端ServerSocket
2、服务端监听端口----------------------------建立连接-------------------------------->客户端
3、服务端监听端口-----------------------------发送反馈信息-------------------------->客户端
在上面的三步中,服务端要首先开启,监听端口,创建ServerSocket服务,然后客户端创建Socket服务,创建Socket流对象,连接服务器,同时,获取Socket流的输出流对象,将数据发送到服务端。
在第2步中,服务端通过ServerSocket服务的accept()方法,获取客户端的Socket流对象。然后获取Socket的读取流,读取客户端发送至的数据,然后通过Socket 的输出流,将反馈信息发送至客户端
第3步,通过Socket 流的读取流,来接收服务端发送过来的反馈信息。最后关闭资源。
如下图:
来看一个练习:
/*
需求:建立一个文本转换服务器
客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。
而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束
分析
客户端:
既然是操作设备上的数据,那么就可以使用IO技术,并按照IO的操作规律来思考。
源:键盘录入
目的:网络设备,网络输出流
而且操作的文本数据,可以选择字符流
步骤:
1、建立服务。
2、获取键盘录入
3、将数据发给服务端
4、获取服务端返回的大写数据
5、结束,关闭资源
都是文本数据,可以使用字符流进行操作,同时为提高效率,加入缓冲
*/
import java.io.*;
import java.net.*;
class TransClient
{
public static void main(String[] args) throws Exception
{
Socket s=new Socket("127.0.0.1",10005);
//源,从键盘获取录入的数据
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
//建立一个客户端 Socket 的输出流(定义目的,将数据写入到Socket输出流,发给服务端)
BufferedWriter bufOut=
new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//定义一个Socket读取流,读取服务端返回的大写信息
BufferedReader bufIn=
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
//从键盘读取
while((line=br.readLine())!=null)
{
if("over".equals(line))
break;
bufOut.write(line); //将数据发到服务端去
bufOut.newLine();
bufOut.flush();
String str=bufIn.readLine(); //读取服务端反馈回的数据信息
System.out.println("Server:"+str);
}
br.close();
s.close();
}
}
/*
服务端
源:Socket读取流
目的:Socket输出流
都是文本,装饰
*/
class TransServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss=new ServerSocket(10005);
//获取客户端的Socket对象
Socket s=ss.accept();
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
//获取客户端对象的读取流,读取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)
{
System.out.println(line);
bufOut.write(line.toUpperCase());
bufOut.newLine();
bufOut.flush();
}
s.close();
ss.close();
}
}
结果:
再看下面一个例子,里面涉及了程序阻塞问题
两个方法:
void shutdownOutput() 禁用此套接字的输出流
void shutdownInput() 此套接字的输入流置于“流的末尾”。
/*
客户端
思路:
1、创建Socket服务,并指定主机的地址和端口
2、既然是上传文件,那么要创建文件读取流,用来读取源文件,将其作为要发送到服务端的数据
3、获取Socket流的输出流,然后将数据发到服务端
4、关闭客户端
*/
import java.net.*;
import java.io.*;
class UploadClient
{
public static void main(String[] args) throws Exception
{
Socket s=new Socket("127.0.0.1",10008);
//准备数据
FileInputStream fis=new FileInputStream("D:\\构造最全的Java面试题.pdf");
//获取Socket服务的输出流
OutputStream out=s.getOutputStream();
//获取Socket服务的输入流,用于读取服务端的反馈信息
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
//InputStream in=s.getInputStream();
byte[] byt=new byte[1024];
int len=0;
while((len=fis.read(byt))!=-1)
{
out.write(byt,0,len); //将数据发送到服务端
}
s.shutdownOutput();
//接收服务端发送过来的反馈信息
/*
byte[] by=new byte[1024];
int leng=0;
leng=in.read(by);
*/
String str=bufIn.readLine();
System.out.println(str);
//System.out.println(new String(by,0,leng));
fis.close();
s.close();
}
}
/*
服务端
思路:
1、创建服务端的Socket服务,并监听端口
2、获取客户端对象的输入流
3、创建文件输出流,用于存储文件数据
4、通过对象的输入流读取客户端发送过来的数据,再通过文件输出流保存为文件。
5、获取客户端对象的输出流,输出反馈信息到客户端
5、关闭文件输出流对象资源和客户端
*/
class UploadServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss=new ServerSocket(10008);
Socket s=ss.accept(); //获取客户端对象
//获取客户端信息
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"...connected");
//定义文件目的地
FileOutputStream fos=new FileOutputStream("E:\\构造最全的Java面试题.pdf");
//获取客户端对象的读取流
BufferedInputStream bufIn=new BufferedInputStream(s.getInputStream());
//获取客户端对象的输出流,用于给客户端发送反馈信息
BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//OutputStream out=s.getOutputStream();
byte[] byt=new byte[1024];
int len=0;
while((len=bufIn.read(byt))!=-1)
{
fos.write(byt,0,len);
}
bufOut.write("上传成功!");
bufOut.flush();
//out.write("上传成功".getBytes());
fos.close();
s.close();
}
}
如果上面代码中没有s.shutdownOutput() 这句,那么当客户端读完数据,完成发送后,但是服务器那边没有读到表示到达末尾的-1,所以依然会循环读取,那么,就不会执行下面的发送反馈信息给客户端,而客户端在执行到in.read()时进行阻塞状态,等待服务端发送反馈信息。而在这里添加了这句以后,当客户端完成发送数据后,通过shutdownOutput()往服务端发送了一个-1,告诉服务端已发完数据,这时服务端的while语句就会读取到-1,完成循环,发送反馈信息。
从上面的代码里,我们可以看出,上面的代码只能一个一个地上传文件,当A客户端连接服务端,被服务端获取到,服务端执行具体流程。这时B客户端如果连接,只有等待。因为服务端还没有处理完A客户端的请求,还有循环回来执行下次accept方法,所以暂时获取不到B客户端对象。A上传文件成功后,B才能连上服务器,而做不到A和B同时连接服务端,同时上传文件。
那么为了让多个客户端同时并发访问服务端,那么服务端最好就是将每个客户端封装一个单独的线程中,这样,就可以同时处理多个客户端请求。
代码:
import java.net.*;
import java.io.*;
class PicThread implements Runnable
{
Socket s;
PicThread(Socket s)
{
this.s=s;
}
//复写run方法
public void run()
{
String ip=s.getInetAddress().getHostAddress(); //获取客户端IP地址
try
{
//对上传的文件保存名称作设置
System.out.println(ip+"....connected");
int num=1;
File file=new File("临时文件\\"+ip+"("+num+")"+".jpg");
while(file.exists())
file=new File("临时文件\\"+ip+"("+(num++)+")"+".jpg");
//定义一个输出流,用于接收数据,保存为文件
BufferedOutputStream bufos=new BufferedOutputStream(new FileOutputStream(file));
//获取客户端对象的读取流
BufferedInputStream bufIn=new BufferedInputStream(s.getInputStream());
byte[] byt=new byte[1024];
int len=0;
while((len=bufIn.read(byt))!=-1)
{
bufos.write(byt,0,len);
}
//将反馈信息发送至客户端
BufferedOutputStream bufOut=new BufferedOutputStream(s.getOutputStream());
bufOut.write("上传成功!".getBytes());
bufOut.flush();
bufos.close();
s.close();
}
catch(Exception e)
{
System.out.println(ip+"上传失败");
}
}
}
/*
服务端
思路:1、创建服务端Soceket服务,并监听端口
2、通过accept方法获取客户端对象,获取客户端对象的读取流
3、定义一个输出流,用于保存客户端发送过来的数据
4、获取客户端对象的输出流,将反馈信息发送给客户端
5、关闭客户端,关闭输出流
*/
class PicServer
{
public static void main(String[] args) throws Exception
{
//文件从控制台输入地址来获取
//创建服务端Socket服务,并监听端口
ServerSocket ss=new ServerSocket(10009);
while(true)
{
Socket s=ss.accept();
new Thread(new PicThread(s)).start();
}
}
}
/*
客户端
思想:
1、创建客户端的Socket服务,并指定主机和端口
2、获取客户端服务的输出流
3、准备数据,上传图片,使用文件输出流
4、将准备好的数据发送至服务端
5、通过客户端服务的读取流,获取服务端的反馈信息
6、关闭资源
*/
class PicClient
{
public static void main(String[] args) throws Exception
{
//对客户端上传的文件作限制(格式,大小,类型)
if(args.length!=1)
{
System.out.println("请选择一个jpg格式的图片");
return;
}
File file=new File(args[0]);
if(!(file.exists()&&file.isFile()))
{
System.out.println("该文件有问题,要么不存在,要么不是文件");
return;
}
if(!file.getName().endsWith(".jpg"))
{
System.out.println("图片格式错误,请重新选择");
return;
}
if(file.length()>1024*1024*5)
{
System.out.println("文件过大,请重新选择!");
return;
}
Socket s=new Socket("127.0.0.1",10009);
BufferedOutputStream bufOut=new BufferedOutputStream(s.getOutputStream());
BufferedInputStream bufis=new BufferedInputStream(new FileInputStream("client.jpg"));
byte[] byt=new byte[1024];
int len=0;
while((len=bufis.read(byt))!=-1)
{
bufOut.write(byt,0,len); //将数据发送至服务端
}
s.shutdownOutput();//数据已发送完毕
BufferedInputStream bufIn=new BufferedInputStream(s.getInputStream());
byte[] by=new byte[100];
int leng=bufIn.read(by);
String str=new String(by,0,leng);
System.out.println(str);
}
}
练习:客户端并发登录
/*
客户端通过键盘录入用户名
服务端对这个用户名进行校验
如果该用户存在,在服务端显示XXX,已登录。
并在客户端显示XXX,欢迎光临。
如果该用户不存在,在服务端显示XXX,尝试登录
并在客户端显示XXX,该用户不存在。
最多就登录三次。
*/
import java.io.*;
import java.net.*;
/*
客户端
1、创建一个Socket服务,并确定主机和端口
2、通过Socket流,获取输出流对象
3、通过键盘读取输入的用户名
4、获取Socket流的读取流,读取服务端发过来的反馈信息
5、关闭客户端资源
*/
class UserLogin
{
public static void main(String[] args)
{
method();
}
public static void method()
{
try
{
Socket s=new Socket("127.0.0.1",10007);
//接收键盘输入的用户名
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
//获取客户端Socket流的输出流对象
BufferedWriter bwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//获取客户端Socket流的读取流对象
BufferedReader brIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
for(int i=0;i<3;i++)
{
String line=null;
line=br.readLine();
if(line==null)
break;
bwOut.write(line); //将用户名发送到服务端
bwOut.newLine();
bwOut.flush();
String str=brIn.readLine();
System.out.println(str);
if(str.contains("欢迎"))
break;
}
br.close();
s.close();
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
/*
服务端
1、创建Socket服务,并监听端口
2、创建一个读取流对象,读取用户名数据文件
3、获取客户端Socket流对象,并获取其读取流,读取客户端发送至的数据
4、验证用户名是否存在,并反馈相关信息
5、关闭客户端资源
*/
class ServerThread implements Runnable
{
private Socket s;
ServerThread(Socket s)
{
this.s=s;
}
public void run()
{
BufferedReader br=null;
try{
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"...connected");
for(int i=0;i<3;i++)
{
//获取用户名列表文件
br=new BufferedReader(new FileReader("UserList.txt"));
//获取客户端Socket的读取流
BufferedReader brIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
//获取客户端Socket的输出流,用于给客户端发送反馈信息
BufferedWriter bwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String userName=brIn.readLine();
if(userName==null)
break;
String line=null;
boolean flag=false;
while((line=br.readLine())!=null)
{
System.out.println(line+"::"+userName);
if(userName.equals(line))
{
flag=true;
break;
}
}
if(flag)
{
System.out.println(userName+",已登录!");
bwOut.write(userName+",欢迎光临!");
bwOut.newLine();
bwOut.flush();
break;
}
else
{
System.out.println(userName+",尝试登录!");
bwOut.write(userName+",用户不存在!");
bwOut.newLine();
bwOut.flush();
}
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
finally
{
try{
if(s!=null)
{
s.close();
br.close();
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
}
class Server
{
public static void main(String[] args)
{
method();
}
public static void method()
{
try{
ServerSocket ss=new ServerSocket(10007);
while(true)
{
//获取客户端Socket 流对象
Socket s=ss.accept();
//创建线程
new Thread(new ServerThread(s)).start();
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
结果:
自定义服务端(以浏览器作为客户端)
代码:
/*
自定义服务端
*/
import java.net.*;
import java.io.*;
class ServerDemo
{
public static void main(String[] args) throws Exception
{
//创建服务端Socket服务,并监听端口
ServerSocket ss=new ServerSocket(11000);
//获取客户端的Socket流对象
Socket s=ss.accept();
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip);
//BufferedReader brIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedWriter bwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bwOut.write("客户端你好");
bwOut.newLine();
bwOut.flush();
s.close();
ss.close();
}
}
结果:
URL类
类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。
其常见方法有:
String getProtocol() 获取此 URL 的协议名称。
String getHost() 获取此 URL 的主机名(如果适用)。
String getPath() 获取此 URL 的路径部分。
int getPort() 获取此 URL 的端口号。
String getQuery() 获取此 URL 的查询部分。
String getPath() 获取此 URL 的路径部分。
int getPort() 获取此 URL 的端口号。
String getQuery() 获取此 URL 的查询部分。
注:一般输入网址,是不带端口号的,此时可进行获取,通过获取网址返回的port,若port为-1,则分配一个默认的80端口,如
int port = getPort();
if(port == -1)
port = 80;
2、URLConnection
方法:
1)URLConnection openConnection();//用URL调用此方法,返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。
2)InputStream getInputStream();//获取输入流
3)OutputStream getOutputStream();//获取输出流
int port = getPort();
if(port == -1)
port = 80;
2、URLConnection
方法:
1)URLConnection openConnection();//用URL调用此方法,返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。
2)InputStream getInputStream();//获取输入流
3)OutputStream getOutputStream();//获取输出流
代码:
import java.net.*;
class UrlDemo
{
public static void main(String[] args)
{
URL url=null;
try
{
url=new URL("http://www.letv.com/ptv/vplay/20075539.html");
//获取协议名称
sop("getProtocol:"+url.getProtocol());
//获取主机名
sop("getHost:"+url.getHost());
//获取路径部分
sop("getPath:"+url.getPath());
//获取端口
sop("getPort:"+url.getPort());
//获取查询部分
sop("getQuery:"+url.getQuery());
sop("getFile:"+url.getFile());
}
catch(Exception e)
{
sop(e.toString());
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
结果:
练习,使用URL对象的openStream()方法,将指定URL对象的内容读取到一个文件中
InputStream openStream() 打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。
import java.net.*;
import java.io.*;
class URLConnectionDemo
{
public static void main(String[] args)
{
URL url=null;
InputStream in=null;
BufferedOutputStream bos=null;
try
{
url=new URL("http://www.letv.com/ptv/vplay/20075539.html");
URLConnection conn=url.openConnection();
System.out.println(conn);
//获取一个一个URLConnection的读取流
in=conn.getInputStream();
//将读取的数据存入一个TXT文件
bos=new BufferedOutputStream(new FileOutputStream("url.txt"));
byte[] byt=new byte[1024];
int len=0;
while((len=in.read(byt))!=-1)
{
bos.write(byt,0,len);
bos.flush();
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
finally
{
try
{
if(bos!=null)
bos.close();
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
}
结果:
小知识: ServerSocket
构造函数
ServerSocket(int port, int backlog) 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
这里的backlog参数:
backlog
- 队列的最大长度。即连接到服务器端的客户端最大个数
在浏览器输入网址访问一台主机所做的操作:
如输入http://61.135.169.125,可以直接连接此ip的主机,而我们一般是输入主机名:http:/www.baidu.ocm(百度主机对应的ip地址就是:61.135.169.125),那么此时浏览器做了神马操作呢?
也就是说如何通过主机名获取IP地址,从而连接到这台主机呢?这就需要将主机名翻译成IP地址,即域名解析:DNS
在进行访问的时候,会先在本地的hosts文件(c:\windows\system32\drivers\ext\host)中找对应的映射。若有,则直接返回请求;若无,则到公网的映射列表即DNS中找对应的映射,找到后,将主机名对应的IP地址返回给本机,本机通过这个IP地址找到对应的服务器。
host应用:可屏蔽一些恶意网址,即将对应的映射关系写入hosts中,将IP地址改为本机的回环地址,那么会直接找到hosts,就不会将请求发送出去了。