最新在研究网络编程,感觉好好玩,然后玩得兴起就想做个聊天室来玩玩。这个聊天室叫 ChatRoom ,是一个基于 TCP 通信的聊天室,并且用到了一些 Material Design 的控件,之前一直都在赶项目都没时间学习 Material Design(其实不是没时间,是有些事情很烦,所以耽搁了),工作室的昭哥老说我,你基础工不扎实,嘿嘿。
TCP介绍
TCP 是一种可靠、必须连接才能通信的协议。
因为需要连接才能通信,所以 TCP 通信严格区分客户端和服务器端,只有客户端连接了服务器端才能实现通信,服务器端不能连接客户端并且需要事先启动,等待客户端的请求。
使用重发机制以保证数据传输的可靠性。(发送后,需要收到确认的信息,否则进行重发)
ChatRoom
好了,扯了辣么多,我们来看看 ChatRoom 是怎么实现的吧!先看看效果:
服务器端
/**
* Created by kn on 2016/5/24.
*
* 聊天室主线程服务
*/
public class MyServerSocket {
public static void main(String args[]){
new ServerListener().start();
}
}
/**
* Created by kn on 2016/5/24.
*
* 聊天监听线程类
*/
public class ServerListener extends Thread {
@Override
public void run() {
try {
//端口号port:1~65535
ServerSocket serverSocket = new ServerSocket(30000);
while(true){
//该方法会阻塞当前线程
Socket socket = serverSocket.accept();
//建立连接
System.out.println("有客户端链接到本地的30000端口");
//将socket传递给新的线程
ChatSocket chatSocket = new ChatSocket(socket);
chatSocket.start();
ChatManager.getInstance().add(chatSocket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Created by kn on 2016/5/24.
*
* 聊天线程类
*/
public class ChatSocket extends Thread {
Socket socket;
public ChatSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8"));
String line = null;
while ((line = br.readLine()) != null) {
ChatManager.getInstance().publish(this, line);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 输出服务器返回的信息
* @param message
*/
public void showMessage(String message) {
try {
socket.getOutputStream().write((message + "\n").getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 判断socket是否关闭
* @return
*/
public boolean isSocketClosed(){
return socket.isClosed();
}
}
/**
* Created by kn on 2016/5/24.
*
* 聊天室线程管理类
*/
public class ChatManager {
private static ChatManager instance = new ChatManager();
//聊天线程列表
Vector<ChatSocket> vector = new Vector<>();
private ChatManager() {
}
/**
* 单例模式
* @return
*/
public static ChatManager getInstance() {
if (instance == null) {
synchronized (ChatManager.class) {
if (instance == null) {
instance = new ChatManager();
}
}
}
return instance;
}
/**
* 添加聊天线程
*
* @param chatSocket
*/
public void add(ChatSocket chatSocket) {
vector.add(chatSocket);
}
/**
* 向聊天室的其他线程发布消息
* @param chatSocket
* @param message
*/
public void publish(ChatSocket chatSocket, String message) {
//遍历线程列表
for (int i = 0; i < vector.size(); i++) {
ChatSocket mChatSocket = vector.get(i);
//判断是否是己线程
if (!chatSocket.equals(mChatSocket)) {
//判断该线程是否已经断开服务器的连接
if (mChatSocket.isSocketClosed()) {
vector.remove(i);
} else {
mChatSocket.showMessage(message);
}
}
}
}
}
写完以上代码,服务器端基本上完成了,其实现在,我们就可以玩聊天室了。打开多个 cmd 并输入 telnet localhost 30000 就可以连接服务器,然后你就可以在黑框下聊天了。
客户端
主界面,输入服务器的 IP 和聊天中显示的名字
进入界面
public class MainActivity extends AppCompatActivity {
MaterialEditText metName;//聊天的昵称
MaterialEditText metIP;//聊天室的IP
ImageView ivAvatar;//聊天的头像
ButtonFlat btnEnter;//进入聊天按钮
ProgressBar progressBar;//进度条
FrameLayout flProgressBar;
public static Socket socket = null;
final static int SUCCESS = 0x01;//进入聊天室成功
final static int FAILURE = 0x02;//进入聊天室失败
final static int TIMEOUT = 0x03;//连接超时
final static int UN_KNOWN_HOST = 0x04;//未知主机
private BufferedWriter writer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
metName = (MaterialEditText) findViewById(R.id.met_name);
metIP = (MaterialEditText) findViewById(R.id.met_ip);
ivAvatar = (ImageView) findViewById(R.id.iv_avatar);
btnEnter = (ButtonFlat) findViewById(R.id.btn_enter);
progressBar = (ProgressBar) findViewById(R.id.progress);
flProgressBar = (FrameLayout) findViewById(R.id.fl_progress);
metName.setText("kntryer");
metIP.setText("192.168.1.101");
btnEnter.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
enterChatRoom();
}
});
}
private void enterChatRoom() {
//获取ip
final String ipString = metIP.getText().toString().trim();
new Thread(new Runnable() {
@Override
public void run() {
try {
socket = new Socket();
socket.connect(new InetSocketAddress(ipString, 30000), 5000);
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
handler.sendEmptyMessage(SUCCESS);
} catch (SocketTimeoutException e) {
//连接超时 在UI界面显示消息
handler.sendEmptyMessage(TIMEOUT);
e.printStackTrace();
} catch (UnknownHostException e) {
handler.sendEmptyMessage(UN_KNOWN_HOST);
e.printStackTrace();
} catch (IOException e) {
handler.sendEmptyMessage(FAILURE);
e.printStackTrace();
}
}
}).start();
}
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case SUCCESS:
String name = metName.getText().toString();
try {
if (writer != null) {
writer.write("system," + name + " enter ChatRoom\n");
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
Intent intent = new Intent(MainActivity.this, ChatActivity.class);
intent.putExtra("name", name);
startActivity(intent);
break;
case FAILURE:
showIsOpenWifi("Please check whether the network is open or not.");
break;
case TIMEOUT:
showIsOpenWifi("SocketTimeoutException");
break;
case UN_KNOWN_HOST:
showIsOpenWifi("UnknownHostException");
break;
}
return false;
}
});
private void showIsOpenWifi(String message) {
new SnackBar(this, message, "Yes", new View.OnClickListener() {
@Override
public void onClick(View v) {
//跳到WiFi,这个还没做
}
}).show();
}
}
聊天界面
/**
* Created by kn on 2016/5/24.
*/
public class ChatActivity extends AppCompatActivity {
ButtonFlat btnSent;//发送消息
MaterialEditText metMsg;//消息
ListView lvMsg;//显示消息
ArrayList<ChatMessage> msgList = new ArrayList<>();//消息列表
MessageAdapter adapter;
Socket socket;
String name;//聊天昵称
BufferedReader reader;//读取服务器返回的数据
BufferedWriter writer;//向服务器发送数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
initView();
}
private void initView() {
name = getIntent().getStringExtra("name");
socket = MainActivity.socket;
btnSent = (ButtonFlat) findViewById(R.id.btn_sent);
metMsg = (MaterialEditText) findViewById(R.id.met_msg);
lvMsg = (ListView) findViewById(R.id.lv_msg);
adapter = new MessageAdapter(this,msgList);
lvMsg.setAdapter(adapter);
//开启一个新的线程监听聊天室,并获取聊天室的消息
getMessage();
btnSent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ChatMessage chatMessage = new ChatMessage();
chatMessage.setType(2);
chatMessage.setName(name);
chatMessage.setMessage(metMsg.getText().toString());
msgList.add(chatMessage);
adapter.notifyDataSetChanged();
String message = name + "," + metMsg.getText().toString();
metMsg.setText("");
setMessage(message);
}
});
}
/**
* 发送消息
* @param message
*/
private void setMessage(String message) {
try {
if (writer != null) {
writer.write(message + "\n");
writer.flush();
} else {
Toast.makeText(ChatActivity.this, "你已经离开 YY 聊天室!", Toast.LENGTH_LONG).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取聊天室的消息
*/
private void getMessage() {
new Thread(new Runnable() {
@Override
public void run() {
try {
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
ChatMessage chatMessage = new ChatMessage();
String[] temp = line.split(",");
System.out.print(temp[0]);
if(temp[0].equals("system")){//如果temp[0]=system说明是系统消息
chatMessage.setType(0);
chatMessage.setMessage(temp[1]);
}else{
chatMessage.setType(1);
chatMessage.setName(temp[0]);
chatMessage.setMessage(temp[1]);
}
msgList.add(chatMessage);
handler.sendEmptyMessage(0x00);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
adapter.notifyDataSetChanged();
return false;
}
});
}
注意
查看服务器 IP
打开 cmd ,输入 ipconfig ,就可以看到本机的 IP 设置,在无线局域网适配 wifi 这里的 IPv4地址 就是服务器的 IP 了。
手机连不上服务器
模拟器可以连上服务器,手机却连不上怎么办?网上说是手机是外网,访问不了内网的问题,手机和电脑连同一个 wifi 就行了。