【Java】基於TCP協議多執行緒伺服器-客戶端互動控制檯聊天室簡例
前兩天想到一個手機APP專案,使用到藍芽,發現BluetoothSocket和J2EE網路變成的Socket差不多,使用之餘順手寫一個多執行緒伺服器與客戶端互動實現聊天室的一個小例子,方便新人學習網路程式設計模組,期間使用到多執行緒和IO輸入輸出流的操作,有點兒不明白的過後我會有一些個人使用心得總結,敬請期待哈!
原始碼內容十分簡單,我工程檔案我存在下面的地址上去了,方便大家下載,0積分,為了方便大家學習了。
Server.Java檔案,主要是服務端管理,通過多執行緒接收各使用者傳送訊息以及訊息轉送等處理。
package cn.com.dnyy.tcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Server {
// 用以存放客戶端Socket的管理Map集合
private Map<String, ClientManager> ClientManagers = null;
public static void main(String[] args) {
new Server().startServer();// 啟動服務端
}
// 開啟伺服器方法
private void startServer(){
ServerSocket ss = null;// 伺服器Socket
Socket s = null ;// 與客戶端互動的Socket
try {
ss = new ServerSocket(8888);// 建立伺服器,埠為8888
ClientManagers = new HashMap<String, Server.ClientManager>();// 建立管理集合
System.out.println("建立伺服器成功!");
while(true){
s = ss.accept();// 接收客戶端請求Socket管道
ClientManager cm = new ClientManager(s);// 新建執行緒Runnable
new Thread(cm).start();// 構建新執行緒管理該客戶端Socket
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(ss != null) ss.close();// 關閉伺服器
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 管理客戶端Socket執行緒內部類
private class ClientManager implements Runnable {
private Socket clientSocket;// 客戶端Socket
private BufferedReader br;// 基於客戶端Socket的輸入流(從客戶端輸入服務端)
private PrintWriter pw;// 基於客戶端Socket的輸出流(服務端輸出到客戶端)
private boolean flag;// 執行緒可用標識
private String physicalAddress;// 客戶端名
private String userName;// 使用者名稱
// 構造方法
public ClientManager(Socket s) throws IOException{
this.clientSocket = s;// 獲取客戶端Socket
br = new BufferedReader(new InputStreamReader(s.getInputStream()));// 基於客戶端Socket建立輸入流
pw = new PrintWriter(s.getOutputStream(), true);// 基於客戶端Socket建立輸出流
physicalAddress = s.getInetAddress().getHostAddress()+":"+s.getPort();// 獲取客戶端Socket實體地址:埠
userName = br.readLine();// 獲取使用者名稱
flag = true;// 標識可用
System.out.println(userName+"["+physicalAddress+"]"+"已經成功連線到伺服器!");// 客戶端連線伺服器成功訊息傳送給所有線上使用者
sendMessageForAll("已經成功連線到伺服器!");// 客戶端連線伺服器成功訊息傳送給所有線上使用者
ClientManagers.put(userName, this);// 將自身加入全域性客戶端執行緒管理中
sendOnlineList(false);// 傳送當前所有線上使用者的列表
}
/*
* 傳送當前所有線上使用者的列表
* 引數IsOnly-->true:傳送給當前使用者;false:傳送給所有線上使用者;
*/
private void sendOnlineList(boolean IsOnly) {
if(IsOnly){
StringBuilder sb = new StringBuilder(KeyWords.ONLINE_CLIENTS_LIST + "所有人");// 線上使用者列表字首
for(Map.Entry<String, ClientManager> item : ClientManagers.entrySet()){// 遍歷線上使用者Socket集合
if(userName.equals(item.getKey())) continue;// 不加入自身
sb.append("," + item.getKey());// 新增其它線上使用者
}
pw.println(sb);// 傳送列表給當前使用者
}else{
for(Map.Entry<String, ClientManager> item : ClientManagers.entrySet()){// 遍歷線上使用者Socket集合
StringBuilder sb = new StringBuilder(KeyWords.ONLINE_CLIENTS_LIST);// 線上使用者列表字首
sb.append(mapKeyToStringBesidesItem(ClientManagers, item.getKey()));// 題頭基礎上加上各項
item.getValue().pw.println(sb);// 傳送列表給所有使用者
}
}
}
// 輸入Map<String, ClientManager>後輸出除了指定字串外其餘的用","連線的字串
public String mapKeyToStringBesidesItem(Map<String, ClientManager> stringMap, String besides){
if (stringMap==null) {// 如果輸入的Map集合為空,則不進行操作
return null;
}
StringBuilder result = new StringBuilder();// 結果字串Builder
boolean flag = false;// 是否使用逗號拼接標記
for (Map.Entry<String, ClientManager> item : stringMap.entrySet()) {// 遍歷Map集合
if(item.getKey().equals(besides)) continue;// 如果存在排除的字串則不進行新增操作
if (flag) {// 使用逗號進行拼接(不為第一項)
result.append(",");
}else {// 不適用逗號拼接(第一項標記)
flag=true;
}
result.append(item.getKey());// 拼接字串
}
return result.toString();// 輸出字串
}
// 傳送訊息給所有線上使用者
private void sendMessageForAll(String msg) {
for(Map.Entry<String, ClientManager> item : ClientManagers.entrySet()){// 遍歷線上使用者Socket集合
if(userName.equals(item.getKey())) continue;// 不傳送給自身
item.getValue().pw.println(userName+"["+physicalAddress+"]:"+msg);// 加上訊息頭髮送
}
}
// 傳送訊息給指定使用者
private void sendMessageForSomeOne(List<String> userList, String msg){
for(Map.Entry<String, ClientManager> item : ClientManagers.entrySet()){// 遍歷線上使用者Socket集合
if(userList.contains(item.getKey())) item.getValue().pw.println(userName+"["+physicalAddress+"]:"+msg);// 加上訊息頭髮送
}
}
// TODO 接收資訊方法
private void receiveMessage() throws IOException{
String str = null;// 臨時接收資訊變數
while ((str = br.readLine()) != null){// 迴圈接收客戶端資訊
if(str.equals(KeyWords.CLIENT_CLOSE_POST)){// 客戶端請求斷開連線
stopThread();// 停止執行緒
pw.println(KeyWords.CLIENT_CLOSE_POST_RETURN);// 返回客戶端斷開確認
break;// 結束迴圈
} else if(str.startsWith(KeyWords.SEND_TO_TARGET_START)){// 接收訊息物件列表字首
// 分割出傳送物件列表
String[] tempSendTo = str.substring(KeyWords.SEND_TO_TARGET_START.length(), str.indexOf(KeyWords.SEND_TO_TARGET_END)).split(",");
List<String> sendTo = Arrays.asList(tempSendTo);
// 獲取傳送的訊息
String msg = str.substring(str.indexOf(KeyWords.SEND_TO_TARGET_END) + KeyWords.SEND_TO_TARGET_END.length());
if(Integer.valueOf(1).equals(tempSendTo.length)){// 如果傳送物件只有一個
if("所有人".equals(tempSendTo[0])){// 傳送給所有人
sendMessageForAll(msg);
}else{// 真的只有一個傳送目標
sendMessageForSomeOne(sendTo, msg);
}
}else{// 傳送目標不止一個
sendMessageForSomeOne(sendTo, msg);
}
} else if(str.startsWith(KeyWords.GET_ONLINE_CLIENTS_LIST)){// 請求獲取線上客戶端列表字首
sendOnlineList(true);
}
System.out.println("收到"+userName+"["+physicalAddress+"]"+"發來的訊息:"+str);// 客戶端訊息傳遞日誌
}
System.out.println(userName+"["+physicalAddress+"]"+"已經斷開與伺服器的連線!");// 客戶端斷開連線日誌
stopThread();// 斷開了連線則需要將此執行緒移除
}
// 停止該程序的方法
private void stopThread(){
flag = false;// 標識為空
}
@Override
public void run() {
try {
while (true) {
if(!flag) break;// 如果標識為空則結束迴圈
receiveMessage();// 呼叫接收訊息方法
}
} catch (SocketException e){
stopThread();// 停止執行緒
System.out.println(userName+"["+physicalAddress+"]"+"通過非常規方式斷開了與伺服器的連線!");// 客戶端強制關閉日誌
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
sendMessageForAll("已經斷開與伺服器的連線!");// 客戶端斷開伺服器的連線訊息傳送給所有線上使用者
if(clientSocket != null) clientSocket.close();// 關閉客戶端連線
ClientManagers.remove(userName);// 將執行緒自身從全域性客戶端執行緒管理中移除
sendOnlineList(false);// 傳送當前所有線上使用者的列表
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Client.Java為客戶端部分,輸入與接收分執行緒實現,從而輸入之餘能夠顯示聊天資訊互動,以及一些特殊操作關鍵詞過濾等功能。
package cn.com.dnyy.tcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
public class Client {
private Socket linkServerSocket;// 與伺服器通訊的Socket管道
private BufferedReader br;// 接收服務端輸入
private PrintWriter pw;// 對服務端輸出
private BufferedReader ubr = null;// 接收使用者控制檯輸入
private String userName = null;// 使用者名稱
private boolean flag = true;// 接收執行緒可用標識
private List<String> onlineUserName;// 線上使用者列表
private List<String> sendToList;// 傳送的目標使用者列表
public static void main(String[] args) {
new Client().startUp();// 啟動客戶端
}
// 啟動客戶端
private void startUp(){
try {
linkServerSocket = new Socket("127.0.0.1", 8888);// 連線伺服器IP和埠
ubr = new BufferedReader(new InputStreamReader(System.in));// 通過InputStreamReader轉換流從位元組輸入流InputStream轉換為BufferedReader
br = new BufferedReader(new InputStreamReader(linkServerSocket.getInputStream()));// 通過伺服器Socket建立輸入流
pw = new PrintWriter(linkServerSocket.getOutputStream(), true);// 通過伺服器Socket建立輸出流
onlineUserName = new ArrayList<String>();// 構建新使用者列表
sendToList = new ArrayList<String>();// 構建新的傳送目標使用者列表
System.out.println("連線伺服器通訊正常!");// 提示客戶端連線正常
System.out.println("=========請輸入您的使用者名稱:=========");// 提示使用者輸入使用者名稱資訊
userName = ubr.readLine();// 獲取使用者輸入的使用者名稱
pw.println(userName);// 傳送使用者名稱給伺服器
System.out.println("=========我們歡迎您的到來!=========");
System.out.println("輸入\"-M\"顯示選單功能提示");
System.out.println("輸入訊息並按回車鍵進行訊息傳送");
System.out.println("=========預祝您使用愉快!!=========");
new Thread(new ReceiveMessage()).start();// 啟動接收執行緒
String str = null;// 記錄讀取到的使用者輸入的字元內容
while ((str = ubr.readLine()) != null) {// TODO 迴圈讀取使用者輸入資料
if(!flag) break;
if("-M".equals(str)){// 顯示選單
showMainMenu();
}else if("-U".equals(str)){// 顯示使用者列表
showUserList();
}else if("-Q".equals(str)){// 斷開連線
pw.println(KeyWords.CLIENT_CLOSE_POST);
}else if("-A".equals(str)){// 設定訊息接收人為所有人
sendToList.clear();// 清空訊息接收人
sendToList.add("所有人");// 設定訊息接收人為所有人
}else if("-I".equals(str)){// 檢視當前接收人列表
showReceiverList();
}else if(str.startsWith("-S,")){// 新增接收人
String tempAddName = str.substring(str.indexOf(",") + 1);// 獲取要新增的使用者名稱
if(onlineUserName.contains(tempAddName)) {// 如果存在該使用者
sendToList.add(tempAddName);// 新增到接收名單中
System.out.println("接收人[" + tempAddName + "]新增成功!");
if(sendToList.contains("所有人")) sendToList.remove("所有人");// 如果存在“所有人”選項,則去掉
}
}else{// 傳送訊息
StringBuilder sendTo = new StringBuilder(KeyWords.SEND_TO_TARGET_START);// 訊息物件列表字首
if(sendToList.size() < 1){// 如果傳送目標列表小於1,即無傳送列表
sendToList.add("所有人");// 群聊
}
for (int i = 0; i < sendToList.size(); i++) {// 加入接收使用者列表
if(i < sendToList.size() - 1){
sendTo.append(sendToList.get(i)+",");
}else{
sendTo.append(sendToList.get(i));
}
}
sendTo.append(KeyWords.SEND_TO_TARGET_END+str);// 接收使用者列表字尾+內容
pw.println(sendTo);// 傳輸使用者寫入的資料到伺服器
}
}
} catch (SocketException e){
flag = false;
System.out.println("伺服器正在維護中!請稍後重試登陸!");
System.out.println("=========請按回車關閉程式!=========");
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(linkServerSocket != null) linkServerSocket.close();// 關閉服務端連線
} catch (IOException e) {
e.printStackTrace();
}
try {
if(ubr != null) ubr.close();// 關閉使用者輸入流
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 顯示主選單
private void showMainMenu() {
System.out.println("======================================");
System.out.println("=|[-U]使用者列表|[-I]接收人列表|[-Q]安全退出|=");
}
// 顯示使用者列表
private void showUserList() {
StringBuilder sb = new StringBuilder("==============使用者列表=============");// 分隔符
sb.append("\n");// 換行
for(int i = 0; i < onlineUserName.size(); i++){// 迴圈遍歷在想使用者列表
sb.append(onlineUserName.get(i));// 輸出名字
sb.append("\n");// 換行
}
sb.append("=================================");// 分隔符
sb.append("輸入\"-A\"清空接收人並設定為傳送給所有人\n");
sb.append("輸入\"-S,接收人暱稱\"新增訊息接收人");
System.out.println(sb);// 輸出列表到控制檯
}
// 檢視當前接收人列表
private void showReceiverList() {
StringBuilder sb = new StringBuilder("==============接收列表=============");// 分隔符
sb.append("\n");// 換行
for(int i = 0; i < sendToList.size(); i++){// 迴圈遍歷在想使用者列表
sb.append(sendToList.get(i));// 輸出名字
sb.append("\n");// 換行
}
sb.append("=================================");// 分隔符
sb.append("輸入\"-A\"清空接收人並設定為傳送給所有人\n");
sb.append("輸入\"-S,接收人暱稱\"新增訊息接收人");
System.out.println(sb);// 輸出列表到控制檯
}
// TODO 接收訊息
private void receive() {
try {
String str = br.readLine();// 讀取伺服器訊息
if(str.equals(KeyWords.CLIENT_CLOSE_POST_RETURN)){// 允許關閉程式標識
flag = false;// 如果收到可以關閉程式標識,則關閉程式
System.out.println("=========請按回車關閉程式!=========");
return;
} else if(str.startsWith(KeyWords.ONLINE_CLIENTS_LIST)){// 線上使用者列表字首標識
onlineUserName.clear();// 同步線上使用者前清空之前存在的線上使用者列表
String[] tempUserNameList = str.substring(KeyWords.ONLINE_CLIENTS_LIST.length()).split(",");// 將去掉字首後的字串進行分割
for(String item : tempUserNameList){// 遍歷使用者列表
onlineUserName.add(item);// 將遍歷項新增到使用者列表中
}
for(int i = 0; i < sendToList.size(); i++){// 刪除傳送目標中不存在的線上使用者
if(!onlineUserName.contains(sendToList.get(i))) sendToList.remove(i);
}
if(sendToList.size() < 1){// 如果傳送目標列表小於1,即無傳送列表
sendToList.add("所有人");// 群聊
}
} else {// 獲取訊息
System.out.println(str);// 輸出訊息
}
} catch (SocketException e){
flag = false;
System.out.println("伺服器正在維護中!請稍後重試登陸!");
System.out.println("=========請按回車關閉程式!=========");
} catch (IOException e) {
e.printStackTrace();
}
}
// 接收伺服器資訊執行緒
private class ReceiveMessage implements Runnable {
@Override
public void run() {
while(true){
if(!flag) break;
receive();
}
}
}
}
KeyWords.Java檔案主要是伺服器和客戶端互動的關鍵字記錄檔案。
package cn.com.dnyy.tcp;
public class KeyWords {
public final static String CLIENT_CLOSE_POST = "quit:";// 客戶端請求斷開連線
public final static String CLIENT_CLOSE_POST_RETURN = "disconnect:";// 客戶端請求斷開連線返回值
public final static String GET_ONLINE_CLIENTS_LIST = "getonline:";// 向服務端請求線上客戶端列表字首
public final static String ONLINE_CLIENTS_LIST = "onlinelist:";// 服務端傳送線上客戶端列表字首
public final static String SEND_TO_TARGET_START = "sendto:";// 訊息中接收目標列表字首
public final static String SEND_TO_TARGET_END = ":end";// 訊息中接收目標列表字尾
}
檔案就這3個,內容也非常之簡單,註釋我也寫得十分的詳盡,如果大家有什麼疑問的話,敬請在下方留言,我會一一給大家答疑清楚的,喜歡的話請關注點贊,謝謝支援啦~
相關推薦
【Java】基於TCP協議多執行緒伺服器-客戶端互動控制檯聊天室簡例
前兩天想到一個手機APP專案,使用到藍芽,發現BluetoothSocket和J2EE網路變成的Socket差不多,使用之餘順手寫一個多執行緒伺服器與客戶端互動實現聊天室的一個小例子,方便新人學習網路程式設計模組,期間使用到多執行緒和IO輸入輸出流的
Qt:基於TCP的多執行緒檔案傳輸工具
FTP (File transfer protocol)是一個古老實用的檔案傳輸協議,方便在客戶端和伺服器之間進行檔案的傳輸,我們可以在Linux作業系統上使用vsftpd這個軟體來搭建 FTP 伺服器並建立專有的 FTP 登入賬戶保障伺服器安全,但是它對於非專業使用者來說,使用命令列來
【iOS】第02講 多執行緒NSThread/GCD/RunLoop/NSTimer/Socket資料傳輸
一、NSThread 1.1 基本使用 -(void) createThread{ //NSThread &nb
【收藏】C#中的多執行緒——執行緒同步基礎
第二部分:執行緒同步基礎 同步要領 下面的表格列展了.NET對協調或同步執行緒動作的可用的工具: 簡易阻止方法 構成 目的 Sleep 阻止給定的時間週期 Join 等待另一個執行緒完成 鎖系統 構成 目的 跨程序?
【Spring】Spring高階話題-多執行緒-TaskExecutor
分析 在Spring中,通過任務執行器,也就是TaskExecutor來實現多執行緒和併發程式設計。 使用ThreadPoolTaskExecutor可實現一個基於執行緒池的TaskExecutor。 而實際開發中任務一般是非阻礙的,也就是非非同步
JAVA中基於UDP實現多執行緒通訊
伺服器端程式,利用DatagramSocket負責監聽埠,當客戶端發過來訊息時,伺服器端就會響應,並將訊息內容儲存到Datagrampacket物件中,並且!每一次while迴圈必須重新建立DatagramPacket物件用於儲存訊息資料。並將socket,pa
tcp/ip 多執行緒伺服器端的實現(參考tcp/ip網路程式設計)
執行緒的切換比程序快的多,因為它不需要切換資料區和堆 共享資料區和堆可以用來交換資訊 一、執行緒的建立 pthread_create()函式 #include<pthread.h> int prthread_create(pthread * thread,c
基於QT的多執行緒伺服器
// thread.cpp #include"thread.h" Thread::Thread(intsocketDescriptor,QObject*parent) :QThread(parent) { m_socketDescriptor=socketDescrip
linux網路程式設計之用多執行緒實現客戶端到服務端的通訊(基於udp)
1、開啟一個執行緒接受資料,主執行緒傳送資料的程式碼 #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #includ
【Linux C/C++】 第08講 多執行緒TCP傳輸檔案/select模型
一、多執行緒 pthread.h libpthread.so -lpthread 1.建立多執行緒 1.1 程式碼 &nbs
【紮實基本功】Java基礎教程系列之多執行緒
1. 多執行緒的概念 1.1 程序、執行緒、多程序的概念 程序:正在進行中的程式(直譯)。 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒。 一個應用程式可以理解成就是一個程序。 多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作。 1.
java網路程式設計:9、基於TCP的socket程式設計(二)伺服器端迴圈監聽接收多個客戶端_多執行緒伺服器程式
宣告:本教程不收取任何費用,歡迎轉載,尊重作者勞動成果,不得用於商業用途,侵權必究!!! 文章目錄 一、核心程式碼編寫 1、伺服器端程式的編寫 2、客戶端程式的編寫 3、測試列印輸出 二、系列文章(java網路程式設計) 上篇講了基於tcp的程式設計的一些基礎知識
【Java併發程式設計】之八:多執行緒環境中安全使用集合API(含程式碼)
在集合API中,最初設計的Vector和Hashtable是多執行緒安全的。例如:對於Vector來說,用來新增和刪除元素的方法是同步的。如果只有一個執行緒與Vector的例項互動,那麼,要求獲取
【網路】實現簡單的TCP、UDP伺服器、TCP多程序/多執行緒伺服器
1.0 一個簡單的TCP伺服器(只服務一個客戶端) 先看程式碼如下: server.c #include<stdio.h> #include<stdlib.h> #include<string.h> #includ
【Java】echarts,highcharts中多個y軸對應的一個x軸的數量的Java對應排序程式碼(一個key下的多個value值對應key的位置)
1.首先,可以很輕鬆的從後臺資料庫獲取多個list。list如下: 2.根據其中一個的list的排序,獲取出x軸的陣列。(echarts,highcharts的X,Y軸一般為陣列) Java程式碼: String x[] = new String[li
【go語言 socket程式設計系列】從單執行緒到簡單多執行緒的服務端搭建
簡單單執行緒serverdemo 通過下面程式碼簡單搭建一個服務端,並通過telnet模擬客戶端,演示多客戶端同時請求訪問單執行緒伺服器時的阻塞現象。 package main import ( "fmt" "net" "os" ) func main() {
【Java】基於jsoup爬蟲實現(從智聯獲取工作資訊)
這幾天在學習Java解析xml,突然想到Dom能不能解析html,結果試了半天行不通,然後就去查了一些資料,發現很多人都在用Jsoup解析html檔案,然後研究了一下,寫了一個簡單的例項,感覺還有很多地方需要潤色,在這裡分享一下我的例項,歡迎交流指教!後續想通過Java把資料匯入到Excel或者
TCP/IP網路程式設計 基於Linux程式設計_4 --多執行緒伺服器端的實現
執行緒基本概念 前面我們講過多程序伺服器,但我們知道它開銷很大,因此我們才引入執行緒,我們可以把它看成是一種輕量級程序。它相比程序有如下幾個優點: 執行緒的建立和上下文切換開銷更小且速度更快。 執行緒間交換資料時無需特殊技術。 程序:在作業系統構成
【muduo】多執行緒伺服器的適用場合與程式設計模型
文章目錄 一、程序與執行緒 1、程序的概念 2、關於程序的一個形象比喻(人) 3、執行緒的概念 二、多程序和多執行緒的適用場景 1、需要頻繁建立銷燬的優先用執行緒 2、
Java併發程式設計隨筆【九】中被丟棄的執行緒組ThreadGroup
執行緒組的初衷是作為一種隔離的機制,當然是出於安全的考慮。但是它們從來沒有真正的履行這個承諾,它們的安全價值已經差到根本不在Java安全模型的標準工作中被提及的地步。 既然執行緒組並沒有提供所提及的任何安全功能,那麼它們到底提供了什麼功能呢?不多,它們允許你同