Java提供了两个不同层次的网络支持机制:
利用URL访问网络资源
利用Socket通信
一、统一资源定位符URL类
1、URL用来网络资源定位,它的值由5部分组成,格式如下所示
<传输协议>://<主机名>:<端口号>/<文件名>#<引用>
其中传输协议(protocol)指明获取资源所使用的传输协议,如http、ftp、file等。http表示通过HTTP访问网络资源。ftp表示通过网络协议FTP访问网络资源。file表示本机上文件,是所指定的资源。news表示通过NNTP访问指定新闻地址的资源。
主机名(hostname)指定资源所在的计算机,可以是IP地址,如127.0.0.1,也可以是主机名或域名,如www.sun.com。
一个计算机中可能有多种服务(应用程序),端口号(port)用来区分不同的网络服务,如http服务的默认端口号是80,ftp服务的默认端口号是21等。理论上有0-65535个端口,其中1024号以下端口一般都给定了用途,例如HTTP使用80端口;FTP默认21端口;Telnet使用23端口。平常我们使用1024号以上端口。
文件名(filename)包括该文件的完整路径。在http协议中,缺省的文件名是index.html,因此,http://java.sun.com就相等同于 http://java.sun.com/index.html。
引用(reference)为资源内的某个引用,用来定位显示文件内容的位置,如http://java.sun.com/index.html#chapter1。但并非所有的URL都包含这些元素。对于多数的协议,主机名和文件名是必需的,但端口号和文件内部的引用则是可选的。
2、URL类位于java.net包中,是指向互联网资源的指针,使用URL可以通过给定的URL地址访问到需要的资源。资源可以是简单的文件和目录,也可以是对象的引用、对数据库或搜索引擎的查询。
URL类的对象代表一个URL地址。URL对象的创建示例:
其构造方法:
1)通过指定的URL字符串创建URL对象
URL gamelan= new URL("http://www.gamelan.com/pages/Gamelan.net.html");
2)通过指定的协议、主机地址、路径字符串创建URL对象
URL gamelan =new URL("http", "www.gamelan.com","/pages/Gamelan.net.html");
3)通过指定的协议、主机地址、端口号、路径字符串创建URL对象
URL gamelan = new URL("http", "www.gamelan.com",80, "pages/Gamelan.network.html");
上两种方法将一个URL地址分解,按不同部分分别指定协议、主机、端口、文件。
4) 通过指定的上下文中对指定的协议的解析创建对象:URL(URL context, String spec)
这种方法基于一个已有的URL对象创建一个新的URL对象,多用于访问同一个主机上不同路径的文件,例如:
URL u1=new URL(u,”tutorial.intro.html”);
创建URL后,可以对其操作:
public int getDefaultItPort(); 获取与此URL关联的默认端口号
public String getFile();获取此URL的文件名
public String getHost();获取此URL的主机名
public String getPath();获取此URL的路径部分
public int getPort();获取此URL的端口号
public String getProtocol ();获取此URL的协议名称
public String getQuery();获取此URL的查询部分
public InputStream openStream();打开到此URL的连接并返回一个用于从该连接读入的InputStream。
3、URLConnection抽象类
该抽象类代表应用程序和URL之间的通信链接。
创建一个到URL的连接步骤:
1)对影响到远程资源连接的参数操作,通过在URL上调用openConnection方法创建连接对象。
2)除了设置参数和一般请求属性。
3)使用connect方法建立到远程对象的实际连接,与资源交互;查询头字段和内容。
4)远程对象变为可用。远程对象的头字段和内容变为可访问。
建立到远程对象的连接后,可以使用以下方法对头字段和内容进行访问。
public Object getContent() throws IOException; 获取此URL链接的内容
public String getHeaderField(int n); 获取指定头字段的值
public InputStream getInputStream() throws IOException; 获取输入流,如果读超时,会抛出SockeTimeoutException。
public OutputStream getOutputStream() throws IOException; 获取输出流
public String getContentEncoding(); 获取content-encoding的值
public int getContentLength(); 获取content-length的值
public String getContentType(); 获取content-type的值
public long getDate(); 获取date的值
public long getLastModified(); 获取last-modified的值,结果为距离格林威治标准时间的毫秒数。
以下是测试一例。
import java.io.*;
import java.net.*;
import java.util.*;
//写URL的测试类,获取网站的信息,并将网站主页下载到本地磁盘上
public class URLTest
{//主方法
public static void main(String[] args)
{
try
{
//定义urlName的字符串,如果args数组长度大于0,则将args[0]的值赋给urlName
String urlName="http://www.itxx8.cn/index.asp";
if (args.length > 0)
{
urlName = args[0];
}
URL url = new URL(urlName);//创建URL对象
System.out.println("打印url的一些信息:");
System.out.println("getProtocol:"+ url.getProtocol());//获取协议名称
System.out.println("getthost:" + url.getHost());//获取主机名称
System.out.println("gettfile:" + url.getFile());//获取此 URL 的文件名
System.out.println("getPath:" + url.getPath());//获取路径
System.out.println("getPort:" + url.getPort());//获取端口号,未设置返回-1
System.out.println("getDefaultPort:" +url.getDefaultPort());//返回默认端口号
URLConnection connection =url.openConnection();//打开远程对象的连接,返回连接值
connection.connect();//连接到服务器,打开此URL引用的资源的通信链接
//获取并打印头字段的值
System.out.println("打印头字段信息:");
int n = 1;
String key;
while ((key =connection.getHeaderFieldKey(n)) != null)
{
String value =connection.getHeaderField(n);//返回第n个头字段的值
System.out.println(key + ":" + value);//打印头字段的键、值
n++;
}
//打印引用资源的一些属性
System.out.println("打印引用资源的一些属性");
System.out.println("getContentType:"
+ connection.getContentType());//返回引用资源的内容类型
System.out.println("getContentLength: "
+connection.getContentLength());//返回引用资源的内容长度
System.out.println("getContentEncoding: "
+ connection.getContentEncoding());//返回引用资源的内容编码
//返回引用资源的发送日期,为距离格林威治标准时间1970年1月1日的毫秒数。
System.out.println("getDate:"+ connection.getDate());
//返回引用资源上次的修改日期,若未知返回0
System.out.println("getLastModifed:"+connection.getLastModified());
//下载引用文件到本地磁盘,读取文件
BufferedReader in = newBufferedReader(new
InputStreamReader(connection.getInputStream()));
// //打印输出源文件前10行
// String line;
// n = 1;
// while ((line = in.readLine()) != null&& n <= 10)
// {//读出文件并输出
// System.out.println(line);
// n++;
// }
// if (line != null)
// System.out.println(". . . . . .");
//创建输出流,并指定目标文件
BufferedWriter bw=new BufferedWriter(newFileWriter("e://URLTest.html"));
//对输出流做进一步进行封装
PrintWriter pw=new PrintWriter(bw);
//声明临时字符串引用
String temps=null;
//从输入流中获取资源并测试是否读取完毕
while((temps=in.readLine())!=null)
{
//将获取的数据写如目标文件
pw.println(temps);
}
//打印提示信息
System.out.println("您好,网站主页已经下载完毕,已经写入了URLTest.html");
pw.close();//关闭输出流
in.close();//关闭输入流
}
catch (IOException exception)
{
exception.printStackTrace();
}
}
}
二、Socket编程
传统的C/S模式的网络程序主要通过服务器端和客户端的网络连接来实现数据传输。服务器端和客户端的网络连接主要是TCP Socket连接。在java.net包中提供了Socket和ServerSocket类来实现连接。
在Socket编程中发送方和接收方的两个Socket之间必须先连接,再才能在TCP协议的基础上进行通信。
服务器的任务就是等候建立一个连接,然后用那个连接产生的Socket 创建一个InputStream 以及一个OutputStream。之后,从InputStream 读入的所有东西都会反馈给OutputStream,直到接收到行中止(END)为止,最后关闭连接。客户机连接与服务器的连接,然后创建一个OutputStream。文本行通过OutputStream 发送。客户机也会创建一个InputStream,用它收听服务器说些什么。服务器与客户机(程序)都使用同样的端口号,而且客户机利用本地主机地址连接位于同一台机器中的服务器(程序),所以不必在一个物理性的网络里完成测试。
注意,ServerSocket 只要一个端口编号,不需要IP 地址(因为它就在这台机器上运行)。调用accept()时,方法暂时陷入停顿状态,直到某个客户尝试同它建立连接。建好一个连接以后,accept()会返回一个Socket对象,它是那个连接的代表。假如ServerSocket 构建器失败,则程序简单地退出(注意必须保证ServerSocket 的构建器在失败之后不会留下任何打开的网络套接字)。针对这种情况,main()会“掷”出一个IOException违例,所以不必使用一个try块。若ServerSocket构建器成功执行,则其他所有方法调用都必须到一个try-finally 代码块里寻求保护,以确保无论块以什么方式留下,ServerSocket 都能正确地关闭。
同样的道理也适用于由accept()返回的Socket。若accept() 失败,那么我们必须保证Socket 不再存在或者含有任何资源,以便不必清除它们。但假若执行成功,则后续的语句必须进入一个try-finally 块内,以保障在它们失败的情况下,Socket 仍能得到正确的清除。由于套接字使用了重要的非内存资源,所以在这里必须特别谨慎,必须自己动手将它们清除。
无论ServerSocket还是由accept()产生的Socket 都打印到System.out 里。这意味着它们的toString方法会得到自动调用。这样便产生了:
ServerSocket[addr=0.0.0.0,PORT=0,localport=8080]
Socket[addr=127.0.0.1,PORT=1077,localport=8080]
在后面的程序中大家会看到它们如何与客户程序做的事情配合。
服务器端程序:
mport java.io.*;
import java.net.*;
//写服务器端,对端口进行监听,接受客户端请求并为客户端服务
public class TcpServer{
publicstatic void main(String[] args)
{//服务器对8888端口进行监听,接受到客户端连接请求后,创建和启动服务线程,
longcount = 0;//声明用来计数的变量
ServerSocketserverSocket = null;//声明服务器套接字
try{
serverSocket= new ServerSocket(8888);//创建绑定到8888端口的ServerSocket对象
System.out.println("服务器对8888端口实施监听......");//打印提示信息
//服务器循环接收客户端的请求,为不同的客户端提供服务
while(true) {
Socketsocket = serverSocket.accept();//接收客户端的连接请求,若有连接请求返回连接对应的Socket对象
count++;
ServerThreadserverThread = new ServerThread(socket, count);//创建服务器线程
serverThread.start();//启动服务器线程
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
//定义ServerThread类,继承自Thread类
class ServerThread extends Thread
{//根据客户端和计数器来创建服务器线程
Socketsocket;
longcount;
//构造方法
publicServerThread(Socket socket, long c)
{
this.count= c;
this.socket= socket;
}
//重写run方法
publicvoid run()
{
inttimeCounter=0;
try
{
InputStreamins = socket.getInputStream();//获取套接字的输入流
InputStreamReaderisr = new InputStreamReader(ins);//封装输入流
BufferedReaderbr = new BufferedReader(isr);//封装输入流
OutputStreamos = socket.getOutputStream();//获取套接字的输入流
PrintStreampw = new PrintStream(os);//封装输出流
while(true) {
timeCounter++;//声明用来计数的变量
Stringstr = br.readLine();//定义字符串为读取br的值
if(str.equals("exit"))
{//如果客户端输入的是“exit”,则关闭客户端
pw.println("exit");
pw.flush();
socket.close();
break;
}
//告知客户端是第几次转换,有几个客户端,并输出转换的字符串
pw.println("这是 " + socket.toString() +"第"+timeCounter+"次发送转换请求,现在有"+count+"个客户在线,转换后的字符串为"
+str.toUpperCase());
pw.flush();
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
客户端程序:
import java.io.*;
import java.net.Socket;
//客户端程序
public class TcpClient
{
publicstatic void main(String[] args)
{
try{
Socketsocket = new Socket("127.0.0.1", 8888);//创建套接字,传入IP和端口
booleanflag=true;//定义布尔型变量
OutputStreamos = socket.getOutputStream();//返回套接字输出流
PrintStream ps = new PrintStream(os);//封装输出流
InputStreamins = socket.getInputStream();//返回套接字输入流
InputStreamReaderisr = new InputStreamReader(ins);//封装输入流
BufferedReaderbr = new BufferedReader(isr);//封装输入流
Stringstr = null;//定义字符串
//获取输入的字符串
BufferedReaderkbr = new BufferedReader(new InputStreamReader
(System.in));
while(flag) {
//提示用户输入小写字母,服务器将转换成大写
System.out.println("请输入您要转换的小写字母,服务器将为您转换成
大写字母");
System.out.print("请输入:");
StringinputString = kbr.readLine();//定义获取的字符串
ps.println(inputString);//写到服务器端
if((str = br.readLine()) != null) {//读取数据
if(str.equals("exit")){//如果输入exit,退出循环
flag=false;
}
System.out.println("服务器返回转换信息:"+str);
}
}
socket.close();//关闭客户端
} catch (Exception e) {
e.printStackTrace();
}
}
}
本程序操作方法:
开始、运行、CMD、cd到工作目录,打start,产生二个DOS命令操作界面,分别在二个界面中编译与执行服务器端程序和客户端程序。在客户端输入小写字母等,将显示将其中小写字符变成大写字符的效果。
三、UDP Socket编程
UDP Socket的信息传输方式类似于发传真,发送端先准备好要发送的资料,打电话给接收端,接收端准备好纸张,回一个信号,再开始传真。数据报套接字(DatagramSocket)不需要创建两个Socket,不可以使用输入、输出流。
import java.net.*;
//定义UDP接收端
public class UDPServer {
publicstatic void main(String[] args) {
byte[]bufferIn=new byte[26];
byte[]bufferOut;
try{
//建立信箱,创建数据包套接字并绑定到13端口
DatagramSocketds=new DatagramSocket(13);
System.out.println("UDP接收端启动,在13端口..." );
intnum=0;
while(num<100){
num++;
//准备收件信封,接收长度为length的数据包
DatagramPacketpacketIn=new DatagramPacket(bufferIn,bufferIn.length);
ds.receive(packetIn);//收信,接受数据报
InetAddressia=packetIn.getAddress();//通过信封得到发信人的地址和端口号
intport=packetIn.getPort();//获取端口
Stringstr=new String(bufferIn);//获取字符串
str=str.toUpperCase();//把字符串变成大写
bufferOut=str.getBytes();//给输出数组赋值
System.out.println("收到的第"+num+"封信,来源ip是"+ia.toString()+",端口号为"+port);
//发信的信封
DatagramPacketpacketOut=new DatagramPacket(bufferOut,bufferOut.length,ia,port);
ds.send(packetOut);//发信(回信)
}
ds.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
客户端程序:
import java.io.*;
import java.net.*;
//定义UDP发送端
public class UDPClient {
publicstatic void main(String[] args) {
byte[]bufferOut=new byte[26];
byte[]bufferIn=new byte[26];
for(inti=0;i<bufferOut.length;i++){
bufferOut[i]=(byte)(‘a‘+i);//定义数组元素
}
try{
//建立信箱,创建数据包套接字并绑定到3333端口
DatagramSocketds=new DatagramSocket(4444);
InetAddressia=InetAddress.getByName(args[0]);//通过主机名确定主机的IP地址
//准备发信信封
DatagramPacketpacketOut=new DatagramPacket(bufferOut,bufferOut.length,ia,13);
ds.send(packetOut);//发信
//准备收件信封
DatagramPacketpacketIn=new DatagramPacket(bufferIn,bufferIn.length);
ds.receive(packetIn);//收信
Stringstr1=new String(packetIn.getData());
Stringstr2=new String(bufferIn);
System.out.println(str1);
System.out.println(str2);
ds.close();//6,关闭信箱
}catch (Exception e) {
e.printStackTrace();
}
}
}
四、网络聊天室
注:本程序关于登录的程序部分使用了awt和swing。需要改相应的内容。
1、协议接口
public interface Protocol {
finalstatic String SOFTWARE = "QQ聊天室";// 软件名
finalstatic String VERSION = "1.4";// 软件版本号
finalstatic String DEFAULT_IP = "127.0.0.1";// 默认IP地址
finalstatic int DEFAULT_PORT = 8888;// 默认端口号
finalstatic int NAME_MAX = 15;// 用户名最大长度
finalstatic int NAME_MIN = 4;// 用户名最小长度
//敏感词汇
finalstatic String[] FORBID_WORDS = { "萨达姆侯赛因", "切格瓦拉", "恐怖分子" };
finalstatic int TIME_BETWEEN_MSG = 2;// 防刷屏时间设置
finalstatic String SYSTEM_MSG = "1k3N1k";// 系统信息
finalstatic String ADD_USER = "d9Fa0j";// 新用户进入聊天室
finalstatic String DELETE_USER = "9fIk3l";//用户离开聊天室
finalstatic String EXIST_USERS = "9A3kmc";//已登陆用户
finalstatic String MSG_FROM = "4lCcj8";// 信息来源标记
finalstatic String NAME_END = "ci8Kja";// 名字信息结尾标记
finalstatic String USER_LOGOUT = "k8O0z2";// 用户退出聊天室
finalstatic String USER_EXIST = "USER_EXIST";// 用户名已有人使用
//登陆名已被使用的异常
finalstatic class ExistException extends Exception {
publicExistException() {
super("用户名已存在");
}
}
//版本过期异常
finalstatic class VersionException extends Exception {
publicVersionException(String ver) {
super("版本过期,最新版本为v" + ver);
}
}
}
2、服务器端
1)多现场
/**
* QQ聊天室服务器端
*/
import java.net.*;
import java.io.*;
import java.util.*;
public class Server implements Protocol {
/*
* 用户名存入key,线程存入对应value
*/
private static Map users = newHashMap();
/**
* 用户线程类,每个用户一个线程
*/
static class UserThread extendsThread {
private Socket s;
private String username ="";
private PrintWriter out;
private static int online;// 统计在线人数
private static String lock ="";
public UserThread(Socket s) {
this.s = s;
}
public void run() {
try {
/*
* 创建流
*/
InputStream is =s.getInputStream();
InputStreamReader ir= new InputStreamReader(is, "GBK");
//java.nio.charset.Charset.defaultCharset());
BufferedReader in =new BufferedReader(ir);
OutputStream os =s.getOutputStream();
OutputStreamWriter or= new OutputStreamWriter(os, "GBK");
out = newPrintWriter(or);
out.println(VERSION);
out.flush();
/*
* 判断版本是否过期
*/
if(!in.readLine().equals(VERSION)) {
throw new Exception("版本过期");
}
this.username =in.readLine();
synchronized (lock) {
/*
* 读取用户名,并判断是否已经存在
*/
if(isExist(this.username)) {
throw newExistException();
}
/*
* 登陆成功
*/
out.println(SYSTEM_MSG);
out.flush();
/*
* 通知所有人有新用户登陆
*/
sendAll(SYSTEM_MSG+ ADD_USER + this.username);
/*
* 刷新在线人数
*/
System.out.print("\rOnline:"+ ++online);
/*
* 给本线程用户发送在线用户列表
*/
listAll();
/*
* 将本用户加入集合
*/
users.put(this.username,this);
}
Stringmsg = "";
Stringtouser = "All";
while(!s.isClosed()) {
while(!s.isClosed() && (msg = in.readLine()) != null
&&msg.length() > 0) {
/*
* 收到用户退出的系统信息,删除集合中对应项,通知所有用户
*/
if(msg.startsWith(SYSTEM_MSG + USER_LOGOUT)) {
synchronized(lock) {
users.remove(this.username);
}
sendAll(SYSTEM_MSG+ DELETE_USER + this.username);
s.close();
System.out.print("\rOnline:"+ --online + " ");
}
/*
* 收到聊天信息,解析出发送对象和信息内容,并发送
*/
else {
touser =msg.substring(0, msg.indexOf(NAME_END));
msg =msg.replaceFirst(touser + NAME_END, "");
send(msg,touser);
}
}
}
}
/*
* 登陆时出现用户名已存在情况,通知用户
*/
catch (ExistException e){
out.println(SYSTEM_MSG+ USER_EXIST);
out.flush();
} catch (Exception e) {
} finally {
try {
s.close();
} catch (Exception e){
}
}
}
/**
* 发送信息给所有用户
*/
2、
private void sendAll(Stringmsg) {
Set s = users.keySet();
Iterator it =s.iterator();
while (it.hasNext()) {
UserThread t =(UserThread) users.get(it.next());
if (t != this)
t.sendUser(msg);
}
}
/**
* 给本线程发送在线用户列表
*/
private void listAll() {
Set s = users.keySet();
Iterator it =s.iterator();
while (it.hasNext()) {
this.sendUser(SYSTEM_MSG+ EXIST_USERS + it.next());
}
}
/**
* 判断用户名是否已经有人使用
*/
private booleanisExist(String name) {
Set s = users.keySet();
Iterator it =s.iterator();
while (it.hasNext()) {
if(name.equals((String) it.next())) {
return true;
}
}
return false;
}
/**
* 给本线程对应的用户发信息
*/
private void sendUser(Stringmsg) {
out.println(msg);
out.flush();
//System.out.println("to " + this.username + ": " + msg);// 调试用代码
}
/**
* 给指定对象发送信息
*/
private void send(String msg,String touser) {
/*
* 调用相应函数,给所有人发信息时
*/
if (touser.equals("All")){
sendAll(this.username+ NAME_END + msg);
return;
}
/*
* 根据发送目标的名字获得相应线程,调用目标线程的函数给目标发送信息
*/
if(users.containsKey(touser))// 加判断,防止用户已经离线
((UserThread)users.get(touser)).sendUser(MSG_FROM
+this.username + NAME_END + msg);
}
}
/**
* 主方法:启动服务器
*/
publicstatic void main(String[] args) {
/*
* 根据参数的情况,获得端口号,无效时使用默认值,并返回相应信息
*/
int port = DEFAULT_PORT;
if (args.length > 0) {
int newport;
try {
newport = Integer.parseInt(args[0]);
/*
* 无效端口
*/
if (newport> 65535 || newport< 0) {
System.out.println("Theport " + newport+ " is invalid.");
}
/*
* 操作系统预留端口
*/
else if (newport <= 1024) {
System.out.println("Theport 0~1024 is not allowed.");
} else {
port = newport;
}
}
/*
* 不能转换成整数的参数
*/
catch(NumberFormatException e) {
System.out.println("Invalidport number!");
}
}
try {
ServerSocket ss = newServerSocket(port);
System.out.print("Serveris running.\nPort:" + port + "\nOnline:0");
while(true) {
Socket s = ss.accept();
Thread t = newUserThread(s);
t.start();
}
}
/*
* 端口绑定失败
*/
catch(IOException e) {
System.out.println("Failedto bind " + port + "port.");
}
}
}
3、客户端代码:
//聊天室客户端
public class Client {
//主方法:启动登陆线程
public static void main(String[]args) throws Exception {
Thread login = newLoginThread();
login.start();
}
}
4、客户端登录线程
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import lianxi.Protocol.ExistException;
import lianxi.Protocol.VersionException;
/**
* 登陆线程
*/
public class LoginThread extends Threadimplements Protocol {
privateJFrame loginf;
privateJTextField t;
publicvoid run() {
/*
* 设置登陆界面
*/
loginf= new JFrame();
loginf.setLocation(300,200);
loginf.setSize(400,150);
loginf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
loginf.setTitle(SOFTWARE+ " - 登陆");
t= new JTextField("Version " + VERSION + " By Lindan");
t.setHorizontalAlignment(JTextField.CENTER);
t.setEditable(false);
loginf.getContentPane().add(t,BorderLayout.SOUTH);
JPanelloginp = new JPanel(new GridLayout(4, 2));
loginf.getContentPane().add(loginp);
JTextFieldt1 = new JTextField("登陆名:");
t1.setHorizontalAlignment(JTextField.CENTER);
t1.setEditable(false);
loginp.add(t1);
finalJTextField loginname = new JTextField("");
loginname.setHorizontalAlignment(JTextField.CENTER);
loginp.add(loginname);
JTextFieldt2 = new JTextField("服务器IP:");
t2.setHorizontalAlignment(JTextField.CENTER);
t2.setEditable(false);
loginp.add(t2);
finalJTextField loginIP = new JTextField(DEFAULT_IP);
loginIP.setHorizontalAlignment(JTextField.CENTER);
loginp.add(loginIP);
JTextFieldt3 = new JTextField("端口号:");
t3.setHorizontalAlignment(JTextField.CENTER);
t3.setEditable(false);
loginp.add(t3);
finalJTextField loginport = new JTextField(DEFAULT_PORT + "");
loginport.setHorizontalAlignment(JTextField.CENTER);
loginp.add(loginport);
/*
* 监听退出按钮(匿名内部类)
*/
JButtonb1 = new JButton("退 出");
loginp.add(b1);
b1.addActionListener(newActionListener() {
publicvoid actionPerformed(ActionEvent e) {
System.exit(0);
}
});
finalJButton b2 = new JButton("登 陆");
loginp.add(b2);
loginf.setVisible(true);
/**
* 监听器,监听"登陆"Button的点击和TextField的回车
*/
classButtonListener implements ActionListener {
privateSocket s;
publicvoid actionPerformed(ActionEvent e) {
if(checkName(loginname.getText())) {
try{
s= new Socket(loginIP.getText(), Integer
.parseInt(loginport.getText()));
/*
* 连接服务器
*/
try{
InputStreamis = s.getInputStream();
InputStreamReaderir = new InputStreamReader(is,
"GBK");
//java.nio.charset.Charset.defaultCharset());
BufferedReaderin = new BufferedReader(ir);
OutputStreamos = s.getOutputStream();
OutputStreamWriteror = new OutputStreamWriter(os,
"GBK");
PrintWriterout = new PrintWriter(or);
out.println(VERSION);
out.flush();
out.println(loginname.getText());
out.flush();
Stringver;
if(!(ver = in.readLine()).equals(VERSION)) {
thrownew VersionException(ver);
}
if(in.readLine().equals(USER_EXIST)) {
thrownew ExistException();
}
/*
* 启动聊天线程
*/
Threadchat = new ChatThread(loginname.getText(),
s,in, out);
loginf.setVisible(false);
loginf.setEnabled(false);
chat.start();
}catch (IOException e1) {// 流操作异常
t.setText("通讯失败,请重试!");
try{
s.close();
}catch (IOException e2) {
}
}catch (VersionException e3) {// 用户存在异常(接口中定义)
t.setText(e3.getMessage());
try{
s.close();
}catch (IOException e4) {
}
}catch (ExistException e5) {// 用户存在异常(接口中定义)
t.setText(e5.getMessage());
try{
s.close();
}catch (IOException e6) {
}
}
}catch (IOException e7) {// Socket连接服务器异常
t.setText("连接服务器失败,请重试!");
}
}
}
}
ButtonListenerbl = new ButtonListener();
b2.addActionListener(bl);
loginname.addActionListener(bl);
loginIP.addActionListener(bl);
loginport.addActionListener(bl);
}
/**
* 判断登陆名是否有效
*/
privateboolean checkName(String name) {
if(name.length() < NAME_MIN) {
t.setText("错误:登陆名不能小于" + NAME_MIN + "字符");
returnfalse;
}
if(name.length() > NAME_MAX) {
t.setText("错误:登陆名不能大于" + NAME_MAX + "字符");
returnfalse;
}
if(name.indexOf(" ") > -1) {
t.setText("错误:登陆名不能包含空格");
returnfalse;
}
for(int i = 0; i < FORBID_WORDS.length; i++) {
if(name.indexOf(FORBID_WORDS[i]) > -1) {
t.setText("错误:登陆名不能包含敏感信息");
returnfalse;
}
}
returntrue;
}
}
5、聊天线程
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
* 聊天线程
*/
class ChatThread extends Thread implementsProtocol {
private Map users = new HashMap();
privateString name;
privateSocket s;
privateBufferedReader in;
privatePrintWriter out;
privateJComboBox cb;
privateJFrame f;
privateJTextArea ta;
privateJTextField tf;
privatestatic long time;// 上一条信息的发出时间
privatestatic int total;// 在线人数统计
publicChatThread(String name, Socket s, BufferedReader in, PrintWriter out) {
this.name= name;
this.s= s;
this.in= in;
this.out= out;
}
publicvoid run() {
/*
* 设置聊天室窗口界面
*/
f= new JFrame();
f.setSize(600,400);
f.setTitle(SOFTWARE+ " - " + name + " 当前在线人数:" + ++total);
f.setLocation(300,200);
ta= new JTextArea();
JScrollPanesp = new JScrollPane(ta);
ta.setEditable(false);
tf= new JTextField();
cb= new JComboBox();
cb.addItem("All");
JButtonjb = new JButton("私聊窗口");
JPanelpl = new JPanel(new BorderLayout());
pl.add(cb);
pl.add(jb,BorderLayout.WEST);
JPanelp = new JPanel(new BorderLayout());
p.add(pl,BorderLayout.WEST);
p.add(tf);
f.getContentPane().add(p,BorderLayout.SOUTH);
f.getContentPane().add(sp);
/*
* 监听私聊窗口按钮(匿名内部类)
*/
jb.addActionListener(newActionListener() {
publicvoid actionPerformed(ActionEvent e) {
Stringname = (String) cb.getSelectedItem();
if(!name.equals("All")) {
ChatWindowcw = (ChatWindow) users.get(name);
if(cw.fs.isVisible())
cw.fs.setVisible(false);
else
cw.fs.setVisible(true);
}
}
});
/**
* 监听关闭按钮
*/
classMyWindowListener implements WindowListener {
publicvoid windowActivated(WindowEvent e) {
}
publicvoid windowClosed(WindowEvent e) {
}
publicvoid windowClosing(WindowEvent e) {
/*
* 向服务器发送退出信息
*/
out.println(SYSTEM_MSG+ USER_LOGOUT);
out.flush();
try{
s.close();
}catch (IOException e1) {
}
System.exit(0);
}
publicvoid windowDeactivated(WindowEvent e) {
}
publicvoid windowDeiconified(WindowEvent e) {
}
publicvoid windowIconified(WindowEvent e) {
}
publicvoid windowOpened(WindowEvent e) {
}
}
MyWindowListenerwl = new MyWindowListener();
f.addWindowListener(wl);
/**
* 接收服务器发送的信息
*/
classGetMsgThread extends Thread {
publicvoid run() {
try{
Stringmsg, name;
while(!s.isClosed()) {
/*
* 不断接受服务器信息,外层循环防止读到null跳出循环
*/
while(!s.isClosed() && (msg = in.readLine()) != null) {
msg= specialMsg(msg);// 系统信息处理
if(msg.startsWith(MSG_FROM)) {
msg= msg.replaceFirst(MSG_FROM, "");
name= msg.substring(0, msg.indexOf(NAME_END));
msg= msg.replaceFirst(name + NAME_END, "");
JTextAreatas = ((ChatWindow) users.get(name)).tas;
tas.append(name+ " 说: " +msg + "\n");
tas.setCaretPosition(tas.getText().length());
ta.append(name+ " 悄悄地对你说: "+ msg + "\n");
}else if (msg.contains(NAME_END)) {
name= msg.substring(0, msg.indexOf(NAME_END));
msg= msg.replaceFirst(name + NAME_END, "");
ta.append(name+ " 说: " +msg + "\n");// 在窗口显示信息
}else {
ta.append(msg+ "\n");
}
ta.setCaretPosition(ta.getText().length());//自动滚动到底部
}
}
}catch (Exception e) {
out.println(SYSTEM_MSG+ USER_LOGOUT);// 当异常产生时向系统发出退出信息
}finally {
try{
s.close();
}catch (IOException e) {
}
}
}
}
GetMsgThreadgt = new GetMsgThread();
gt.start();
/*
* 监听用户在公聊窗口输入的信息(匿名内部类)
*/
tf.addActionListener(newActionListener() {
publicvoid actionPerformed(ActionEvent e) {
Stringmsg = e.getActionCommand();
if(isAllowed(msg, null)) {
sendMsg((String)cb.getSelectedItem(), msg, true);
tf.setText(null);
}
}
});
f.setVisible(true);
}
/**
* 处理系统信息
*/
privateString specialMsg(String msg) {
if(msg.startsWith(SYSTEM_MSG)) {
msg= msg.replaceFirst(SYSTEM_MSG, "");
/*
* 当有人进入聊天室
*/
if(msg.startsWith(ADD_USER)) {
msg= msg.replaceFirst(ADD_USER, "");
cb.addItem(msg);
users.put(msg,new ChatWindow(msg));
total++;
msg+= " 进入聊天室";
}
/*
* 当有人离开聊天室
*/
elseif (msg.startsWith(DELETE_USER)) {
msg= msg.replaceFirst(DELETE_USER, "");
cb.removeItem(msg);
((ChatWindow)users.get(msg)).tas.append(msg + " 退出聊天室\n");
users.remove(msg);
total--;
msg+= " 退出聊天室";
}
/*
* 登陆时获得的在线用户列表信息
*/
elseif (msg.startsWith(EXIST_USERS)) {
msg= msg.replaceFirst(EXIST_USERS, "");
cb.addItem(msg);
users.put(msg,new ChatWindow(msg));
total++;
msg+= " 正在聊天室";
}
/*
* 即时显示在线人数
*/
f.setTitle(SOFTWARE+ " - " + name + " 当前在线人数:" + total);
returnmsg;
}
returnmsg;
}
/**
* 检查信息是否允许发送,包括检查敏感词汇/空信息/刷屏
*/
privateboolean isAllowed(String msg, String msgto) {
/*
* 过滤空信息
*/
if(msg.length() == 0)
returnfalse;
Stringerrmsg = null;
/*
* 过滤敏感词汇
*/
for(int i = 0; i < FORBID_WORDS.length; i++) {
if(msg.indexOf(FORBID_WORDS[i]) > -1) {
errmsg= "包含敏感信息,信息发送失败!\n";
break;
}
}
longtimenow = (new Date()).getTime();// 获得当前时间信息
/*
* 防刷屏
*/
if(timenow - time < TIME_BETWEEN_MSG * 1000) {
errmsg= "发送信息的最短间隔为"+ TIME_BETWEEN_MSG + "秒,请勿刷屏!\n";
}
if(errmsg == null) {
time= timenow;// 记录发送信息时间
returntrue;
}else if (msgto == null)
ta.append(errmsg);
else
((ChatWindow)users.get(msgto)).tas.append(errmsg);
returnfalse;
}
/*
* 私聊窗口
*/
privateclass ChatWindow {
JFramefs;
JTextAreatas;
Stringname;
publicChatWindow(String username) {
this.name= username;
fs= new JFrame();
fs.setSize(400,200);
fs.setTitle(SOFTWARE+ " - " + "与" + name + "私聊");
fs.setLocation(300,200);
tas= new JTextArea();
JScrollPanesps = new JScrollPane(tas);
tas.setEditable(false);
finalJTextField tfs = new JTextField();
JPanelps = new JPanel(new BorderLayout());
//JComboBox cbs = new JComboBox();
//cbs.addItem(name);
//ps.add(cbs, BorderLayout.WEST);
ps.add(tfs);
fs.getContentPane().add(ps,BorderLayout.SOUTH);
fs.getContentPane().add(sps);
/*
* 监听用户在私聊窗口输入的信息(匿名内部类)
*/
tfs.addActionListener(newActionListener() {
publicvoid actionPerformed(ActionEvent e) {
if(users.containsKey(name)) {
if(isAllowed(e.getActionCommand(), name)) {
sendMsg(name,e.getActionCommand(), true);
tfs.setText(null);
}
}else {
tas.append("信息发送失败,用户已经离开聊天室.\n");
}
}
});
}
}
privatevoid sendMsg(String name1, String msg, boolean isRoomShow) {
out.println(name1+ NAME_END + msg);
out.flush();
if(name1.equals("All"))
ta.append("我 说: " + msg + "\n");
else{
((ChatWindow)users.get(name1)).tas.append("我说: " + msg + "\n");
if(isRoomShow)
ta.append("我悄悄地对 " + name1 + " 说: " + msg + "\n");
}
}
}
原文地址:http://blog.csdn.net/wulianwang001/article/details/38843939