??最新在研究網(wǎng)絡(luò)編程乡小,感覺好好玩佩微,然后玩得興起就想做個聊天室來玩玩九串。這個聊天室叫 ChatRoom 生棍,是一個基于 TCP 通信的聊天室,并且用到了一些 Material Design 的控件蛤虐,之前一直都在趕項目都沒時間學(xué)習(xí) Material Design(其實(shí)不是沒時間党饮,是有些事情很煩,所以耽擱了)驳庭,工作室的昭哥老說我刑顺,你基礎(chǔ)工不扎實(shí)氯窍,嘿嘿。
TCP介紹
TCP 是一種可靠蹲堂、必須連接才能通信的協(xié)議狼讨。
因為需要連接才能通信,所以 TCP 通信嚴(yán)格區(qū)分客戶端和服務(wù)器端柒竞,只有客戶端連接了服務(wù)器端才能實(shí)現(xiàn)通信政供,服務(wù)器端不能連接客戶端并且需要事先啟動,等待客戶端的請求能犯。
使用重發(fā)機(jī)制以保證數(shù)據(jù)傳輸?shù)目煽啃增昶#òl(fā)送后,需要收到確認(rèn)的信息踩晶,否則進(jìn)行重發(fā))

ChatRoom
好了执泰,扯了辣么多,我們來看看 ChatRoom 是怎么實(shí)現(xiàn)的吧渡蜻!先看看效果:

服務(wù)器端
/**
* Created by kn on 2016/5/24.
*
* 聊天室主線程服務(wù)
*/
public class MyServerSocket {
public static void main(String args[]){
new ServerListener().start();
}
}
/**
* Created by kn on 2016/5/24.
*
* 聊天監(jiān)聽線程類
*/
public class ServerListener extends Thread {
@Override
public void run() {
try {
//端口號port:1~65535
ServerSocket serverSocket = new ServerSocket(30000);
while(true){
//該方法會阻塞當(dāng)前線程
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();
}
}
/**
* 輸出服務(wù)器返回的信息
* @param message
*/
public void showMessage(String message) {
try {
socket.getOutputStream().write((message + "\n").getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 判斷socket是否關(guān)閉
* @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);
}
/**
* 向聊天室的其他線程發(fā)布消息
* @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)) {
//判斷該線程是否已經(jīng)斷開服務(wù)器的連接
if (mChatSocket.isSocketClosed()) {
vector.remove(i);
} else {
mChatSocket.showMessage(message);
}
}
}
}
}
寫完以上代碼术吝,服務(wù)器端基本上完成了,其實(shí)現(xiàn)在茸苇,我們就可以玩聊天室了排苍。打開多個 cmd 并輸入 telnet localhost 30000 就可以連接服務(wù)器,然后你就可以在黑框下聊天了学密。
客戶端
主界面淘衙,輸入服務(wù)器的 IP 和聊天中顯示的名字
進(jìn)入界面
public class MainActivity extends AppCompatActivity {
MaterialEditText metName;//聊天的昵稱
MaterialEditText metIP;//聊天室的IP
ImageView ivAvatar;//聊天的頭像
ButtonFlat btnEnter;//進(jìn)入聊天按鈕
ProgressBar progressBar;//進(jìn)度條
FrameLayout flProgressBar;
public static Socket socket = null;
final static int SUCCESS = 0x01;//進(jìn)入聊天室成功
final static int FAILURE = 0x02;//進(jìn)入聊天室失敗
final static int TIMEOUT = 0x03;//連接超時
final static int UN_KNOWN_HOST = 0x04;//未知主機(jī)
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;//發(fā)送消息
MaterialEditText metMsg;//消息
ListView lvMsg;//顯示消息
ArrayList<ChatMessage> msgList = new ArrayList<>();//消息列表
MessageAdapter adapter;
Socket socket;
String name;//聊天昵稱
BufferedReader reader;//讀取服務(wù)器返回的數(shù)據(jù)
BufferedWriter writer;//向服務(wù)器發(fā)送數(shù)據(jù)
@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);
//開啟一個新的線程監(jiān)聽聊天室腻暮,并獲取聊天室的消息
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);
}
});
}
/**
* 發(fā)送消息
* @param message
*/
private void setMessage(String message) {
try {
if (writer != null) {
writer.write(message + "\n");
writer.flush();
} else {
Toast.makeText(ChatActivity.this, "你已經(jīng)離開 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說明是系統(tǒng)消息
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;
}
});
}
注意
查看服務(wù)器 IP
??打開 cmd ,輸入 ipconfig 哭靖,就可以看到本機(jī)的 IP 設(shè)置具垫,在無線局域網(wǎng)適配 wifi 這里的 IPv4地址 就是服務(wù)器的 IP 了。
手機(jī)連不上服務(wù)器
??模擬器可以連上服務(wù)器试幽,手機(jī)卻連不上怎么辦筝蚕?網(wǎng)上說是手機(jī)是外網(wǎng),訪問不了內(nèi)網(wǎng)的問題铺坞,手機(jī)和電腦連同一個 wifi 就行了起宽。