本案例目的在于开发一个简单的聊天室功能,所有代码都是本人调试修改之后可以正常使用,主要功能在于通过多线程技术由服务器接收客户端的请求,之后将聊天内容发送给每个接入服务器的每个客户端。另外实现了登录功能,只有登录验证之后才可以实现聊天。具体的技术细节在本栏目不涉及,主要是多线程基于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);
}
}
}
}
}
版权声明:本文为博主原创文章,未经博主允许不得转载。