------- android培训、java培训、期待与您交流! ----------
学习笔记10网络编程
>>网络模型
OSI |
TCP/IP |
应用层 |
应用层(JAVAweb开发) |
表示层 |
|
会话层 |
|
传输层 |
传输层 |
网络层 |
网络层 |
数据链路层 |
主机至网络层 |
物理层 |
>>本地回环地址127.0.0.1
端口号:0~65535 分为物理端口和逻辑端口,0~1024为保留和系统端口不建议使用
常见的通信协议为TCP与UDP都用于数据传输。
UDP
将目的和源都创建成数据包并且不需要创建连接。
每个数据包的大小在64k内
不需要创建连接,属于不可靠协议
不需要创建连接,速度快
TCP
建立连接,形成数据传输通道
在连接中进行大数据量的传输
通过三次握手完成连接,是可靠协议(在吗?在!我知道了!)
必须建立连接,效率会稍低
比如视频就用UDP,因为丢几帧没关系,关键是速度,下载TCP的,因为要求质量。
Socket机制,我们说网络编程,其实就是Socket编程。它是为网络服务提供的一种机制。通信的两端都有Socket。网络通信其实就是Socket间的通信。数据在两个Socket键通过IO传输。
>>
java中:
java.net
类 InetAddress
java.lang.Object
java.net.InetAddress
没有构造方法
eg:演示获取ip地址和主机名
import java.net.*;
class netDemo
{
publicstatic void main(String[] args)throws Exception
{
show_2();
}
//获取本地IP地址和计算机名
publicstatic void show_1()throws Exception
{
InetAddressneta = InetAddress.getLocalHost();
Stringip = neta.getHostAddress();
Stringname = neta.getHostName();
System.out.println("ip="+ip+"\n"+"name= "+name);
}
//获取网络上的IP地址和计算机名
publicstatic void show_2()throws Exception
{
InetAddressneta = InetAddress.getByName("192.168.77.221");
//InetAddressneta = InetAddress.getByName("www.baidu.com");
//没有连接成功,就会返回UnknownHostException
//当然有时候一个域名对应了很多ip地址,那么有方法
/*getAllByName
publicstatic InetAddress[] getAllByName(String host)
throwsUnknownHostException
在给定主机名的情况下,根据系统上配置的
名称服务返回其 IP 地址所组成的数组。 */
Stringip = neta.getHostAddress();
Stringname = neta.getHostName();
System.out.println("ip="+ip+"\n"+"name= "+name);
}
/*
这里有一个伏笔:返回的两个结果都是对方的ip地址,因为本机并没有与之对应的关系表
如果要配置这样一个对应关系,可以打开:
C:\Windows\System32\drivers\etc\hosts文件,在其中添加
ip地址和相对应的主机名即可,这个知识点以后会涉及
*/
}
>>UDP的发送和接收
建立发送端,接收端
建立数据包
调用Socket的发送接收方法
关闭Socket
发送端与接收端是两个独立的运行程序
DatagramSocket与DatagramPacket两个类
eg:演示UDP的接收端和发送端,验证时可以使用两个控制台
import java.net.*;
/*
udp发送端的思路:
1,建立udp的socket服务。
2,将要发送的数据封装成数据包。
3,通过udp的socket服务,将数据包发送取出。
4,建议关闭资源。
*/
class UDPSentDemo
{
publicstatic void main(String[] args)throws Exception
{
//1,建立udp服务。
DatagramSocketds = new DatagramSocket(8888);
//如果发送端并没有指定端口,那么会自动分配一个端口。
//2,定义数据内容,并将数据封装成数据包。
byte[]buf = "udp 哥们来了".getBytes();
DatagramPacketdp = new
DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.54.9"),10000);
//3,通过udp的socket服务中的功能,完成数据包的发送。
ds.send(dp);
//4,关闭资源。
ds.close();
}
}
/*
udp的接收端:
思路:
1,建立udpsocket服务。需要该应用程序定义数字标识,也就是端口,
也就是说:让该应用程序监听一个端口,只要是给这个端口发送的数据,
该应用程序就可以进行处理。
2,通过receive方法接收数据。
3,将收到的数据存储到数据包对象中。
4,通过数据包对象的功能来完成对接收到的数据进行解析。
5,可以对资源进行关闭。
*/
class UDPReceDemo
{
publicstatic void main(String[] args)throws Exception
{
//1,定义socket服务,并监听一个端口,明确哪些是是该应用程序可以处理的。
DatagramSocketds = new DatagramSocket(10000);
while(true) //循环保证接收端一直开着
{
//2,预先定义好一个数据包。用于存储接收到的数据。
//因为数据包中必须有一个容器存储这些数据。所以要先定义字节数组。
byte[]buf = new byte[1024];
DatagramPacketdp = new DatagramPacket(buf,buf.length);
//3,使用socket服务的receive方法将接收到的数据,存储到数据包中。
ds.receive(dp);//该方法是阻塞式。
//4,通过数据包对象的方法获取其中的数据内容。包括,地址,端口,数据主体。
Stringip = dp.getAddress().getHostAddress();
intport = dp.getPort();
Stringtext = new String(dp.getData(),0,dp.getLength());//取得发送的数据
System.out.println(ip+":"+port+"....."+text);
}
//5,关闭资源。
ds.close();
}
}
>>结合IO流从键盘键入信息
eg:import java.net.*;
import java.io.*;
class UDPSentDemo
{
publicstatic void main(String[] args)throws Exception
{
DatagramSocketds = new DatagramSocket();
BufferedReaderbufr = new BufferedReader(new InputStreamReader(System.in));
Strings = null;
while((s= bufr.readLine())!=null)
{
byte[]buf = s.getBytes();
DatagramPacketdp = new
DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10000);
//这里如果用广播地址就可以群聊了
ds.send(dp);
if(s.equals("886"))
break;
}
ds.close();
}
}
>>多线程聊天室
eg:发送和接收由不同的线程控制
import java.io.*;
import java.net.*;
class Rece implements Runnable
{
privateDatagramSocket ds;
Rece(DatagramSocketds)
{
this.ds= ds;
}
publicvoid run()
{
try
{
while(true)
{
byte[]buf = new byte[1024];
DatagramPacketdp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
Stringip = dp.getAddress().getHostAddress();
intport = dp.getPort();
Stringtext = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+":"+port+"....."+text);
if(text.equals("886"))
{
System.out.println(ip+"...离开聊天室");
}
}
}
catch(Exception e)
{
thrownew RuntimeException("接收失败");
}
}
}
class Send implements Runnable
{
privateDatagramSocket ds;
Send(DatagramSocketds)
{
this.ds= ds;
}
publicvoid run()
{
try
{
BufferedReaderbufr =
newBufferedReader(new InputStreamReader(System.in));
Stringline = null;
while((line=bufr.readLine())!=null)
{
byte[]buf = line.getBytes();
DatagramPacketdp =
newDatagramPacket(buf,buf.length,InetAddress.getByName("192.168.54.127"),10001);
ds.send(dp);
if("886".equals(line))
break;
}
ds.close();
}
catch(Exception e)
{
thrownew RuntimeException("发送失败 ");
}
}
}
class ChatDemo
{
publicstatic void main(String[] args) throws Exception
{
DatagramSocketsendSocket = new DatagramSocket();
DatagramSocketreceSocket = new DatagramSocket(10001);
newThread(new Send(sendSocket)).start();
newThread(new Rece(receSocket)).start();
}
}
>>TCP的客户端和服务端
服务端只负责提供服务,不负责流的建立,因为客户端有流。
eg:演示TCP客户端向服务端传输一条数据,开启的时候,要先开启服务端
/*
TCP:面向连接,可靠的协议。
客户端和服务端。
客户端:Socket
服务端:ServerSocket
*/
import java.io.*;
import java.net.*;
/*
客户端建立思路:
1,建立客户端的socket服务。并明确要连接的服务端。
因为只有建立了连接,才可以进行通讯。
2,如果连接建立成功,就表明,已经了数据传输的通道。就可以在该通道通过IO进行数据的读取和写入。
该通道称为socket流,socket流中既有读取流,也有写入流。
3,通过socket对象的方法。可以获取这两个流对象。
4,通过流对象就可以对数据进行传输。
5,如果传输数据完毕,关闭资源。
*/
//通过客户端给服务端写点数据。
class ClientDemo
{
publicstatic void main(String[] args) throws Exception
{
//1,建立客户端socket服务。并去进行目的地址的连接。
Sockets = new Socket("192.168.54.9",10002);
//2,通过socket对象的方法getOutputStream获取输出流对象。
OutputStreamout = s.getOutputStream();
//3,将数据写入流中。
out.write("TCP哥们 又来了".getBytes());
//4,关闭资源
s.close();
}
}
/*
建立服务端的思路:
1,建立服务端的socket服务。分配一个端口,用于监听该端口收到的数据。
2,服务端没有直接流的操作,而是通过accept方法获取客户端对象,
在通过获取到的客户端对象的流和客户端进行通讯。
3,通过客户端的获取流对象的方法,读取数据或者写入数据。
4,如果服务完成,需要关闭客户端,然后关闭服务端。
但是一般会关闭客户端,不关闭服务端,因为服务端是一值提供服务的。
*/
class ServerDemo
{
publicstatic void main(String[] args) throws Exception
{
//1, 建立服务端对象,监听一个端口。
ServerSocketss = new ServerSocket(10002);
//2,通过accept方法获取客户端对象。
Sockets = ss.accept();//该方法是阻塞式的。
Stringip = s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");
//3,获取socket的读取流,读取客户端发送过来的数据。
InputStreamin = s.getInputStream();
//4,就是读取数据的基本操作。
byte[]buf = new byte[1024];
intlen = in.read(buf);
Stringtext = new String(buf,0,len);
System.out.println(text);
s.close();
ss.close();
}
}
>>在上述例子的基础上,让客户端和服务端可以通讯
eg:其实就是反向发送信息
import java.net.*;
import java.io.*;
class TCPClient
{
publicstatic void main(String[] args)throws Exception
{
SocketclientSocket = new Socket("127.0.0.1",10000);
OutputStreamosw = clientSocket.getOutputStream();
osw.write("TCP,Iam coming!".getBytes());
//接收返回信息
InputStreamos = clientSocket.getInputStream();
byte[]b = new byte[1024];
intlen = os.read(b);
Stringstr = new String(b,0,len);
System.out.println(str);
clientSocket.close();
}
}
class TCPServer
{
publicstatic void main(String[] args)throws Exception
{
ServerSocketserverSocket = new ServerSocket(10000);
Sockets = serverSocket.accept();
Stringip = s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");
InputStreamins = s.getInputStream();
byte[]bytes = new byte[1024];
intlen = ins.read(bytes);
Stringstr = new String(bytes,0,len);
System.out.println(str);
//发送返回信息
OutputStreamos = s.getOutputStream();
//注意不要写成OutputStreamos = serverSocket.getOutputStream();
os.write("Igot!".getBytes());
s.close();
serverSocket.close();
}
}
>>练习:客户端传输文本,服务端接收并转换成大写发还给客户端
eg:有错误的代码
import java.net.*;
import java.io.*;
class TranClient
{
publicstatic void main(String[] args)throws Exception
{
SocketsocketClient = new Socket("127.0.0.1",10000);
//客户端接收用户数据
BufferedReaderbufr =
newBufferedReader(new InputStreamReader(System.in));
//客户端发送数据给服务端
BufferedWriterbufOutS =
newBufferedWriter(new OutputStreamWriter(socketClient.getOutputStream()));
//客户端接收服务端数据
BufferedReaderbufInS =
newBufferedReader(new InputStreamReader(socketClient.getInputStream()));
Stringstr = null;
while((str= bufr.readLine())!=null)
{
if(str.equals("over"))
{
break;
}
bufOutS.write(str);
Stringline = bufInS.readLine();
System.out.println("servers:"+line);
}
}
}
class TranServer
{
publicstatic void main(String[] args)throws Exception
{
ServerSocketserverSocket = new ServerSocket(10000);//注意不要写成Socket了
Socketss = serverSocket.accept();
//确认客户端的连接
Stringinfo = ss.getInetAddress().getHostAddress();
System.out.println(info+"...connected!");
//读取客户端传输的数据
BufferedReaderbufInS =
newBufferedReader(new InputStreamReader(ss.getInputStream()));
//将数据发送给客户端
BufferedWriterbufOutS =
newBufferedWriter(new OutputStreamWriter(ss.getOutputStream()));
Stringstr = null;
while((str= bufInS.readLine()) != null)
{
System.out.println(“收到数据:”+str);
bufOutS.write(str.toUpperCase());
}
}
}
/*
这个代码看上去好像没有什么问题,编译通过!
分别运行了服务端和客户端
显示:127.0.0.1...connected!也没有问题。
但是在客户端输入数据,服务端没有收到,并且客户端好像被锁定了,不能输入数据
通过Ctrl+c退出,查找原因!
原因:BufferedReader和BufferedWriter是缓冲区,
数据写到内存去,所以,我们忘了刷新!
另外,bufOutS.write(str);这个语句在while((str= bufr.readLine())!=null)循环中,并且,readLine()方法的终止条件是:读取一个文本行。通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。返回的字符中不包含任何行终止符!
所以,即使有刷新,但客户端bufOutS.write(str);语句传出的str是不包含换行符的,服务端的while((str= bufInS.readLine()) != null)在读取的时候没有换行符,就一直认为没有终止,所以造成了客户端服务端都停止!
我们为此加上换行符,使用newLine()方法!
注意:服务端想客户端反馈信息也是同样的道理,要加上换行并刷新!
*/
eg:更正后的代码,加上刷新和换行,关闭资源
import java.net.*;
import java.io.*;
class TranClient
{
publicstatic void main(String[] args)throws Exception
{
SocketsocketClient = new Socket("127.0.0.1",10000);
//客户端接收用户数据
BufferedReaderbufr =
newBufferedReader(new InputStreamReader(System.in));
//客户端发送数据给服务端
BufferedWriterbufOutS =
newBufferedWriter(new OutputStreamWriter(socketClient.getOutputStream()));
//客户端接收服务端数据
BufferedReaderbufInS =
newBufferedReader(new InputStreamReader(socketClient.getInputStream()));
Stringstr = null;
while((str= bufr.readLine())!=null)
{
if(str.equals("over"))
{
break;
}
bufOutS.write(str);
bufOutS.newLine();
bufOutS.flush();
Stringline = bufInS.readLine();
System.out.println("servers:"+line);
}
bufr.close();
socketClient.close();
}
}
class TranServer
{
publicstatic void main(String[] args)throws Exception
{
ServerSocketserverSocket = new ServerSocket(10000);//注意不要写成Socket了
Socketss = serverSocket.accept();
//确认客户端的连接
Stringinfo = ss.getInetAddress().getHostAddress();
System.out.println(info+"...connected!");
//读取客户端传输的数据
BufferedReaderbufInS =
newBufferedReader(new InputStreamReader(ss.getInputStream()));
//将数据发送给客户端
BufferedWriterbufOutS =
newBufferedWriter(new OutputStreamWriter(ss.getOutputStream()));
Stringstr = null;
while((str= bufInS.readLine()) != null)
{
System.out.println("收到数据:"+str);
bufOutS.write(str.toUpperCase());
bufOutS.newLine();
bufOutS.flush();
}
serverSocket.close();
ss.close();
}
}
另外还可以用打印流简化代码
eg:………………
//定义目的,将数据写入到socket输出流。发给服务端。
//BufferedWriterbufOut =
//newBufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriterout = new PrintWriter(s.getOutputStream(),true);//打印流
//定义一个socket读取流,读取服务端返回的大写信息。
BufferedReaderbufIn =
newBufferedReader(new InputStreamReader(s.getInputStream()));
Stringline = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
out.println(line);//自动换行和刷新!
// bufOut.write(line);
// bufOut.newLine();
// bufOut.flush();
Stringstr =bufIn.readLine();
System.out.println("server:"+str);
}
………………
//目的。socket输出流。将大写数据写入到socket输出流,并发送给客户端。
//BufferedWriterbufOut =
//newBufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriterout = new PrintWriter(s.getOutputStream(),true);//打印流
Stringline = null;
while((line=bufIn.readLine())!=null)
{
System.out.println(line);
out.println(line.toUpperCase());//自动换行和刷新!
// bufOut.write(line.toUpperCase());
// bufOut.newLine();
// bufOut.flush();
}
………………
>>能发送文本当然也能复制文件
eg:原理是在服务端建立一个文件(代码可以改进成和上传文件同名),客户端读取要复制的文件,将其中的数据发送到服务端,服务端输出到建立的文件中,然后反馈成功的消息给客户端。
import java.io.*;
import java.net.*;
class TextClient
{
publicstatic void main(String[] args) throws Exception
{
Sockets = new Socket("192.168.1.254",10006);
BufferedReaderbufr =
newBufferedReader(new FileReader("IPDemo.java"));
PrintWriterout = new PrintWriter(s.getOutputStream(),true);
Stringline = null;
while((line=bufr.readLine())!=null)
{
out.println(line);
}
s.shutdownOutput();//关闭客户端的输出流。相当于给流中加入一个结束标记-1.
BufferedReaderbufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
Stringstr = bufIn.readLine();
System.out.println(str);
bufr.close();
s.close();
}
}
class TextServer
{
publicstatic void main(String[] args) throws Exception
{
ServerSocketss = new ServerSocket(10006);
Sockets = ss.accept();
Stringip = s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
BufferedReaderbufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriterout = new PrintWriter(newFileWriter("server.txt"),true);
Stringline = null;
while((line=bufIn.readLine())!=null)
{
//if("over".equals(line))
//break;
out.println(line);
}
PrintWriterpw = new PrintWriter(s.getOutputStream(),true);
pw.println("上传成功");
out.close();
s.close();
ss.close();
}
}
特别说一下这条语句:
s.shutdownOutput();//关闭客户端的输出流。相当于给流中加入一个结束标记-1。
在没加这条语句时,开启服务端,开启客户端,上传。发现又一次“卡住”了!两个端都在等待。在服务端打开上传的文件,发现已经上传已经成功了,并且数据完好!
那么,这次我们使用了打印流自动换行和刷新。问题又出在哪里呢?
原来,对比上一个传输文本的例子,在我们客户端的“当循环”中,有一个
if(str.equals("over"))
{
break;
}
语句作为结束标记,上传文件的这个例子是没有的,当然我们也可以设定这么一个结束标记,但是非常不好,因为如果上传的文本里面有over字段的话,意味着文件复制到一半就结束了!
另外,在本例中的客户端,还有一条语句:String str = bufIn.readLine();是用来接收服务端的这条语句的:pw.println("上传成功");
这样,原因找到了:
1、客户端的while循环没有结束标记
2、但是当读取文件结束时,while循环结束了,但是s.close();还没有被执行
3、流并没有被关闭,而是在Stringstr = bufIn.readLine();等待服务端的反馈
4、服务端并不知道客户端已经传送完毕了,因为流并没有被关闭。
5、所以服务端的while循环没有结束,而是同样等待
6、这也意味着服务端的pw.println("上传成功");没有发出,客户端不能结束等待
所以在客户端的while循环结束后,执行这样一个方法:
publicvoid shutdownOutput()
throwsIOException
>>并发上传图片(涉及多人的上传)
eg:先单人上传的情况
importjava.net.*;
importjava.io.*;
//客户端
classPicTranClient
{
public static void main(String[]args)throws Exception
{
Socket clientSocket = newSocket("127.0.0.1",10000);
BufferedInputStream bufr =
newBufferedInputStream(new FileInputStream("PicTranDemo.bmp"));
OutputStream out =clientSocket.getOutputStream();
byte[] outBytes = newbyte[1024];
int outLen = 0;
while((outLen =bufr.read(outBytes)) != -1)
{
out.write(outBytes,0,outLen);
out.flush();
}
clientSocket.shutdownOutput();
//接收服务端反馈信息
InputStream in =clientSocket.getInputStream();
byte[] b = new byte[1024];
int len = in.read(b);
System.out.println(newString(b,0,len));
bufr.close();
clientSocket.close();
}
}
//服务端
classPicTranServer
{
public static void main(String[]args)throws Exception
{
ServerSocket serSocket = newServerSocket(10000);
Socket serAcc =serSocket.accept();
//确认客户端连接
String ip =serAcc.getInetAddress().getHostAddress();
System.out.println(ip+"...connected");
InputStream in =serAcc.getInputStream();
BufferedOutputStream bufw =
newBufferedOutputStream(new FileOutputStream("PicTranDemoRFS.bmp"));
byte[] b = new byte[1024];
int len = 0;
while((len = in.read(b)) !=-1)
{
bufw.write(b,0,len);
bufw.flush();
}
//发送反馈信息给客户端
OutputStream out =serAcc.getOutputStream();
out.write("上传成功".getBytes());
bufw.close();
serAcc.close();
serSocket.close();
}
}
那么为了可以让多个客户端同时并发访问服务端。服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。
如何定义线程呢?
只要明确了每一个客户端要在服务端执行的代码即可。将该代码存入run方法中。
eg:演示多线程并发上传,并且防止覆盖,同时客户端限制发送条件
importjava.net.*;
importjava.io.*;
//客户端
classPicTranThreadClient
{
public static void main(String[] args)throwsException
{
//前面一系列判断
if(args.length!=1)
{
System.out.println("请选择一个jpg格式的图片");
return ;
}
//使用主线程输入方法
File file = newFile(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)//限制5MB的文件
{
System.out.println("文件过大,没安好心");
return ;
}
Socket clientSocket = new Socket("127.0.0.1",10000);
BufferedInputStream bufr =
newBufferedInputStream(new FileInputStream(file));
OutputStream out =clientSocket.getOutputStream();
byte[] outBytes = newbyte[1024];
int outLen = 0;
while((outLen = bufr.read(outBytes))!= -1)
{
out.write(outBytes,0,outLen);
out.flush();
}
clientSocket.shutdownOutput();
//接收服务端反馈信息
InputStream in =clientSocket.getInputStream();
byte[] b = new byte[1024];
int len = in.read(b);
System.out.println(newString(b,0,len));
bufr.close();
clientSocket.close();
}
}
//服务端
classPicTranThreadServer
{
public static void main(String[]args)throws Exception
{
ServerSocket serSocket = newServerSocket(10000);
//保证服务端一直开启
while(true)
{
Socket serAcc = serSocket.accept();
new Thread(newPicThread(serAcc)).start();
}
}
}
//线程类
classPicThread implements Runnable
{
private Socket serAcc;
PicThread(Socket serAcc)
{
this.serAcc = serAcc;
}
public void run()
{
String ip =serAcc.getInetAddress().getHostAddress();
try
{
System.out.println(ip+"...connected");
InputStream in =serAcc.getInputStream();
//文件名要变
int count = 1;
File file = newFile(ip+"("+count+")"+".bmp");
while(file.exists())//如果文件存在就改名,防止覆盖
{
file = newFile(ip + "(" + (count++) + ")" + ".bmp");
}
BufferedOutputStreambufw =
newBufferedOutputStream(new FileOutputStream(file));
byte[] b = newbyte[1024];
int len = 0;
while((len =in.read(b)) != -1)
{
bufw.write(b,0,len);
bufw.flush();
}
//发送反馈信息给客户端
OutputStream out =serAcc.getOutputStream();
out.write("上传成功".getBytes());
bufw.close();
serAcc.close();
}
catch(Exception e)
{
throw newRuntimeException(ip+"...上传失败");
}
}
}
自己贴士:其实限制文件的功能应当封装在服务端比较安全吧!
>>练习:需求:
客户端通过键盘录入用户名。
服务端对这个用户名进行校验。
如果该用户存在,在服务端显示xxx,已登陆。
并在客户端显示 xxx,欢迎光临。
如果该用户存在,在服务端显示xxx,尝试登陆。
并在客户端显示 xxx,该用户不存在。
最多就登录三次。
eg:
importjava.net.*;
import java.io.*;
//客户端
classLoginClient
{
public static void main(String[]args)throws Exception
{
Socket clientSocket = newSocket("127.0.0.1",10000);
BufferedReader bufr =
newBufferedReader(new InputStreamReader(System.in));
PrintWriter out = newPrintWriter(clientSocket.getOutputStream(),true);
BufferedReader burIn =
newBufferedReader(new InputStreamReader(clientSocket.getInputStream()));
for(int x = 0 ; x < 3 ;x++)
{
String strOut =bufr.readLine();
if(strOut == null)
{
break;
}
out.println(strOut);
String strIn =burIn.readLine();
System.out.println("serverInfo:"+strIn);
if(strIn.contains("欢迎"))
{
break;
}
}
bufr.close();
clientSocket.close();
}
}
//服务端
classLoginServer
{
public static void main(String[]args)throws Exception
{
ServerSocket serverSocket =new ServerSocket(10000);
while(true)
{
Socket s =serverSocket.accept();
new Thread(newUserThread(s)).start();
}
}
}
//多线程
class UserThreadimplements Runnable
{
private Socket s;
UserThread(Socket s)
{
this.s = s;
}
public void run()
{
String ip =s.getInetAddress().getHostAddress();
System.out.println(ip+"...connected");
try
{
for(int x = 0 ; x< 3 ; x++)
{
//读取客户端发送的数据
BufferedReaderbufIn =
newBufferedReader(new InputStreamReader(s.getInputStream()));
String name= bufIn.readLine();
if(name==null)
{
break;
}
//载入账户文件
BufferedReaderbufr =
newBufferedReader(new FileReader("UserAccount.txt"));
//发送反馈信息给客户端
PrintWriterout = new PrintWriter(s.getOutputStream(),true);
booleanflag = false;
String str= null;
while((str= bufr.readLine() )!= null)
{
if(str.equals(name))
{
flag= true;
break;
}
}
if(flag ==true)
{
System.out.println(name+"...已登录!");
out.println(name+"欢迎光临!");
}
else
{
System.out.println(name+"...尝试登陆!");
out.println(name+"用户名不存在");
}
}
s.close();
}
catch (Exception e)
{
throw newRuntimeException(ip+"校验失败");
}
}
}
>>自定义服务端
eg:演示自定义的服务器,然后用本机的IE浏览器去访问
importjava.net.*;
importjava.io.*;
classLocalServerDemo
{
public static void main(String[]args)throws Exception
{
ServerSocket ss =new ServerSocket(11024);
while(true)
{
Socket s =ss.accept();
System.out.println(s.getInetAddress().getHostAddress()+"...connected!");
PrintWriter pw = newPrintWriter(s.getOutputStream(),true);
pw.println("欢迎登陆!");
s.close();
}
}
}
(演示使用127.0.0.1:11024成功)
>>在上述例子加入语句:
InputStreamins = s.getInputStream();
byte[] bytes = new byte[1024];
int len= ins.read(bytes);
System.out.println(newString(bytes,0,len));
获取浏览器(客户端)的请求信息如下:
GET /HTTP/1.1
Accept:image/jpeg, application/x-ms-application, image/gif, application/xaml+xml,image/pjpeg, appication/x-ms-xbap, application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword, */*
Accept-Language:zh-CN
User-Agent:Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR2.0.507
7; .NETCLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; Tablet PC2.0)
Accept-Encoding:gzip, deflate
Host:127.0.0.1:11024
Connection:Keep-Alive
其实信息到上一行结束。
这是国际通用的,符合HTTP协议的请求语句
1、http协议是1.1版本的
2、“Accept”表示可以接受的信息,看到jpeg和word以及powerpoint了么!
3、“Accept-Encoding”压缩方式,服务器将网页等压缩后发给客户端解压
4、目前看到的属于请求数据头,其实还有请求数据体,只是目前是空白的,头和体之间由一个空行连接。
>>玩一玩tomcat服务器
启动tomcat\bin\ startup.bat,启动了服务器。
在浏览器中输入127.0.0.1:8080(端口是http端口),显示了tomcat的主页
有了服务器和浏览器的请求信息,我们自己来写一个浏览器
eg:
importjava.net.*;
importjava.io.*;
classMyIE
{
public static void main(String[]args)throws Exception
{
Socket s = newSocket("127.0.0.1",8080);
//尼玛,一定不要忘了写true啊!
PrintWriter out = newPrintWriter(s.getOutputStream(),true);
//请求信息
out.println("GET/myWeb/test.html HTTP/1.1");
out.println("Accept:*/*");
out.println("Accept-Language:zh-CN");
out.println("Host:127.0.0.1:11000");
//out.println("Connection:Keep-Alive");
out.println("Connection:closed");
out.println();
out.println();
//不要忘了http格式,要加空行
BufferedReader bufr =
newBufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line= bufr.readLine()) != null)
{
System.out.println(line);
}
s.close();
}
}
结果:
F:\java\workspace\day24>javaMyIE
HTTP/1.1200 OK
Server:Apache-Coyote/1.1
Accept-Ranges:bytes
ETag:W/"158-1331212562108"
Last-Modified:Thu, 08 Mar 2012 13:16:02 GMT
Content-Type:text/html
Content-Length:158
Date:Thu, 08 Mar 2012 13:23:11 GMT
Connection:close
welcome
abcdefg
abcdefg
发现结果除了html代码,还是包含响应头信息的!在传输层!
>>浏览器的图形化界面
其实Socket是一样的,只是融合了GUI技术,关键是输入域名的解析
eg:
importjava.awt.*;
importjava.awt.event.*;
import java.io.*;
importjava.net.*;
class MyIEByGUI
{
private Frame f;
private TextField tf;
private Button but;
private TextArea ta;
private Dialog d;
private Label lab;
private Button okBut;
MyIEByGUI()
{
init();
}
public void init()
{
f = new Frame("mywindow");
f.setBounds(300,100,600,500);
f.setLayout(newFlowLayout());
tf = new TextField(60);
but = new Button("转到");
ta = new TextArea(25,70);
d = new Dialog(f,"提示信息-self",true);
d.setBounds(400,200,240,150);
d.setLayout(newFlowLayout());
lab = new Label();
okBut = new Button("确定");
d.add(lab);
d.add(okBut);
f.add(tf);
f.add(but);
f.add(ta);
myEvent();
f.setVisible(true);
}
private void myEvent()
{
okBut.addActionListener(new ActionListener()
{
public voidactionPerformed(ActionEvent e)
{
d.setVisible(false);
}
});
d.addWindowListener(newWindowAdapter()
{
public voidwindowClosing(WindowEvent e)
{
d.setVisible(false);
}
});
tf.addKeyListener(newKeyAdapter()
{
public voidkeyPressed(KeyEvent e)
{
try
{
if(e.getKeyCode()==KeyEvent.VK_ENTER)
showDir();
}
catch(Exception ex)
{
}
}
});
but.addActionListener(newActionListener()
{
public voidactionPerformed(ActionEvent e)
{
try
{
showDir();
}
catch(Exception ex)
{
}
}
});
f.addWindowListener(newWindowAdapter()
{
public voidwindowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
private void showDir()throws Exception
{
ta.setText("");
String url =tf.getText();//http://192.168.1.254:8080/myweb/demo.html
//域名解析,将域名切割
int index1 =url.indexOf("//")+2;
int index2 =url.indexOf("/",index1);
String str =url.substring(index1,index2);
String[] arr =str.split(":");
String host = arr[0];
int port =Integer.parseInt(arr[1]);
String path =url.substring(index2);
//ta.setText(str+"...."+path);
Socket s = newSocket(host,port);
PrintWriter out = newPrintWriter(s.getOutputStream(),true);
out.println("GET"+path+" HTTP/1.1");
out.println("Accept:*/*");
out.println("Accept-Language:zh-cn");
out.println("Host:192.168.1.254:11000");
out.println("Connection:closed");
out.println();
out.println();
BufferedReader bufr =
new BufferedReader(newInputStreamReader(s.getInputStream()));
String line = null;
while((line=bufr.readLine())!=null)
{
ta.append(line+"\r\n");
}
s.close();
}
public static void main(String[] args)
{
new MyIEByGUI();
}
}
目前制作的“山寨版”IE浏览器没有解析引擎,并且位于传输层,IE浏览器是应用层的,并且对信息进行了拆包,只保留html代码,并且对html代码解析。
>>可以解析的图形化浏览器,并且改进域名解析方法
其实自己书写域名解析方法应当被封装,另外java有提供方法:
java.net
类 URL
java.lang.Object
java.net.URL
所有已实现的接口:
Serializable
类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。另外URI是比URL更大的定义。
其中非常爽的方法:
StringgetFile()
获取此 URL 的文件名。
StringgetHost()
获取此 URL 的主机名(如果适用)。
StringgetPath()
获取此 URL 的路径部分。
intgetPort()
获取此 URL 的端口号。
StringgetProtocol()
获取此 URL 的协议名称。
StringgetQuery()
获取此 URL 的查询部分。web开发高点击率方法!
eg:演示方法
importjava.net.*;
classURLDemo
{
public static void main(String[] args)throws MalformedURLException
{
URL url = newURL("http://192.168.1.254/myweb/demo.html?name=haha&age=30");
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());
/*即,如果没有指定端口,就设定默认端口80
int port = getPort();
if(port==-1)
port = 80;
getPort()==-1
*/
}
}
结果:
F:\java\workspace\day24>javaURLDemo
getProtocol():http
getHost():192.168.1.254
getPort():-1
getPath():/myweb/demo.html
getFile():/myweb/demo.html?name=haha&age=30
getQuery():name=haha&age=30
注意,没有指定端口的时候port返回-1。
另外,还有一个重要的类:
java.net
类 URLConnection
java.lang.Object
java.net.URLConnection
直接已知子类:
HttpURLConnection,JarURLConnection
抽象类URLConnection 是所有类的超类,它代表应用程序和 URL 之间的通信链接。此类的实例可用于读取和写入此 URL 引用的资源。
通常,创建一个到 URL 的连接需要几个步骤:
openConnection() connect()
对影响到远程资源连接的参数进行操作。 与资源交互;查询头字段和内容。
---------------------------->
时间
1、通过在 URL 上调用 openConnection 方法创建连接对象。
2、处理设置参数和一般请求属性。
3、使用 connect 方法建立到远程对象的实际连接。
4、远程对象变为可用。远程对象的头字段和内容变为可访问。
使用以下方法修改设置参数:
setAllowUserInteraction
setDoInput
setDoOutput
setIfModifiedSince
setUseCaches
………………
涉及类URL的方法:
URLConnectionopenConnection()
返回一个URLConnection 对象,它表示到 URL 所引用的远程对象的连接。
eg:演示URLConnection返回信息
importjava.net.*;
classURLConnectionDemo
{
public static void main(String[]args)throws Exception//MalformedURLException,IOException
{
URL url = newURL("http://192.168.1.254:8080/myweb/demo.html");
URLConnection conn =url.openConnection();
System.out.println(conn);
}
}
//结果:sun.net.www.protocol.http.HttpURLConnection:http://192.168.1.254:8080/myweb/demo.html
说明,其封装了http协议,进入应用层
并且,发现两个方法:
InputStreamgetInputStream()
返回从此打开的连接读取的输入流。
OutputStreamgetOutputStream()
返回写入到此连接的输出流。
证明其也封装了Socket
eg:二话不说,开启tomcat服务器,演示读取流
importjava.net.*;
importjava.io.*;
classURLConnectionDemo
{
public static void main(String[]args)throws Exception//MalformedURLException,IOException
{
URL url = newURL("http://127.0.0.1:8080/myWeb/test.html");
URLConnection conn =url.openConnection();
System.out.println(conn);
InputStream ins =conn.getInputStream();
byte[] bytes = newbyte[1024];
int len = ins.read(bytes);
System.out.println(newString(bytes,0,len));
}
}
结果:
F:\java\workspace\day24>javaURLConnectionDemo
sun.net.www.protocol.http.HttpURLConnection:http://127.0.0.1:8080/myWeb/test.htm
welcome
abcdefg
abcdefg
惊喜的发现,响应头信息没有了!!!
>>域名解析是怎么回事呢?
我们在浏览器中输入www.baidu.com的时候,并非先去访问百度的服务器,而是寻找公网上的DNS服务器,用这个域名换取ip地址,DNS服务器将ip地址发给本机后,浏览器再用百度的IP地址访问百度的服务器。
那么在没有联网的情况下,为什么输入127.0.0.1和localhost都能访问本机服务器的页面呢?原因是,在C:\Windows\System32\drivers\etc中有一个文件叫hosts,其中就有127.0.0.1和localhost的映射关系。
实际上这个hosts文件非常好用。
1、如果我们知道了新浪的ip地址,那么我们就添加一条新浪的ip和www.sina.com.cn的映射,那么访问新浪的时候就不用访问DNS服务器(因为计算机先访问这个文件看看有没有对应关系才访问DNS服务器),这样会快很多!
2、如果有恶意网站,那么添加一条127.0.0.1和这个恶意网址的关系,那么相当于这个网站被屏蔽了。