网络编程
昨天讲解了网络编程的理论部分,今天主要围绕网络编程的开发,进行技术的梳理
主要技术点:Socket编程+IO流+多线程
一、基本互发数据
案例:客户端发数据给服务器,服务器也可以给客户端发数据
//创建客户端socket对象,指定服务器的ip和端口
Socket socket = new Socket("127.0.0.1", 6666);
//客户端发数据
OutputStream os = socket.getOutputStream();
os.write("client说:xxxxxxx".getBytes());
//客户端接收数据
InputStream is = socket.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b); //阻塞
System.out.println(new String(b,0,len));
Utils.closeAll(os,is,socket);
//1.创建服务器socket,指定自身端口
ServerSocket ss = new ServerSocket(6666);
Socket socket = ss.accept(); //阻塞
//服务器接收:
InputStream is = socket.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b); //阻塞
System.out.println(new String(b,0,len));
//服务器发数据:
OutputStream os = socket.getOutputStream();
os.write("server说:yyyyyy".getBytes());
Utils.closeAll(os,is,socket); //统一关闭
二、上传图片
案例:从客户端上传图片资源到服务器
分析
传输二进制资源可以使用字节节点流即可
通过画图分析可知:
- 客户端需要从本地读资源,写到socket中–循环的先读后写
- 服务器从socket读,再写到服务器本地—–循环的先读后写
上传实现
//客户端:
Socket socket = new Socket("127.0.0.1", 7777);
//客户端从本地读图片资源,再写到socket
InputStream is = new FileInputStream("c.jpg");
int len;
byte[] b = new byte[1024];
OutputStream os = socket.getOutputStream();
while((len=is.read(b))!=-1) { //循环先读后写
os.write(b, 0, len);
}
System.out.println("上传成功!");
Utils.closeAll(is,os,socket);
//服务器:
ServerSocket ss = new ServerSocket(7777);
Socket socket = ss.accept();
InputStream is = socket.getInputStream(); //从socket中读取资源
int len;
byte[] b = new byte[1024];
OutputStream os = new FileOutputStream("d:/c.jpg");
while((len=is.read(b))!=-1) { //循环的先读后写;写到了服务器本地
os.write(b, 0, len);
}
System.out.println("存到服务器成功~");
Utils.closeAll(is,os,socket);
三、多客户端发数据
案例:多个客户端启动后,可以循环发数据到服务器端
分析
每个客户端都应该对应一个服务器的socket;来一个客户端则产生一个服务器socket;意味着服务器也需要循环监听,且需要通过多线程分离一条路径与客户都交互。
多个客户端循环的从控制台录入数据;服务器都需要能够接收到
优化:将字节流封装成字符缓冲流
技术点: socket+IO流+多线程
代码实现
//客户端:
//创建一个客户端类,只需要运行多次,则是模拟多个客户端
Socket socket = new Socket("127.0.0.1", 5555);
System.out.println("客户端输入数据:");
Scanner sc = new Scanner(System.in);
OutputStream os = socket.getOutputStream(); //socket的字节流
//封装成字符缓冲流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
while(true) {
String msg = sc.next();
bw.write(msg); //写数据
bw.newLine(); //换行
bw.flush(); //刷新缓冲
if(msg.equals("886")) { //退出出口
break;
}
}
Utils.closeAll(bw,os,socket);
//服务器:
//服务器循环监听客户端的访问
ServerSocket ss = new ServerSocket(5555);
Socket socket = null;
while(true) { //循环监视客户端访问
socket = ss.accept(); //每次客户端来了,产生子线程;主线从都会继续阻塞
new ServerThread(socket).start(); //启动线程和客户端的socket交互
}
//服务器线程:
public class ServerThread extends Thread {
private Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStream is = null;
BufferedReader br = null;
try {
is = socket.getInputStream(); //获取字节流
br = new BufferedReader(new InputStreamReader(is));
while(true) {
String msg = br.readLine();
System.out.println(msg);
if(msg.equals("886")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
Utils.closeAll(br,is,socket);
}
}
}
四、注册功能
案例分析
在web开发中,注册的流程:前端准备注册数据,传到后端;后端接收数据,调取数据库的数据,匹配用户信息,如果用户名相同,返回已注册;没有相同的,则进行存储到数据库,并返回注册成功。
因为现在没有数据库基础,没有web服务器技术;所以只能通过tcp编程的方式,模拟注册功能。
客户端–前端页面 服务器端—web后端 数据库—-文本文件(Properties)
Properties存储格式:key-ID,value-所有数据拼接
数据处理:从客户端发数据,服务器接收;再通过字符串分解匹配;并存储到文件;服务器反馈信息
代码实现
//客户端:
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
String msg = getInfo(); //封装数据
bw.write(msg); //"id:1001,name:zs,pwd:123,age:30"
bw.newLine();
bw.flush();
InputStream is = socket.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
System.out.println(new String(b,0,len));
Utils.closeAll(os,is,bw,socket);
}
private static String getInfo() {
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要注册的信息:");
System.out.print("ID:");
String id = sc.next();
System.out.print("name:");
String name = sc.next();
System.out.print("pwd:");
String pwd = sc.next();
System.out.print("age:");
String age = sc.next();
return "id:"+id+",name:"+name+",pwd:"+pwd+",age:"+age;
}
//服务器:
ServerSocket ss = new ServerSocket(8888);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//接收数据
String msg = br.readLine(); //"id:1001,name:zs,pwd:123,age:30"
//拆分字符串:
String ids = msg.split(",")[0]; //id:1001
String id = ids.split(":")[1];
Properties p = new Properties(); //实例化集合,并加载配置文件的数据
p.load(new FileInputStream("user.properties")); //将文件数据读到p集合
OutputStream os = socket.getOutputStream();
if(p.containsKey(id)) {
os.write("已注册".getBytes());
}else {
os.write("注册成功~".getBytes());
p.put(id, msg);
p.store(new FileOutputStream("user.properties"), "");
}
Utils.closeAll(is,os,br,socket);
五、登录功能
案例分析
客户端:准备登录的id和pwd,从客户端发送到服务器
服务器:接收到字符串,拿到ID和密码;匹配配置文件中的key;及匹配value的密码
id和密码都一致,则返回登录成功;否则登录失败
代码实现
//客户端:
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("127.0.0.1", 4444);
OutputStream os = socket.getOutputStream();
String msg = getInfo(); //id:1001,pwd:123
os.write(msg.getBytes()); //发送数据
InputStream is = socket.getInputStream(); //接收服务器的返回信息
byte[] b = new byte[1024];
int len = is.read(b);
System.out.println(new String(b,0,len));
Utils.closeAll(is,os,socket); //统一关闭
}
private static String getInfo() {
Scanner sc = new Scanner(System.in);
System.out.println("请输入要登录的信息:");
System.out.print("id:");
String id = sc.next();
System.out.print("pwd:");
String pwd = sc.next();
return "id:"+id+",pwd:"+pwd;
}
//服务器:
ServerSocket ss = new ServerSocket(4444);
Socket socket = ss.accept();
InputStream is = socket.getInputStream(); //socket中获取输入流
byte[] b = new byte[1024];
int len = is.read(b);
String msg = new String(b,0,len); //id:1001,pwd:123
String ids = msg.split(",")[0]; //id:1001
String pwd = msg.split(",")[1]; //pwd:123
String id = ids.split(":")[1]; //1001
Properties p = new Properties();
p.load(new FileInputStream("user.properties"));//加载配置文件数据到集合
String value = p.getProperty(id); //根据key获取value
OutputStream os = socket.getOutputStream();
if(value!=null&&value.contains(pwd)) {//判断id和pwd相等
os.write("登录成功".getBytes());
}else {
os.write("登录失败".getBytes());
}
Utils.closeAll(os,is,socket);
六、反射概述
概述:反射其实就是类对象,类加载的产物;将类加载到内存中,会产生类对象(class文件);有了类对象,即可得到所有类资源的信息: 类,方法,属性,构造器,包,父类,接口…
创建类对象
可以通过三种方式获取类对象:1.类名.class 2.对象.getClass() 3.Class.forName(“全限定名”);
结论:无论哪种方式获取的类对象都是同一个类对象
//1.通过类名获取类对象
Class<?> c1 = Person.class; //<?>:通配所有类型,用在接收数据据时
Class c2 = new Person().getClass();
Class c3 = Class.forName("com.qf.f_reflect.Person");
System.out.println(c1==c2); //true
System.out.println(c1==c3); //true
常用方法
//反射的相关方法
Class c1 = Student.class; //获取反射对象
System.out.println("获取类名:"+c1.getName());
System.out.println("获取包名:"+c1.getPackage());
System.out.println("获取父类:"+c1.getSuperclass());
System.out.println("获取接口:"+Arrays.toString(c1.getInterfaces()));
//getField或getFields获取公开的属性的
System.out.println("获取属性:"+Arrays.toString(c1.getFields()));
//System.out.println("获取指定属性:"+c1.getField("name"));
//获取非空开属性;甚至放开权限后,私有的也可以获取
System.out.println("获取属性2:"+Arrays.toString(c1.getDeclaredFields())); //常用
System.out.println("获取方法:"+Arrays.toString(c1.getMethods())); //常用
//参数1:方法名 参数2:多个参数
System.out.println("获取指定方法:"+c1.getMethod("hi",String.class,int.class));
System.out.println("获取构造器:"+Arrays.toString(c1.getConstructors()));
System.out.println("实例化对象:"+c1.newInstance()); //(常用)
七、总结与作业
总结
作业
1.使用socket完成,客户端发送信息,服务器接收信息(使用字节流收发数据)
2.使用socket完成,客户端发送信息,服务器接收信息(使用字符缓冲流收发数据)
3.实现客户端从服务器中下载图片功能(类似上传图片)
提示:服务器发(服务器本地读;再发到socket)---->客户端收(从socket读取,再存到客户端本地)
晨考
1.TCP是一种面向_连接____,_可靠的____,基于_字节流___的传输通讯协议
UDP是一种_无连接的____,_不可靠的__的传输通讯协议
2.什么是IP
ip是计算机的唯一识别地址
3.ServerSocket的accept方法会导致程序_阻塞______,直到接收到_客户端连接______,才继续运行
4. 客户端与服务器建立连接的语句,请默写
Server
ServerSocket ss = new ServerSocket(xxxx);
Socket sk = ss.accept();
Client
Socket st = new Socket("localhost",xxxxx);