258 - 基于TCP的网络编程

功能:模拟网站的登录,客户端录入账号密码,然后服务器端进行验证。

功能分解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外面套了一个处理流:DataOutputStreamDataOutputStream 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();//同样的,再套一个处理流:DataOutputStreamDataOutputStream 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外面套了一个处理流:DataOutputStreamDataOutputStream 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  —— 快捷生成下面的 serialVersionUIDprivate 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();//同样的,再套一个处理流:DataOutputStreamDataOutputStream 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;}@Overridepublic 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();}}*/}
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部