多线程编程和Java网络编程
1. 线程概述
多任务处理有两种类型:基于进程、基于线程(进程是指一种“自包容”的运行程序,有自己的地址空间; 线程是进程内部单一的一个顺序控制流)
基于进程的特点是允许计算机同时运行两个或更多的程序、基于线程的多任务处理环境中,线程是最小的处理单位
2. 创建和启动线程
a. 编写一个继承Thread 类的类,然后重写Thread类的run()方法
public class thread extends Thread {
public void run() {
}
public static void main(String[] args) {
thread d = new thread();
d.start();
}
}
b. 编写一个类实现Runnable接口,然后将该类的实例与Thread对象联系在一起
public class threadrun implements Runnable { public void run(){
}
public static void main(String[] args) {
threadrun td = new threadrun();
Thread t = new Thread(td);
t.start();
}
}
3. 线程的优先级
A. Java 中的线程优先级是在 Thread 类中定义的常量:
NORM_PRIORITY : 值为 5
MAX_PRIORITY : 值为 10
MIN_PRIORITY : 值为 1
B . 有关优先级的方法有两个:
a. final void setPriority(int newp) : 修改线程的当前优先级
b. final int getPriority() : 返回线程的优先级
4. 使线程暂停执行的条件
A. 使用 sleep( ) 方法使线程睡眠
B. 通过调用 wait( ) 方法,使线程等待
C. 通过调用 yield( ) 方法,线程已显式出让CPU控制权
D. 线程由于等待一个I/O事件被阻塞
5. 线程同步:使用同步关键字synchronized
A.解决线程同步的方法:
a. 同步块
语法:synchronized (取得锁的对象){
//要锁定的代码
}
b.同步方法:在方法名关键字修饰前面使用synchronized
6. 死锁: 当两个线程循环依赖于一对同步对象时将发生死锁
7. 线程之间的相互通讯
方法:
a . 网络编程的实质就是编写程序直接或间接地通过网络协议与其它计算机上的某个程序进行通讯
b. 如何找到网络上的主机上的要进行通讯的程序 ?
c. 提供了IP地址和端口号,就可以找到网络上指定主机上要进行通讯的指定程序
a. 数据传输由TCP/IP分层模型中的传输层负责,该层包含TCP和UDP两种协议
b. TCP协议:较可靠的双向流协议、发送任意数量的数据、提供消息确认、错误检测和错误恢复等服务
c. UDP协议:比较不可靠
10 . 客户端和服务器
A. 服务器和客户端共同承担计算
a. 客户:向另一台计算机请求服务的计算机
b. 服务器:处理客户端请求的计算机
JDK预定义的网络编程相关类均存放在java.net包中。
常用的类:InetAddress 、Socket、ServerSocket 和 SocketImpl、DatagramPacket 和 DatagramSocket、 URL、URLConnection 和 URLEncoder
该类没有构造方法,所以不能直接利用new 关键字创建对象。我们可以使用以下常用方法来获取该类对象:
getLocalHost():返回表示本地主机InetAddress对象
getByName(String hostName):据主机名返回对象
getAddress() :返回IP地址
getHostName():取得IP地址代表的主机名
两个java程序通过一个双向的网络通信连接实现数据交换,这个双向链路的一端称之为Socket( Socket通常用来实现客户服务器之间的连接)
java.net包中的两个类Socket、ServerSocket分别用来实现客户端和服务端
A. 服务端( ServerSocket )编写步骤:
1).利用ServerSocket建立对服务端某个端口的监听
2).利用accept方法创建服务端Socket
3).利用已建立的socket创建输入输出流
4).关闭输入输出流,关闭socket,关闭server
B. 客服端( Socket )编写步骤:
1).创建客户端Socket向服务器发起连接请求
2).利用已建立的socket创建输入输出流
3).关闭输入输出流,关闭socket,关闭server
A. 用户报文协议(UDP)是用于将二进制数据从一台计算机发送到另一台计算的非连接协议
B. 数据报包的发送者和接收者都使用java.net.DatagramSocket类分别发送和接收包
D. 接收数据报包需要执行如下步骤:
1). 创建一个足够大的字节数组,用于存储要接收的包的数据
2). 使用该字节数组实例化一个DatagramPacket对象
3). DatagramSocket被实例化,它被指定该套接字要绑定到的本地主机上的一个端口
4). 调用DatagramSocket类的receive()方法,将DatagramPacket对象传入该方法。这将导致执行线程阻塞,直到接收到一个数据报包或者发生了超时
E. 发送数据报包需要执行如下步骤:
1). 创建一个足够大的字节数组,用于存储要发送的包数据,用该数据填充数组
2). 创建一个新的DatagramPacket 对象,用于存储上面的字节数组,以及服务器名和接收者的端口号
3). DatagramSocket被实例化,它被指定套接字要绑定到本地主机的哪个端口
4). DatagramSocket类的send()方法被调用,传入DatagramPacket对象
例题:简易局域网聊天系统–局域网QQ:
服务器端
实现简易的启动停止服务器端操作,能记录基本日志:客户连接、消息传送,能查看连接客户的昵称。
启动服务器后开始监听客户端连接,创建一个新线程实现该监听操作。
// 启动新线程监听客户端
new Thread(new Runnable() {
public void run() {
writeLog("开始监听客户端:");
listen();
}
}).start();
为每一个客户端连接开启一个新线程处理通讯,包括处理输入流与输出流。
// 每监听到一个客户端连接,启动一个新线程处理该连接
new Participant(this, socket, usersList).start();
获取用户不同的行为实现不同的操作 String flag = in.readUTF();
// 获取用户操作行为
if ("validateUser".equals(flag)) { // 验证用户是否存在
// ……………………
} else if ("newUser".equals(flag)) { // 新用户登录
// ……………………
} else if ("message".equals(flag)) { // 用户间发送消息
// ……………………
} else if ("userList".equals(flag)) { // 获取用户列表
// ……………………
}
在服务器端,采用Map保存每个客户端对应的套接字:
private Map<String, Socket> usersList = new HashMap<String, Socket>();
key为客户端连接时输入的用户昵称,value为对应的套接字对象。
各客户端用户间发送消息时,通过服务器转发送该信息:
String sender = in.readUTF(); // 发送者
String receiver = in.readUTF(); // 接收者
String message = in.readUTF(); // 消息
// 添加日志
server.writeLog(sender + " 向 " + receiver + "发送消息:" + message);
// 服务器向目标转发消息,先获得要转发后的目标套接字对象
Socket socket = server.getUsersList().get(receiver);
System.out.println(out);
if (socket == null) { // 服务器已删除该客户端连接信息
out.writeUTF("message");
out.writeUTF("系统服务器");
out.writeUTF(receiver + "已退出系统,无法再发送消息");
out.flush();
} else {
DataOutputStream thatOut = new DataOutputStream(socket.getOutputStream()); // 创建输出流对象
if (thatOut != null) {
thatOut.writeUTF("message");
thatOut.writeUTF(sender);
thatOut.writeUTF(message);
thatOut.flush(); // 写入客户端输出流中
}
}
客户端
先通过登录窗体实现服务器端连接,然后输入登录用户昵称,如果服务器端还未保存该昵称,则登录成功。登录成功后,可以直接先读取到服务器端已有登录用户的昵称,显示到列表中。双击好友列表中的任一项打开聊天对话框,然后双方可以开始聊天。
创建到服务器的套接字对象:
// 创建套接字对象
socket = new Socket(serverIp.getText().trim(), Integer.parseInt(port.getText().trim()));
连接的服务器与端口从窗体文本框中获取到。
登录成功后,将昵称发送到服务器保存:
out.writeUTF("newUser");
out.writeUTF(nickname); out.flush();
刷新好友列表:
// 读取流中信息
String flag = in.readUTF();
if ("userList".equals(flag)) { // 好友列表
String[] userList = in.readUTF().split("::::"); // 获取好友列表,使用标记分隔
// 创建好友列表模型
DefaultListModel list = new DefaultListModel();
for (int i = 0; i < userList.length; i++) {
list.addElement(userList[i]);
}
friends.setModel(list); // 设置模型
friends.validate(); // 重绘
}
发送消息给某个好友:
out.writeUTF("message");
out.writeUTF(sender);
out.writeUTF(recipient);
out.writeUTF(textArea.getText());
out.flush();
开启新线程读取服务器转发的好友信息:
String flag = in.readUTF();
if ("message".equals(flag)) {
String sender = in.readUTF();
String msg = in.readUTF();
String message = sender + " 对你说:" + msg + "\n";
jTextArea.append(message);
}