异常机制
File类
利用递归遍历目录下的文件以及子目录名称
public static void show(File file) {
File[] files_Array = file.listFiles();
for(File f:files_Array){
String name = f.getName();
if (f.isFile()){
System.out.println(name);
}
if (f.isDirectory()){
System.out.println("["+name+"]");
//递归实现全目录遍历
show(f);
}
}
}
public static void main(String[] args) {
show(new File("c:/迅雷下载"));
}
IO流
按照读写数据的基本单位不同,分为 字节流 和 字符流。
其中字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。
其中字符流主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件。
按照读写数据的方向不同,分为 输入流 和 输出流(站在程序的角度)。
其中输入流主要指从文件中读取数据内容输入到程序中,也就是读文件。
其中输出流主要指将程序中的数据内容输出到文件中,也就是写文件。
按照流的角色不同分为节点流和处理流。
其中节点流主要指直接和输入输出源对接的流。
其中处理流主要指需要建立在节点流的基础之上的流。
字符流
FileReader
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("d:/sb.txt");
/*
int sec = 0;
while ((sec = fr.read()) != -1) {
System.out.print((char)sec);
}*/
char[] arr = new char[4];
int sec = fr.read(arr, 0, 3);
System.out.println(sec);
for (char cv:arr) {
System.out.println((char)sec);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fr){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileWriter
FileWriter fw = null;
try {
fw = new FileWriter("d:/sb.txt",true);
//写入字符,数组等
/*
fw.write("sb");
System.out.println("数据写入成功。");
*/
char[] arr = new char[]{'c','r'};
fw.write(arr);
System.out.println("数据写入成功。");
//刷新流
fw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null != fw) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
字符流实现cpoy
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("d:/sb.txt");
// fr = new FileReader("d:/1.jpg");
fw = new FileWriter("d:/sbb.txt");
// fw = new FileWriter("d:/pixiv/1.jpg");
int sec = 0;
while ((sec = fr.read()) != -1){
fw.write(sec);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
字节流
字节流实现copy
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("c:/迅雷下载/告白.mp4");
fos = new FileOutputStream("d:/pixiv/告白.mp4");
System.out.println("正在拷贝中..........");
//方式一:单个字节进行拷贝,效率低下
/*int res = 0;
while((res = fis.read()) != -1){
fos.write(res);
}*/
//方式二:准备一个文件大小相同的缓存区,将文件内容一次性取出到缓冲区再写进去
//文件过大时,可能会有申请物理空间不足的情况出现
/* int len = fis.available();
System.out.println("文件大小为:"+len+"字节");
byte[] arr = new byte[len];
int res = fis.read(arr);
System.out.println("实际读取文件大小为:"+ res+"字节");
fos.write(arr);
System.out.println("文件拷貝成功。");*/
//方式三:准备一个大小适量的缓存区,将文件分部分进行写入写出
byte[] arr = new byte[1024];
int res = 0;
while ((res = fis.read(arr)) != -1){
fos.write(arr,0,res);
}
System.out.println("文件拷贝成功。");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null != fos){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != fis){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStreamWriter类、InputStreamReader类
java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换。
java.io.InputStreamReader类主要用于实现从字节流到字符流的转换。
public static void main(String[] args) {
BufferedReader br = null;
PrintStream pr = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
pr = new PrintStream(new FileOutputStream("d:/pixiv/a.txt"));
boolean flag = true;
while (true) {
System.out.println("请"+(flag?"我":"你")+"输入聊天内容:");
String s = br.readLine();
if ("bye".equals(s)){
System.out.println("再见。");
break;
}/*else {*/
Date data = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
pr.println(sdf.format(data)+(flag?" 我说:":" 你说:")+s);
}
flag = !flag;
// }
pr.println();
pr.println();
pr.println();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != pr) {
pr.close();
}
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ObjectOutputStream类、ObjectInputStream类
- java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。
- 所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。
- 序列化版本号
序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。 - transient关键字
transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行
化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进
去的。 - 当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,从而避免了通过返回值进行是否达到文件末尾的判断。
创建一个类
需要继承SerialLizable接口实现序列化
public class User implements java.io.Serializable{
private static final long serialVersionUID = -2389283813875441961L;
private String name;
private String password;
private transient String phone;
对象流序列化写入文件
public static void main(String[] args) {
ObjectOutputStream oot = null;
try {
oot = new ObjectOutputStream(new FileOutputStream("d:/pixiv/ObjectOutputStreamTest.txt"));
User user = new User("zyh", "1234567890", "3256");
oot.writeObject(user);
System.out.println("对象写入成功。");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != oot){
try {
oot.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
反序列化读出
ois = new ObjectInputStream(new FileInputStream("d:/pixiv/ObjectOutputStreamTest.txt"));
Object obj = ois.readObject();
System.out.println(obj);
RandomAccessFile类
// 1.创建RandomAccessFile类型的对象与d:/a.txt文件关联
raf = new RandomAccessFile("d:/a.txt", "rw");
// 2.对文件内容进行随机读写操作
// 设置距离文件开头位置的偏移量,从文件开头位置向后偏移3个字节 aellhello
raf.seek(3);
int res = raf.read();
System.out.println("读取到的单个字符是:" + (char)res); // a l
res = raf.read();
System.out.println("读取到的单个字符是:" + (char)res); // h 指向了e
raf.write('2'); // 执行该行代码后覆盖了字符'e'
System.out.println("写入数据成功!");
多线程
- 线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程。多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制。
线程的创建
自定义类继承Thread类并重写run方法
public class SubThreadRun extends Thread {
@Override
public void run() {
// 打印1 ~ 20之间的所有整数
for (int i = 1; i <= 20; i++) {
System.out.println("run方法中:i = " + i); // 1 2 ... 20
}
}
}
然后创建该类的对象调用start方法
public static void main(String[] args) {
// 1.声明Thread类型的引用指向子类类型的对象
Thread t1 = new SubThreadRun();
// 2.调用run方法测试,本质上就是相当于对普通成员方法的调用,因此执行流程就是run方法的代码执行完毕后才能继续向下执行
//t1.run();
// 用于启动线程,Java虚拟机会自动调用该线程类中的run方法
// 相当于又启动了一个线程,加上执行main方法的线程是两个线程
t1.start();
// 打印1 ~ 20之间的所有整数
for (int i = 1; i <= 20; i++) {
System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20
}
}
继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
自定义类实现Runnable接口并重写run方法
public class SubRunnableRun implements Runnable {
@Override
public void run() {
// 打印1 ~ 20之间的所有整数
for (int i = 1; i <= 20; i++) {
System.out.println("run方法中:i = " + i); // 1 2 ... 20
}
}
}
创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法。
public static void main(String[] args) {
// 1.创建自定义类型的对象,也就是实现Runnable接口类的对象
SubRunnableRun srr = new SubRunnableRun();
// 2.使用该对象作为实参构造Thread类型的对象
// 由源码可知:经过构造方法的调用之后,Thread类中的成员变量target的数值为srr。
Thread t1 = new Thread(srr);
// 3.使用Thread类型的对象调用start方法
// 若使用Runnable引用构造了线程对象,调用该方法(run)时最终调用接口中的版本
// 由run方法的源码可知:if (target != null) {
// target.run();
// }
// 此时target的数值不为空这个条件成立,执行target.run()的代码,也就是srr.run()的代码
t1.start();
//srr.start(); Error
// 打印1 ~ 20之间的所有整数
for (int i = 1; i <= 20; i++) {
System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20
}
}
}
实现Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中推荐使用第二种方式。
用匿名内部类创建线程
public static void main(String[] args) {
// 匿名内部类的语法格式:父类/接口类型 引用变量名 = new 父类/接口类型() { 方法的重写 };
// 1.使用继承加匿名内部类的方式创建并启动线程
/*Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("张三说:在吗?");
}
};
t1.start();*/
new Thread() {
@Override
public void run() {
System.out.println("张三说:在吗?");
}
}.start();
// 2.使用实现接口加匿名内部类的方式创建并启动线程
/*Runnable ra = new Runnable() {
@Override
public void run() {
System.out.println("李四说:不在。");
}
};
Thread t2 = new Thread(ra);
t2.start();*/
/*new Thread(new Runnable() {
@Override
public void run() {
System.out.println("李四说:不在。");
}
}).start();*/
// Java8开始支持lambda表达式: (形参列表)->{方法体;}
/*Runnable ra = ()-> System.out.println("李四说:不在。");
new Thread(ra).start();*/
new Thread(()-> System.out.println("李四说:不在。")).start();
}
管理线程编号和名称
用继承方式
public class ThreadIdNameTest extends Thread {
public ThreadIdNameTest(String name) {
super(name); // 表示调用父类的构造方法
}
@Override
public void run() {
System.out.println("子线程的编号是:" + getId() + ",名称是:" + getName()); // 14 Thread-0 guanyu
// 修改名称为"zhangfei"
setName("zhangfei");
System.out.println("修改后子线程的编号是:" + getId() + ",名称是:" + getName()); // 14 zhangfei
}
public static void main(String[] args) {
ThreadIdNameTest tint = new ThreadIdNameTest("guanyu");
tint.start();
// 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用
Thread t1 = Thread.currentThread();
System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());
}
}
用实现方式
public class RunnableIdNameTest implements Runnable {
@Override
public void run() {
// 获取当前正在执行线程的引用,也就是子线程的引用
Thread t1 = Thread.currentThread();
System.out.println("子线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName()); // 14 guanyu
t1.setName("zhangfei");
System.out.println("修改后子线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName()); // 14 zhangfei
}
public static void main(String[] args) {
RunnableIdNameTest rint = new RunnableIdNameTest();
//Thread t2 = new Thread(rint);
Thread t2 = new Thread(rint, "guanyu");
t2.start();
// 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用
Thread t1 = Thread.currentThread();
System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());
}
sleep()
public class ThreadSleepTest extends Thread{
private boolean flag = true;
@Override
public void run(){
while (flag){
Date d1 = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd hh:mm:ss");
System.out.println(sdf.format(d1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadSleepTest tst = new ThreadSleepTest();
tst.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tst.flag = false;
}
线程的优先级管理
public class ThreadPriorityTest extends Thread {
@Override
public void run() {
//System.out.println("子线程的优先级是:" + getPriority()); // 5 10 优先级越高的线程不一定先执行。
for (int i = 0; i < 20; i++) {
System.out.println("子线程中:i = " + i);
}
}
public static void main(String[] args) {
ThreadPriorityTest tpt = new ThreadPriorityTest();
// 设置子线程的优先级
tpt.setPriority(Thread.MAX_PRIORITY);
tpt.start();
Thread t1 = Thread.currentThread();
//System.out.println("主线程的优先级是:" + t1.getPriority()); // 5 普通的优先级
for (int i = 0; i < 20; i++) {
System.out.println("--主线程中:i = " + i);
}
}
守护线程
public class ThreadDaemonTest extends Thread {
@Override
public void run() {
//System.out.println(isDaemon()? "该线程是守护线程": "该线程不是守护线程"); // 默认不是守护线程
// 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止
// 当子线程是守护线程时,当主线程结束后,则子线程随之结束
for (int i = 0; i < 50; i++) {
System.out.println("子线程中:i = " + i);
}
}
public static void main(String[] args) {
ThreadDaemonTest tdt = new ThreadDaemonTest();
// 必须在线程启动之前设置子线程为守护线程
tdt.setDaemon(true);
tdt.start();
for (int i = 0; i < 20; i++) {
System.out.println("-------主线程中:i = " + i);
}
}
线程同步机制
继承Thread同步方法
public class AccountThreadTest extends Thread {
private int balance; // 用于描述账户的余额
private static Demo dm = new Demo(); // 隶属于类层级,所有对象共享同一个
public AccountThreadTest() {
}
public AccountThreadTest(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public /*static*/ /*synchronized*/ void run() {
/*System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
//synchronized (dm) { // ok
//synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
// 1.模拟从后台查询账户余额的过程
int temp = getBalance(); // temp = 1000 temp = 1000
// 2.模拟取款200元的过程
if (temp >= 200) {
System.out.println("正在出钞,请稍后...");
temp -= 200; // temp = 800 temp = 800
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("请取走您的钞票!");
} else {
System.out.println("余额不足,请核对您的账户余额!");
}
// 3.模拟将最新的账户余额写入到后台
setBalance(temp); // balance = 800 balance = 800
//}*/
test();
}
public /*synchronized*/ static void test() {
synchronized (AccountThreadTest.class) { // 该类型对应的Class对象,由于类型是固定的,因此Class对象也是唯一的,因此可以实现同步
System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
//synchronized (dm) { // ok
//synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
// 1.模拟从后台查询账户余额的过程
int temp = 1000; //getBalance(); // temp = 1000 temp = 1000
// 2.模拟取款200元的过程
if (temp >= 200) {
System.out.println("正在出钞,请稍后...");
temp -= 200; // temp = 800 temp = 800
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("请取走您的钞票!");
} else {
System.out.println("余额不足,请核对您的账户余额!");
}
// 3.模拟将最新的账户余额写入到后台
//setBalance(temp); // balance = 800 balance = 800
}
}
public static void main(String[] args) {
AccountThreadTest att1 = new AccountThreadTest(1000);
att1.start();
AccountThreadTest att2 = new AccountThreadTest(1000);
att2.start();
System.out.println("主线程开始等待...");
try {
att1.join();
//t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
att2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终的账户余额为:" + att1.getBalance()); // 800
}
继承Runnable接口的同步方法
public class AccountRunnableTest implements Runnable {
private int balance; // 用于描述账户的余额
private Demo dm = new Demo();
private ReentrantLock lock = new ReentrantLock(); // 准备了一把锁
public AccountRunnableTest() {
}
public AccountRunnableTest(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public /*synchronized*/ void run() {
// 开始加锁
lock.lock();
// 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account,也就是说this就是account
//synchronized (this) { // ok
System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
//synchronized (dm) { // ok
//synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
// 1.模拟从后台查询账户余额的过程
int temp = getBalance(); // temp = 1000 temp = 1000
// 2.模拟取款200元的过程
if (temp >= 200) {
System.out.println("正在出钞,请稍后...");
temp -= 200; // temp = 800 temp = 800
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("请取走您的钞票!");
} else {
System.out.println("余额不足,请核对您的账户余额!");
}
// 3.模拟将最新的账户余额写入到后台
setBalance(temp); // balance = 800 balance = 800
//}
lock.unlock(); // 实现解锁
}
public static void main(String[] args) {
AccountRunnableTest account = new AccountRunnableTest(1000);
//AccountRunnableTest account2 = new AccountRunnableTest(1000);
Thread t1 = new Thread(account);
Thread t2 = new Thread(account);
//Thread t2 = new Thread(account2);
t1.start();
t2.start();
System.out.println("主线程开始等待...");
try {
t1.join();
//t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终的账户余额为:" + account.getBalance()); // 600 800
}
}
class Demo{}
线程安全与死锁问题
-
StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。
-
Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。
-
Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。
死锁
- 线程一执行的代码:
public void run(){
synchronized(a){ //持有对象锁a,等待对象锁b
synchronized(b){
编写锁定的代码;
}
}
}
- 线程二执行的代码:
public void run(){
synchronized(b){ //持有对象锁b,等待对象锁a
synchronized(a){
编写锁定的代码;
}
}
}
同时运行形成死锁
在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用
线程之间的通行实现(生产者消费者模型)
建立生产者,消费者
public class StoreHouse {
private int cnt = 0;
public synchronized void ProduceProduct() {
notify();
if (cnt < 10) {
System.out.println("线程为" + Thread.currentThread().getName() + "正在生产第" + (cnt + 1) + "个产品。");
cnt++;
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void ConsumerProduct() {
notify();
if (cnt > 0){
System.out.println("线程为" + Thread.currentThread().getName() + "正在消费第" + (cnt-1)+"个产品。");
cnt--;
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两者与“仓库”进行联系
确保利用的是同一对象(消费者与生产者构造相同 )
public class ProduceThread extends Thread {
private StoreHouse storeHouse;
public ProduceThread(StoreHouse storeHouse){
this.storeHouse = storeHouse;
}
@Override
public void run(){
while (true){
storeHouse.ProduceProduct();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两线程实现交互通信
public static void main(String[] args) {
StoreHouse storeHouse = new StoreHouse();
ProduceThread t1 = new ProduceThread(storeHouse);
ConsumerThread t2 = new ConsumerThread(storeHouse);
t1.start();
t2.start();
线程池
- 线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
- 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadpoolTest {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
es.submit(new ThreadCallableTest());
es.shutdown();
}
实现Callable接口&FutureTask类
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadCallableTest implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10000; i++) {
sum += i;
}
System.out.println("累加和为: " + sum);
return sum;
}
public static void main(String[] args) {
ThreadCallableTest tct = new ThreadCallableTest();
FutureTask ft = new FutureTask(tct);
Thread t1 = new Thread(ft);
t1.start();
Object obj = null;
try {
obj = ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("线程处理方法的返回值是: "+obj);
}
网络编程
服务器与客户端通信的实现
客户端
public static void main(String[] args) {
Socket = null;
PrintStream ps = null;
Scanner sc = null;
BufferedReader br = null;
try {
// 1.创建Socket类型的对象并提供服务器的主机名和端口号
s = new Socket("127.0.0.1", 8888);
System.out.println("连接服务器成功!");
// 2.使用输入输出流进行通信
sc = new Scanner(System.in);
ps = new PrintStream(s.getOutputStream());
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
while(true) {
//Thread.sleep(10000);
// 实现客户端发送的内容由用户从键盘输入
System.out.println("请输入要发送的数据内容:");
String str1 = sc.next();
// 实现客户端向服务器发送字符串内容"hello"
//ps.println("hello");
ps.println(str1);
System.out.println("客户端发送数据内容成功!");
// 当发送的数据内容为"bye"时,则聊天结束
if ("bye".equalsIgnoreCase(str1)) {
System.out.println("聊天结束!");
break;
}
// 实现接收服务器发来的字符串内容并打印
String str2 = br.readLine();
System.out.println("服务器回发的消息是:" + str2);
}
服务器
public static void main(String[] args) {
ServerSocket ss = null;
Socket s = null;
try {
// 1.创建ServerSocket类型的对象并提供端口号
ss = new ServerSocket(8888);
// 2.等待客户端的连接请求,调用accept方法
while(true) {
System.out.println("等待客户端的连接请求...");
// 当没有客户端连接时,则服务器阻塞在accept方法的调用这里
s = ss.accept();
System.out.println("客户端" + s.getInetAddress() + "连接成功!");
// 每当有一个客户端连接成功,则需要启动一个新的线程为之服务
new ServerThread(s).start();
}
采用多线程机制的实现
public class ServerThread extends Thread {
private Socket s;
public ServerThread(Socket s) {
this.s = s;
}
@Override
public void run() {
BufferedReader br = null;
PrintStream ps = null;
try {
// 3.使用输入输出流进行通信
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
ps = new PrintStream(s.getOutputStream());
while(true) {
// 实现对客户端发来字符串内容的接收并打印
// 当没有数据发来时,下面的方法会形成阻塞
String s1 = br.readLine();
InetAddress inetAddress = s.getInetAddress();
System.out.println("客户端" + inetAddress + "发来的字符串内容是:" + s1);
// 当客户端发来的内容为"bye"时,则聊天结束
if ("bye".equalsIgnoreCase(s1)) {
System.out.println("客户端" + inetAddress + "已下线!");
break;
}
// 实现服务器向客户端回发字符串内容"I received!"
ps.println("I received!");
System.out.println("服务器发送数据成功!");
}
基于udp协议的编程模型
发送方
public static void main(String[] args) {
DatagramSocket ds = null;
try {
ds = new DatagramSocket();
byte[] arr = "sbsbsbsbsbsbs".getBytes();
DatagramPacket dp = new DatagramPacket(arr, arr.length, InetAddress.getLocalHost(), 6868);
ds.send(dp);
System.out.println("数据发送成功。");
//接收回发内容
byte[] brr = new byte[1024];
DatagramPacket dp2 = new DatagramPacket(brr, brr.length);
ds.receive(dp2);
System.out.println("接收到的回发内容为: "+ new String(brr,0,dp2.getLength()));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != ds) {
ds.close();
}
}
}
接收方
public static void main(String[] args) {
DatagramSocket ds = null;
try {
ds = new DatagramSocket(6868);
byte[] brr = new byte[1024];
DatagramPacket dp = new DatagramPacket(brr, brr.length);
System.out.println("等待数据中........");
ds.receive(dp);
System.out.println("接受的数据为: "+new String(brr,0,dp.getLength()));
//回发数据
byte[] brr2 = "i got it".getBytes();
DatagramPacket dp2 = new DatagramPacket(brr2, brr2.length, dp.getAddress(), dp.getPort());
ds.send(dp2);
System.out.println("数据回发成功。");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != ds) {
ds.close();
}
}