功能:模拟网站的登录,客户端录入账号密码,然后服务器端进行验证。
功能分解1:单向通信
功能:客户端发送一句话到服务器
客户端:
package test2_TCP;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestClien {
public static void main(String[] args) throws IOException {
//1、创建套接字:指定服务器的ip和端口号
Socket s = new Socket("192.168.67.124",8888);
//2、对于程序员来说,向外发送数据 的直观感受就是 --》利用输出流
OutputStream os = s.getOutputStream();
//然后写一句话String类型:
//os.write("abc"); //发现,没有要求入String类型的write()
//怎么办?—— (往外写数据(字符串))换一个流:
//在它外面包一个流:DataOutputStream
//即:
// 利用这个OutputStream就可以向外发送数据了,但是没有直接发送String的方法
// 所以我们又在OutputStream外面套了一个处理流:DataOutputStream
DataOutputStream dos = new DataOutputStream(os);//包在os上
dos.writeUTF("你好!");
//写好后:
//3、关闭流 + 关闭网络资源
dos.close();
os.close();// 只关上面的dos就可以了(关闭流)
s.close(); //关闭网络资源
}
}
服务器:
package test2_TCP;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestServer {
public static void main(String[] args) throws IOException {
//1、创建套接字:指定服务器的端口号(服务器知道自己的IP,故不用指定了)
ServerSocket ss = new ServerSocket(8888);
//2、等着客户端发来的信息
//ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
//accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
//接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
Socket s = ss.accept();
//3、感受到的操作流(输入流)
InputStream is = s.getInputStream();
//客户端发送数据的时候使用了OutputStream处理流,服务端接收数据的时候也要配合使用
DataInputStream dis = new DataInputStream(is);
//4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
String str = dis.readUTF();
System.out.println("客户端发来的数据为:" + str);
//5、关闭流 + 关闭网络资源
dis.close();
is.close();
s.close();
ss.close();
}
}
测试:
(1)先开启客户端还是先开启服务器?? 先开服务器,再开启客户端
侧面验证:先开客户端,出错:
功能分解2:双向通信
代码示例:在上面代码的基础上
服务器端:
package test2_TCP;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestServer {
public static void main(String[] args) throws IOException {
//1、创建套接字:指定服务器的端口号(服务器知道自己的IP,故不用指定了)
ServerSocket ss = new ServerSocket(8888);
//2、等着客户端发来的信息
//ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
//accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
//接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
Socket s = ss.accept();
//3、感受到的操作流(输入流)
InputStream is = s.getInputStream();
//客户端发送数据的时候使用了OutputStream处理流,服务端接收数据的时候也要配合使用
DataInputStream dis = new DataInputStream(is);
//4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
String str = dis.readUTF();
System.out.println("客户端发来的数据为:" + str);
//双向通信:
//(再)向客户端输出一句话:---》操作流---》输出流
OutputStream os = s.getOutputStream();
//同样的,再套一个处理流:DataOutputStream
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("你好,我是服务器,我接收到你的请求了!");
//注:下面要关闭对应的流dos、os
//同理,然后是客户端的接收 —— 见客户端
//5、关闭流 + 关闭网络资源
//双向通信时多出来的输出流:(对应也要关闭)
dos.close();
os.close();
dis.close();
is.close();
s.close();
ss.close();
}
}
客户端:
package test2_TCP;
import java.io.*;
import java.net.Socket;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestClien {
public static void main(String[] args) throws IOException {
//1、创建套接字:指定服务器的ip和端口号
Socket s = new Socket("192.168.67.124",8888);
//2、对于程序员来说,向外发送数据 的直观感受就是 --》利用输出流
OutputStream os = s.getOutputStream();
//然后写一句话String类型:
//os.write("abc"); //发现,没有要求入String类型的write()
//怎么办?—— (往外写数据(字符串))换一个流:
//在它外面包一个流:DataOutputStream
//即:
// 利用这个OutputStream就可以向外发送数据了,但是没有直接发送String的方法
// 所以我们又在OutputStream外面套了一个处理流:DataOutputStream
DataOutputStream dos = new DataOutputStream(os);//包在os上
dos.writeUTF("你好!");
//双向通信:
//接收服务端的回话 : —— 同样利用输入流
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
String str = dis.readUTF();
System.out.println("服务器端对我说:" + str);
//注:对应的流在下面要关闭
//写好后:
//3、关闭流 + 关闭网络资源
//双向通信-补加:流的关闭
dis.close();
is.close();
dos.close();
os.close();// 只关上面的dos就可以了(关闭流)
s.close(); //关闭网络资源
}
}
注意:关闭防火墙
功能分解3:对象流传送
—— 登陆的验证
封装的User类:
package test3_TCP_2;
import java.io.Serializable;
/**
* @Auther: zhoulz
* @Description: test3_TCP_2
* @version: 1.0
*/
public class User implements Serializable {
//Alt + Enter —— 快捷生成下面的 serialVersionUID
private static final long serialVersionUID = -4001204060434082179L;
private String name;
private String pwd;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
}
客户端:
package test3_TCP_2;
import javax.xml.transform.Source;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestClien {
public static void main(String[] args) throws IOException {
//1、创建套接字:指定服务器的ip和端口号
Socket s = new Socket("192.168.67.124",8888);
//录入用户的账号和密码:
Scanner sc = new Scanner(System.in);
System.out.println("请录入您的账号:");
String name = sc.next();
System.out.println("请录入您的密码:");
String pwd = sc.next();
//将账号和密码封装为一个User的对象
User user = new User(name,pwd);
//2、对于程序员来说,向外发送数据 的直观感受就是 --》利用输出流
OutputStream os = s.getOutputStream();
//DataOutputStream dos = new DataOutputStream(os);//包在os上
//此时,再往外面写的时候,可以在外面包一个流(这个流就不能是上面的DataOutputStream了)
//而是:
ObjectOutputStream oos =new ObjectOutputStream(os);
//直接有往外写对象的方法:
oos.writeObject(user);
/* dos.writeUTF(name);
dos.writeUTF(pwd);
//分别写,可以,但是如果很多的话,在这么写就不方便了。
//此时,可以把上面的(账号和密码),封装成一个对象
//然后通过对象流向外写一个对象就行了(无论对象里有多少个属性)
//即:抽取出一个类——User*/
//双向通信:
//接收服务端的回话 : —— 同样利用输入流
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
/*String str = dis.readUTF();
System.out.println("服务器端对我说:" + str);
//注:对应的流在下面要关闭*/
//客户端接收验证结果:
boolean b = dis.readBoolean();
if (b){
System.out.println("恭喜,登陆成功!");
}else {
System.out.println("对不起,登陆失败");
}
//写好后:
//3、关闭流 + 关闭网络资源
//双向通信-补加:流的关闭
dis.close();
is.close();
//dos.close();
oos.close();
os.close();// 只关上面的dos就可以了(关闭流)
s.close(); //关闭网络资源
}
}
服务器端:
package test3_TCP_2;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestServer {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1、创建套接字:指定服务器的端口号(服务器知道自己的IP,故不用指定了)
ServerSocket ss = new ServerSocket(8888);
//2、等着客户端发来的信息
//ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
//accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
//接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
Socket s = ss.accept();
//3、感受到的操作流(输入流)
InputStream is = s.getInputStream();
/*//客户端发送数据的时候使用了OutputStream处理流,服务端接收数据的时候也要配合使用
DataInputStream dis = new DataInputStream(is);*/
//上面客户端发送的是String类型的数据时,下面发送了一个对象:
//客户端发送的是一个对象了,则接收用:
ObjectInputStream ois = new ObjectInputStream(is);
//4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
/*String str = dis.readUTF();
System.out.println("客户端发来的数据为:" + str);*/
//读取的为一个对象:(则需要将其装换为对象对应的类型,然后去接收)
User user = (User)(ois.readObject());
//还有,
//对对象进行验证:
boolean flag = false;
if (user.getName().equals("娜娜") && user.getPwd().equals("112233")){
flag = true;
}
//然后,下面将验证的结果返回给客户端:
/*//双向通信:
//(再)向客户端输出一句话:---》操作流---》输出流
OutputStream os = s.getOutputStream();
//同样的,再套一个处理流:DataOutputStream
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("你好,我是服务器,我接收到你的请求了!");
//注:下面要关闭对应的流dos、os
//同理,然后是客户端的接收 —— 见客户端*/
//双向通信:
//(再)向客户端输出结果:---》操作流---》输出流
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
//这里,可以再用DataOutputStream,因为其里面有一个可以写布尔类型的方法
dos.writeBoolean(flag);
//5、关闭流 + 关闭网络资源
//双向通信时多出来的输出流:(对应也要关闭)
dos.close();
os.close();
//dis.close();
ois.close();
is.close();
s.close();
ss.close();
}
}
如果不对 User进行序列化,则会出现如下错误:
结果报错:User 没有序列化
即:IO流 —— 序列化和反序列化问题:
想通过IO流或者网络传送(数据)的话,就必须得进行序列化和反序列化。
功能分解4:加入完整的处理异常方式
客户端:
package test4_TCP_3_try_catch;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestClien {
public static void main(String[] args) {
//1、创建套接字:指定服务器的ip和端口号
Socket s = null;
OutputStream os = null;//提出来后,将前面的如OutputStream 删掉
ObjectOutputStream oos = null;
InputStream is = null;
DataInputStream dis = null;
try {
s = new Socket("192.168.67.124",8888);
//下面的代码都放到try中去:
//录入用户的账号和密码:
Scanner sc = new Scanner(System.in);
System.out.println("请录入您的账号:");
String name = sc.next();
System.out.println("请录入您的密码:");
String pwd = sc.next();
//将账号和密码封装为一个User的对象
User user = new User(name,pwd);
//2、对于程序员来说,向外发送数据 的直观感受就是 --》利用输出流
os = s.getOutputStream();
//DataOutputStream dos = new DataOutputStream(os);//包在os上
oos =new ObjectOutputStream(os);
//直接有往外写对象的方法:
oos.writeObject(user);
//双向通信:
//接收服务端的回话 : —— 同样利用输入流
is = s.getInputStream();
dis = new DataInputStream(is);
//客户端接收验证结果:
boolean b = dis.readBoolean();
if (b){
System.out.println("恭喜,登陆成功!");
}else {
System.out.println("对不起,登陆失败");
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流的代码要放到finally中:
//写好后:
//3、关闭流 + 关闭网络资源
//双向通信-补加:流的关闭
try {
if (dis != null){
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null){
is.close(); //这几个流的关闭—报错的原因:作用域
//作用域都在try中,只在try中有效
//依次提出来即可
//注意:提出来要记得赋初始值
}
} catch (IOException e) {
e.printStackTrace();
}
//dos.close();
try {
if (oos != null){
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null){
os.close();// 只关上面的dos就可以了(关闭流)
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (s != null){
s.close(); //关闭网络资源
}
} catch (IOException e) {
e.printStackTrace();
}
//最后,流和资源不报错了,但是后面的close()又报错了
//因为关闭的时候可能会有异常,所以要加上try-catch
//注意:要分别加 —— 不用一个try-catch是防止第一个流关闭失败导致后面的流都没有能关闭
//最最后,为了防止空指针异常,分别加上一个if条件判断
}
}
}
服务器端:
package test4_TCP_3_try_catch;
import jdk.nashorn.internal.ir.annotations.Ignore;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestServer {
public static void main(String[] args) {
//1、创建套接字:指定服务器的端口号(服务器知道自己的IP,故不用指定了)
ServerSocket ss = null;
Socket s = null;
InputStream is = null;
ObjectInputStream ois = null;
OutputStream os = null;
DataOutputStream dos = null;
try {
ss = new ServerSocket(8888);
//2、等着客户端发来的信息
s = ss.accept();
//3、感受到的操作流(输入流)
is = s.getInputStream();
//客户端发送的是一个对象了,则接收用:
ois = new ObjectInputStream(is);
//4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
/*String str = dis.readUTF();
System.out.println("客户端发来的数据为:" + str);*/
//读取的为一个对象:(则需要将其装换为对象对应的类型,然后去接收)
User user = (User)(ois.readObject());
//有异常,继续try-catch —— 选第一个
//还有,
//对对象进行验证:
boolean flag = false;
if (user.getName().equals("娜娜") && user.getPwd().equals("112233")){
flag = true;
}
//然后,下面将验证的结果返回给客户端:
//双向通信:
//(再)向客户端输出结果:---》操作流---》输出流
os = s.getOutputStream();
dos = new DataOutputStream(os);
//这里,可以再用DataOutputStream,因为其里面有一个可以写布尔类型的方法
dos.writeBoolean(flag);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}finally {
//5、关闭流 + 关闭网络资源
//双向通信时多出来的输出流:(对应也要关闭)
try {
if (dos != null){
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null){
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
//dis.close();
try {
if (ois != null){
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (s != null){
s.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (ss != null){
ss.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
功能分解5:多线程接收用户请求
遗留问题:上面的服务器只针对一个请求服务,之后服务器就关闭了(程序自然结束了)
现在需要解决:服务器必须一直在监听 ,一直开着,等待客户端的请求。
在当前代码中,客户端不用动了(User类也不动)
更改服务器代码:
首先,增加了一个服务器线程类ServerThread:
package test5_TCP_4_thread;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: zhoulz
* @Description: test5_TCP_4_thread
* @version: 1.0
*/
public class ServerThread extends Thread{//线程:专门处理客户端的请求
InputStream is = null;
ObjectInputStream ois = null;
OutputStream os = null;
DataOutputStream dos = null;
Socket s = null;
//构造器
public ServerThread(Socket s) {
this.s = s;
}
@Override
public void run() {
//super.run();
//把服务器中监听(即第2步开始)的操作复制过来,放到线程中处理
try {
//2、等着客户端发来的信息
//3、感受到的操作流(输入流)
is = s.getInputStream(); //s 还在报错,所以要想办法把Socket s 传进来
//利用构造器,见上面
//客户端发送的是一个对象了,则接收用:
ois = new ObjectInputStream(is);
//4、读取客户端发来的数据 (前面的是对数据做一个接收,现在要把数据读出来)
/*String str = dis.readUTF();
System.out.println("客户端发来的数据为:" + str);*/
//读取的为一个对象:(则需要将其装换为对象对应的类型,然后去接收)
User user = (User)(ois.readObject());
//有异常,继续try-catch —— 选第一个
//还有,
//对对象进行验证:
boolean flag = false;
if (user.getName().equals("娜娜") && user.getPwd().equals("112233")){
flag = true;
}
//然后,下面将验证的结果返回给客户端:
//双向通信:
//(再)向客户端输出结果:---》操作流---》输出流
os = s.getOutputStream();
dos = new DataOutputStream(os);
//这里,可以再用DataOutputStream,因为其里面有一个可以写布尔类型的方法
dos.writeBoolean(flag);
}catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}finally {
//5、关闭流 + 关闭网络资源
//双向通信时多出来的输出流:(对应也要关闭)
try {
if (dos != null){
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null){
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
//dis.close();
try {
if (ois != null){
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端:
package test5_TCP_4_thread;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: zhoulz
* @Description: test2_TCP
* @version: 1.0
*/
public class TestServer {
public static void main(String[] args) {
System.out.println("服务器启动了");
//1、创建套接字:指定服务器的端口号
ServerSocket ss = null;
Socket s = null;
int count = 0; //定义一个计数器,用来计数 客户端的请求
try {
ss = new ServerSocket(8888);
while (true){ // 加入死循环,服务器一直监听客户端是否发送数据
s = ss.accept(); //监听
//每次过来的客户端的请求,靠 线程处理:
//故创建一个线程(传入s),并启动
new ServerThread(s).start();
count++;
//输入请求的客户端的信息:
System.out.println("当前是第"+count+"个用户访问我们的服务器,对应的用户是:"+s.getInetAddress());
}
} catch (IOException e) { //ClassNotFoundException 放在ServerThread中处理了,这里要删除
e.printStackTrace();
}/*finally {
//5、关闭流 + 关闭网络资源
//双向通信时多出来的输出流:(对应也要关闭)
//因为服务器要一直监听,所以不用关闭,所以这部分代码不需要了
try {
if (s != null){
s.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (ss != null){
ss.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}*/
}
}