1.编写PC端工程代码:
这是一个Java工程,分为客户端和服务器端。两者的关系如图;
工程目录:
Server.java(这里面有java swing生成界面的代码,和Android-layout有点像)
package com.example.pcchat.server;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class Server extends JFrame implements ActionListener {
//服务器端主线程负责界面以及服务端主线程ServerThread启动
//ServerThread又产生BroadCast和ClientThread线程
private static final long serialVersionUID = 1L;
JPanel jPanel1 = new JPanel();
JButton jButton1 = new JButton("启动服务器");
JButton jButton2 = new JButton("关闭服务器");
JPanel jPanel2 = new JPanel();
JScrollPane jScrollPane = new JScrollPane();
static JTextArea jTextArea = new JTextArea();
boolean bool = false, start = false;
ServerThread serverThread;
Thread thread;
public Server(){
super("Server");
jButton1.addActionListener(this);
jButton2.addActionListener(this);
getContentPane().setLayout(new BorderLayout());
//头部布局
getContentPane().add(jPanel1, BorderLayout.NORTH);
jPanel1.add(jButton1);
jPanel1.add(jButton2);
//头部以下布局
getContentPane().add(jPanel2, BorderLayout.CENTER);
jPanel2.setLayout(new BorderLayout());
jPanel2.add(jScrollPane, BorderLayout.CENTER);
jScrollPane.getViewport().add(jTextArea);
setSize(400, 400);
setVisible(true);
}
public static void main(String[] args) {
Server server = new Server();
server.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//按钮事件处理
public void actionPerformed(ActionEvent e) {
if(e.getSource() == jButton1){//启动服务器
serverThread = new ServerThread();
serverThread.start();
} else if (e.getSource() == jButton2) {//关闭服务器
bool = false;
start = false;
serverThread.finalize();
setVisible(false);
}
}
}
ServerThread.java
package com.example.pcchat.server;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
public class ServerThread extends Thread {
ServerSocket serverSocket;
// ClientThread维持服务器与单个客户端的连接
// 负责接收客户端发来的信息
Vector<ClientThread> clients;
// 用于接收客户端发来的信息
Vector<Object> messages;
// BroadCast类负责服务端向客户端广播消息
BroadCast broadcast;
String ip;
InetAddress myInetAddress = null;
public ServerThread() {
clients = new Vector<ClientThread>();
messages = new Vector<Object>();
try {
serverSocket = new ServerSocket(8521);
myInetAddress = InetAddress.getLocalHost();
ip = myInetAddress.getHostAddress();
} catch (IOException e) {
e.printStackTrace();
}
Server.jTextArea.append("服务器地址:" + ip + "端口号:"
+ String.valueOf(serverSocket.getLocalPort()) + "\n");
broadcast = new BroadCast(this);
broadcast.start();
}
//一旦监听到有new Socket(ip, port)就创建ClientThread来维持服务器与客户端的连接
@Override
public void run() {
while (true) {
try {
Socket socket = serverSocket.accept();
System.out.println(socket.getLocalAddress().getHostAddress());
//启动ClientThread可以监听对应的客户端是否发来信息
ClientThread clientThread = new ClientThread(socket, this);
clientThread.start();
if(socket != null){
synchronized (this) {
clients.add(clientThread);
}
}
} catch (IOException e) {
System.exit(2);
}
}
}
public void finalize() {
try {
if(serverSocket != null){
serverSocket.close();
}
} catch (IOException e1) {
serverSocket = null;
}
}
}
ClientThread.java
package com.example.pcchat.server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class ClientThread extends Thread {
// 服务器和单个客户端的连接
//接收message并保存
Socket clientSocket;
DataInputStream in;
DataOutputStream out;
ServerThread serverThread;
String str;
static int ConnectedNumber = 0;
public ClientThread(Socket socket, ServerThread serverThread) {
clientSocket = socket;
this.serverThread = serverThread;
try {
in = new DataInputStream(clientSocket.getInputStream());
out = new DataOutputStream(clientSocket.getOutputStream());
} catch (IOException e) {
System.out.println("建立IO通道失败!");
System.exit(3);
}
}
@Override
public void run() {
while (true) {
try {
// 读取客户端发来的信息
String message = in.readUTF();
synchronized (serverThread.messages) {
if (message != null) {
// 将客户端发来的数据存在serverThread的messages数组中
serverThread.messages.add(message);
// 在服务器端的文本框中显示新消息
Server.jTextArea.append(message + "\n");
if(message.startsWith("#")){//从Client接收到下线消息
serverThread.clients.remove(this);
}
}
}
} catch (IOException e) {
break;
}
}
}
}
BroadCast.java
package com.example.pcchat.server;
import java.io.IOException;
public class BroadCast extends Thread {
ClientThread clientThread;
ServerThread serverThread;
String str;
public BroadCast(ServerThread serverThread) {
this.serverThread = serverThread;
}
// 不停地向所有客户端发送消息
@Override
public void run() {
while (true) {
// 同步化serverThread.messages
synchronized (serverThread.messages) {
if (!serverThread.messages.isEmpty()) {
str = (String) serverThread.messages.firstElement();
// 利用循环获取所有连接
for (int i = 0; i < serverThread.clients.size(); i++) {
clientThread = (ClientThread) serverThread.clients
.elementAt(i);
try {
// 向每个客户端发送信息
clientThread.out.writeUTF(str);
} catch (IOException e) {
e.printStackTrace();
}
}
// 删除已经发送的数据
serverThread.messages.removeElement(str);
}
}
}
}
}
客户端Client.java
package com.example.pcchat.client;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.sql.Date;
import java.text.SimpleDateFormat;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class Client extends JFrame implements ActionListener, Runnable {
private static final long serialVersionUID = 1L;
Socket socket;
Thread thread;
DataInputStream in;
DataOutputStream out;
// 是否登陆的标记
boolean flag = false;
boolean thread_is_over;
String name, chat_txt, chat_in;
// name---nameField, chat_txt---, chat_in---服务器发来的信息
String ip = null;// -------ipTextField
// 头部布局
JPanel jPanel1 = new JPanel();
JLabel jLabel1 = new JLabel("用户名:");
JTextField nameField = new JTextField(5);
JButton loginJButton = new JButton("进入聊天室");
JButton exitJButton = new JButton("退出聊天室");
// 中部布局
JPanel jPanel2 = new JPanel();
// ------------------左
JScrollPane jScrollPane = new JScrollPane();
JTextArea jTextArea = new JTextArea();
// ------------------右
JPanel jPanel3 = new JPanel();
JLabel jLabel2 = new JLabel("服务器地址:");
JTextField ipTextField = new JTextField(5);
// 底部
JPanel jPanel4 = new JPanel();
JTextField sendTextField = new JTextField(20);
JButton sendBtn = new JButton("发送消息");
public Client() {
thread_is_over = false;
getContentPane().setLayout(new BorderLayout());
loginJButton.addActionListener(this);
exitJButton.addActionListener(this);
sendBtn.addActionListener(this);
// 头部布局
getContentPane().add(jPanel1, BorderLayout.NORTH);
jPanel1.add(jLabel1);
jPanel1.add(nameField);
jPanel1.add(loginJButton);
jPanel1.add(exitJButton);
// 中部布局
getContentPane().add(jPanel2, BorderLayout.CENTER);
jPanel2.setLayout(new BorderLayout());
jPanel2.add(jScrollPane, BorderLayout.CENTER);
jScrollPane.getViewport().add(jTextArea);
jPanel2.add(jPanel3, BorderLayout.EAST);
jPanel3.setLayout(new GridLayout(10, 1));// 10行1列
jPanel3.add(jLabel2);
jPanel3.add(ipTextField);
// 底部布局
getContentPane().add(jPanel4, BorderLayout.SOUTH);
jPanel4.add(sendTextField);
jPanel4.add(sendBtn);
setSize(400, 400);
setVisible(true);
}
public static void main(String[] args) {
Client client = new Client();
client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void run() {
while (!thread_is_over) {
try {
// 获取服务器发来的信息
chat_in = in.readUTF();
jTextArea.append(chat_in + "\n");
} catch (IOException e) {
//e.printStackTrace();
System.out.println("与服务器断开连接!");
}
}
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == loginJButton) {
// 进入聊天室
// 获取用户名和ip地址
name = nameField.getText().toString();
ip = ipTextField.getText().toString();
if (!name.equals("") && !ip.equals("")) {
try {
socket = new Socket(ip, 8521);
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
Date now = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
String nowString = sdf.format(now);
out.writeUTF("$$" + name + " " + nowString + "上线了!");
} catch (Exception e1) {
//登录失败对话框
Toolkit.getDefaultToolkit().beep();
JOptionPane.showMessageDialog(null,
"login fail! ip address is wrong!",
"ERROR_MESSAGE",
JOptionPane.ERROR_MESSAGE);
return;
}
// 开启线程监听server是否有消息
thread = new Thread(this);
thread.start();
flag = true;
}
} else if (e.getSource() == sendBtn) {
// 发送按钮
chat_txt = sendTextField.getText().toString().trim();
if ( !chat_txt.equals("")) {
Date now = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
String nowString = sdf.format(now);
try {
out.writeUTF("^_^" + name + " " + nowString + "说:"
+ chat_txt);
} catch (IOException e1) {
System.out.println("服务器断开,无法发送数据!");
}
}
} else if (e.getSource() == exitJButton) {
// 退出聊天室
if (flag == true) {
try {
thread_is_over = true;
out.writeUTF("##" + name + "下线了");
out.close();
in.close();
if(socket != null){
socket.close();
socket = null;
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
flag = false;
setVisible(false);
}
}
}
当然,客户端可以用android实现(代码相似)
MainActivity.java
package com.example.chatandroidclient;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Locale;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity implements Runnable {
private EditText userName, userIP, message, history;
private Button loginBtn, sendBtn, leaveBtn;
private String nameString, IPString, messageString, historyString;
private Socket socket;
private Thread thread;
private DataInputStream inputStream;
private DataOutputStream outputStream;
// 是否登录的标记
private boolean flag = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
userName = (EditText) findViewById(R.id.userName);
userIP = (EditText) findViewById(R.id.userIP);
message = (EditText) findViewById(R.id.message);
history = (EditText) findViewById(R.id.history);
loginBtn = (Button) findViewById(R.id.loginBtn);
sendBtn = (Button) findViewById(R.id.sendBtn);
leaveBtn = (Button) findViewById(R.id.leaveBtn);
loginBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (flag == true) {
Toast.makeText(MainActivity.this, "已登录!",
Toast.LENGTH_SHORT).show();
return;
}
nameString = userName.getText().toString();
IPString = userIP.getText().toString();
if (!nameString.equals("") && !IPString.equals("")) {
new Thread(new Runnable() {
public void run() {
try {
socket = new Socket(IPString, 8521);
inputStream = new DataInputStream(socket
.getInputStream());
outputStream = new DataOutputStream(socket
.getOutputStream());
Date now = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss",
Locale.CHINA);
String nowString = sdf.format(now);
outputStream.writeUTF("$$" + nameString + " "
+ nowString + "上线了!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("can't connect");
}
thread = new Thread(MainActivity.this);
thread.start();
flag = true;
}
}).start();
}
}
});
sendBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (flag == false) {
Toast.makeText(MainActivity.this, "未登录!",
Toast.LENGTH_SHORT).show();
return;
}
messageString = message.getText().toString().trim();
if (!messageString.equals("")) {
Date now = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss",
Locale.CHINA);
String nowString = sdf.format(now);
try {
outputStream.writeUTF("^_^" + nameString + " "
+ nowString + "说:" + messageString);
} catch (IOException e1) {
System.out.println("服务器断开,无法发送数据!");
}
}
}
});
leaveBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (flag == false) {
Toast.makeText(MainActivity.this, "未登录!",
Toast.LENGTH_SHORT).show();
return;
}
try {
outputStream.writeUTF("##" + nameString + "下线了");
outputStream.close();
inputStream.close();
if (socket != null) {
socket.close();
socket = null;
}
} catch (IOException e1) {
e1.printStackTrace();
}
flag = false;
historyString = "";
history.setText(historyString);
}
});
}
@Override
public void run() {
while (true) {
try {
// 获取服务器发来的信息
messageString = inputStream.readUTF();
messageString = messageString + "\n";
myHandler.sendEmptyMessage(0);
} catch (IOException e) {
//System.out.println("与服务器断开连接!");
}
}
}
private Handler myHandler = new Handler(){
public void handleMessage(Message msg) {
if(msg.what == 0){
historyString += messageString;
history.setText(historyString);
}
};
};
}
main.xml文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.chatandroidclient.MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="用户名" />
<EditText
android:id="@+id/userName"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:background="@android:drawable/edit_text" />
<Button
android:id="@+id/loginBtn"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="登录" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="IP地址" />
<EditText
android:id="@+id/userIP"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:background="@android:drawable/edit_text"
android:text="10.107.56.64" />
<Button
android:id="@+id/leaveBtn"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="离开" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<EditText
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/edit_text" />
<Button
android:id="@+id/sendBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送" />
<EditText
android:id="@+id/history"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:drawable/edit_text"
android:editable="false"
android:inputType="textMultiLine"
android:singleLine="false" />
</LinearLayout>
</LinearLayout>
测试结果:
《Android 4.0网络编程详解》书上的不标准,我把它优化了。有什么问题请大神们指出