本案例目的在于开发一个简单的聊天室功能,所有代码都是本人调试修改之后可以正常使用,主要功能在于通过多线程技术由服务器接收客户端的请求,之后将聊天内容发送给每个接入服务器的每个客户端。另外实现了登录功能,只有登录验证之后才可以实现聊天。具体的技术细节在本栏目不涉及,主要是多线程基于Socket,具体代码如下:
首先是简易的聊天模型图:
客户端代码如下:
功能为指定socket连接的ip地址和端口号,客户端分为2个线程A和B,其中A线程负责登录连接,B线程分为2个子线程,第一个是向服务器发送数据,第2个为从服务器接收数据
public class MainActivity extends Activity {
/**
* IP地址及端口号配置
*/
private final static String IP_ADDRESS = "172.21.212.158";
private final static int PORT = 12345;
/**
* 控件变量
*/
private Button btn_sent,btn_loadon;
private EditText editText,edit_username,edit_password;
private TextView tv_content;
private boolean isConn = false;
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
if (msg.obj.toString().contains("登录成功")) {
isConn = true;
}
showToast(msg.what,msg.obj);
};
};
private ClientThread clientThread;
private Socket socket ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 初始化控件
*/
btn_sent = (Button) findViewById(R.id.btn_sent);
editText = (EditText) findViewById(R.id.edit_text);
tv_content = (TextView) findViewById(R.id.tv_content);
btn_loadon = (Button) findViewById(R.id.btn_loadon);
edit_password = (EditText) findViewById(R.id.edit_password_input);
edit_username = (EditText) findViewById(R.id.edit_username_input);
btn_loadon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取登录名和密码
String user = edit_username.getText().toString().trim();
String pass = edit_password.getText().toString().trim();
//启动登录线程
new Thread(new ConnServer(user, pass)).start();
}
});
if (socket == null) {
Toast.makeText(getApplicationContext(), "Null Socket", Toast.LENGTH_SHORT).show();
}
//响应发送按钮
btn_sent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
//将要发送的内容包装给成消息,因为后面要发送给服务器
new NewClientTask(socket).execute(editText.getText().toString().trim()+"\n");
editText.setText("");
}
});
}
public void showToast(int flag,Object msgobj)
{
switch (flag) {
case 0:
Toast.makeText(this, msgobj.toString(), Toast.LENGTH_SHORT).show();
break;
case 1:
Toast.makeText(this, msgobj.toString(), Toast.LENGTH_SHORT).show();
default:
break;
}
}
/**
* 连接服务器的线程
*/
private class ConnServer implements Runnable{
private String username = null;
private String password = null;
private int waitTime = 0;
private boolean hasSendConnMessage = false;
public ConnServer(String user,String pass){
this.username = user;
this.password = pass;
}
//创建构造函数
@Override
public void run() {
// TODO Auto-generated method stub
try {
//向服务器传递数据
socket = new Socket(IP_ADDRESS, PORT);
OutputStream os = socket.getOutputStream();
byte[] buffer = new byte[512];
String str = this.username+"#"+this.password;
buffer = str.getBytes();
os.write(buffer);
hasSendConnMessage = true;
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
InputStream is = null;
if (hasSendConnMessage) {
try {
is = socket.getInputStream();
while (is == null) {
waitTime += 1000;
}
byte[] buffer2 = new byte[512];
is.read(buffer2);
String reuslt = new String(buffer2,"utf-8");
//建立一个消息
Message msg = new Message();
msg.what = 1;
msg.obj = reuslt;
handler.sendMessage(msg);
buffer2 = null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (waitTime > 5000) {
Message msg = new Message();
msg.what = 0;
msg.obj = "登录超时";
handler.sendMessage(msg);
}
}
}
/**
* 客户端线程类,实现了Runnble接口
* @author sjm
*
*/
private class NewClientTask extends AsyncTask<String, Void, String>
{
private Socket clientScoket ;
public NewClientTask(Socket s){
this.clientScoket = s;
}
@Override
protected void onPostExecute(String result) {
// TODO Auto-generated method stub
tv_content.append(result+"\n");
}
@Override
protected String doInBackground(String... params) {
// TODO Auto-generated method stub
//接收自服务器的数据
String result = null;
StringBuilder sb = new StringBuilder();
try {
//发送给服务器
OutputStream os = clientScoket.getOutputStream();
os.write(params[0].getBytes("utf-8"));
InputStream is = clientScoket.getInputStream();
byte[] buffer = new byte[512];
is.read(buffer);
result = new String(buffer, "utf-8");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
try {
clientScoket.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
}
}
接下来是服务器的功能:主要是用于验证登录,以及分发收到的聊天消息,需要注意的是用户名与密码发送给服务器的格式需要自定义,这里我用的是USER#PASS:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.ObjectInputStream.GetField;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.logging.Handler;
/**
* 我的服务器:用来接收客户端的消息,并且把消息再发送给每个客户端
* @author sjm
*
*/
public class MyServer {
//建立socketArray对象存储各个socket
public static ArrayList<Socket> socketArray = new ArrayList<Socket>();
private final static int PORT = 12345;
private static String USERNAME = "SJM";
private static String PASSWORDS = "1234";
private static Socket clientsocket = null;
private static boolean hasClient = false;
public static void main(String[] args) {
// TODO Auto-generated method stub
//1、启动服务端
try {
ServerSocket server = new ServerSocket(PORT);
while(true)
{
System.out.println("等待一个连接:");
clientsocket = server.accept();
System.out.println("接收到一个连接请求");
//将该客户端socket放置到socketArray当中
//设置登录权限,当用户名与密码均满足条件时才开辟线程
//实现方法是开辟一个验证登录的线程,满足则继续
new Thread(new clientPermission(clientsocket)).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 验证登录的线程
* @author sjm
*
*/
private static class clientPermission implements Runnable{
private Socket socket;
private String userName = null;//用户名
private String passWords = null;//密码
public clientPermission(Socket s) {
this.socket = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
InputStream is = this.socket.getInputStream();
byte[] buffer = new byte[512];
is.read(buffer);
String br = new String(buffer);
System.out.println(br);
//用户名和密码的数据格式是在传入过来的时候自定义的,如username#passwords
if (br != null) {
String str = br.toString();
String[] str2 = str.split("#");
userName = str2[0];
passWords = str2[1];
System.out.println(str);
hasClient = userName.contains(USERNAME)&&passWords.contains(PASSWORDS)?true:false;
System.out.println(String.valueOf(hasClient));
if (hasClient) {
//若验证正确
socketArray.add(socket);
//返回一个消息
OutputStream os = socket.getOutputStream();
os.write(new String("登录成功").getBytes("utf-8"));
buffer = null;
//为该客户端开辟一个线程
new Thread(new serverThread(socket)).start();
}
else{
OutputStream os = socket.getOutputStream();
os.write(new String("密码错误").getBytes("utf-8"));
is.close();
socket.close();
socketArray.remove(socket);
return ;
}
}
else
{
OutputStream os = socket.getOutputStream();
os.write(new String("密码不能为空").getBytes("utf-8"));
is.close();
socket.close();
socketArray.remove(socket);
return;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private static class serverThread implements Runnable{
private Socket client_Socket;
/**
* 线程构造函数
* @param s
*/
public serverThread(Socket s){
this.client_Socket = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
System.out.println("启动啦");
while(true)
{
String str = null;
byte[] buffer = new byte[512];
InputStream is = client_Socket.getInputStream();
if (is != null) {
is.read(buffer);
//str = new String(buffer, "utf-8");
//System.out.println("接收到消息"+str);
for(Socket s : socketArray)
{
OutputStream os = s.getOutputStream();
os.write(buffer);
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
if (client_Socket != null) {
socketArray.remove(client_Socket);
}
}
}
}
}