1 引言:
1.0 本項(xiàng)目還在不斷完善之中判帮,歡迎提出寶貴的建議袱饭。
1.1 實(shí)驗(yàn)?zāi)康?/h4>
初步需求如下:用你熟悉的語(yǔ)言雌桑,開(kāi)發(fā)一個(gè)基于網(wǎng)絡(luò)(比如Socket)的簡(jiǎn)單的互操作程序。要求在局域網(wǎng)內(nèi)實(shí)現(xiàn)網(wǎng)絡(luò)版的QQ务荆,多個(gè)終端可以進(jìn)行文本聊天妆距。
1.2 技術(shù)路線(xiàn)和開(kāi)發(fā)環(huán)境
本次任務(wù)的技術(shù)路線(xiàn)和開(kāi)發(fā)環(huán)境:
開(kāi)發(fā)語(yǔ)言:
- Java(Java 11)
圖形化界面內(nèi)容:
- JavaFX
開(kāi)發(fā)環(huán)境和工具:
- IntelliJ IDEA Educational
- JavaFX Scene Builder 2.0
技術(shù)路線(xiàn):建立C/S架構(gòu),基于Java-Socket建立TCP連接傳遞信息
開(kāi)發(fā)模式:原型開(kāi)發(fā)
備注:由于這個(gè)項(xiàng)目比較小蛹含,出于效率考慮毅厚,不會(huì)完全按照軟件工程的步驟嚴(yán)格執(zhí)行流程。
2 初步設(shè)計(jì)思路
2.0 總體設(shè)計(jì)
總體上浦箱,在功能的實(shí)現(xiàn)中吸耿,我們?cè)诟拍钌献屢慌_(tái)機(jī)器擔(dān)任服務(wù)端,若干臺(tái)機(jī)器擔(dān)任客戶(hù)端進(jìn)行通訊酷窥。
這些機(jī)器共享一個(gè)聊天室咽安。由于不同機(jī)器在對(duì)話(huà)上具有對(duì)等性,我們考慮在這個(gè)程序內(nèi)讓同一臺(tái)機(jī)器既可以當(dāng)客戶(hù)端蓬推,也可以當(dāng)服務(wù)端妆棒。
換言之,讓概念上的客戶(hù)端和服務(wù)端位于同一個(gè)程序中。不同計(jì)算機(jī)的地位區(qū)別在于程序使用的時(shí)候提前進(jìn)行多方約定糕珊,由某一臺(tái)計(jì)算機(jī)擔(dān)任服務(wù)端開(kāi)放端口动分,而讓其他計(jì)算機(jī)擔(dān)任客戶(hù)端連接該端口。
在本設(shè)計(jì)中客戶(hù)端和服務(wù)端的區(qū)別有:
- 聊天室起源于服務(wù)端開(kāi)放端口红选,由客戶(hù)端進(jìn)行連接
- (施工中澜公。。喇肋。)
2.1 客戶(hù)端設(shè)計(jì)
客戶(hù)端主要需要考慮以下功能:
- 連接到局域網(wǎng)中的IP與端口坟乾,加入虛擬聊天室
- 在聊天室中發(fā)言
- (施工中。蝶防。甚侣。)
2.2 服務(wù)端設(shè)計(jì)
服務(wù)端主要需要考慮以下功能:
- 在會(huì)話(huà)中開(kāi)放端口,創(chuàng)建虛擬聊天室
- 在聊天室中發(fā)言
- (施工中间学。殷费。。)
2.3 界面設(shè)計(jì)
(施工中低葫。宗兼。。)
3 協(xié)議設(shè)計(jì)
這是一個(gè)基于TCP的連接氮采,然而我們需要設(shè)定一定的協(xié)議,使得通訊過(guò)程更加順暢染苛,沒(méi)有差錯(cuò)鹊漠。
單方發(fā)送小寫(xiě)字母q可以掐斷聯(lián)系
(施工中。茶行。躯概。)
4 類(lèi)設(shè)計(jì)
設(shè)計(jì)兩個(gè)類(lèi),一個(gè)類(lèi)是Clinet畔师,另一個(gè)類(lèi)是Server娶靡。目前先考慮命令行的基本功能操作。
4.1 服務(wù)器相關(guān)類(lèi)
地址:
src/Server/
繼承:
父類(lèi)Thread
ServerThread類(lèi)繼承Java中的Thread類(lèi)看锉,這是為了支持網(wǎng)絡(luò)進(jìn)行的多線(xiàn)程設(shè)計(jì)姿锭。
屬性:
其中有一個(gè)ServerSocket類(lèi)型的屬性和一個(gè)Socket類(lèi)型的屬性
ServerSocket與Socket不同,ServerSocket是等待客戶(hù)端的請(qǐng)求伯铣,一旦獲得一個(gè)連接請(qǐng)求呻此,就創(chuàng)建一個(gè)Socket示例來(lái)與客戶(hù)端進(jìn)行通信。參考鏈接
然后我們需要這個(gè)類(lèi)的構(gòu)造函數(shù)腔寡,當(dāng)然主要指定Port對(duì)ServerSocket初始化即可
public ServerThread(int port) {
try {
server = new ServerSocket(port);
} catch (IOException e) {
System.out.println("Error Occcurs:");
e.printStackTrace();
}
}
由于目前我們只使用這個(gè)類(lèi)進(jìn)行通信焚鲜,并且為了便于之后對(duì)這個(gè)類(lèi)進(jìn)行測(cè)試,所以添加一個(gè)函數(shù)入口,初始化線(xiàn)程為2333端口忿磅。
public static void main(String[] args) {
ServerThread server = new ServerThread(2333);
server.start();
}
在通信的過(guò)程中糯彬,我們需要一個(gè)新的線(xiàn)程來(lái)幫助我們發(fā)送消息,所以我們建立一個(gè)內(nèi)部類(lèi)葱她,并且重載run函數(shù)幫助我們進(jìn)行消息發(fā)送撩扒。主要機(jī)理是不斷讀取緩沖區(qū),如果讀取到退出則退出览效,不然就繼續(xù)却舀。
class sendMessageThread extends Thread{
@Override
public void run(){
super.run();
Scanner scanner = null;
OutputStream out = null;
try{
if(socket != null){
scanner = new Scanner(System.in);
out = socket.getOutputStream();
String in = "";
do {
in = scanner.next();
out.write((in).getBytes());
out.flush();
}while (!in.equals("q"));
scanner.close();
try{
out.close();
}catch (IOException e){
e.printStackTrace();
}
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
4.2 客戶(hù)端相關(guān)類(lèi)
地址:
src/Clinet/
繼承:
父類(lèi)Thread
ClinetThread類(lèi)繼承Java中的Thread類(lèi),這是為了支持網(wǎng)絡(luò)進(jìn)行的多線(xiàn)程設(shè)計(jì)锤灿。
屬性:
其中有一個(gè)一個(gè)Socket類(lèi)型的屬性以建立連接
我們需要一個(gè)構(gòu)造函數(shù)提供IP和地址初始化這一個(gè)線(xiàn)程
public ClientThread(String host, int port) {
try {
socket = new Socket(host, port);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Clinet同樣需要一個(gè)發(fā)送消息的內(nèi)部類(lèi)
class sendMessageThread extends Thread{
@Override
public void run() {
super.run();
Scanner scanner=null;
OutputStream out= null;
try {
scanner=new Scanner(System.in);
out= socket.getOutputStream();
String message="";
do{
message=scanner.next();
out.write((""+message).getBytes());
out.flush();
}while(!message.contentEquals("q"));
} catch (IOException e) {
e.printStackTrace();
}
scanner.close();
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
繼承線(xiàn)程類(lèi)并重載的run函數(shù)大同小異
@Override
public void run() {
new sendMessageThread().start();
super.run();
try {
InputStream s = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
String txt = "";
boolean exist = false;
while ((len = s.read(buffer)) != -1) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Server at " + df.format(new Date()) + ":");
System.out.println(new String(buffer, 0, len));
}
} catch (Exception e) {
e.printStackTrace();
}
}
主方法
public static void main(String[] args) {
ClientThread clientThread=new ClientThread("127.0.0.1", 2333);
clientThread.start();
}
4.3 功能選擇/協(xié)調(diào)類(lèi)
位置src/sample
這里姑且以Main.java命名這個(gè)類(lèi)
其實(shí)我們只要做一個(gè)功能選擇就可以了挽拔,然后讓客戶(hù)輸入對(duì)應(yīng)的信息進(jìn)行選擇C或者S模式。此處略但校。
其他
關(guān)于多線(xiàn)程:
Java中可以由主線(xiàn)程派生其他線(xiàn)程螃诅,也可以由其他線(xiàn)程派生其他線(xiàn)程,線(xiàn)程一般有以下?tīng)顟B(tài):
我們這個(gè)例子的狀態(tài)轉(zhuǎn)換是這樣的:線(xiàn)程被構(gòu)造状囱,并且進(jìn)入start態(tài)术裸,在一般的網(wǎng)絡(luò)通信中,資源不會(huì)很多亭枷,會(huì)直接進(jìn)入running態(tài)袭艺,然后持續(xù)。
參考鏈接
5.測(cè)試
編譯代碼叨粘,在根目錄輸入 java sample/Main
開(kāi)啟兩個(gè)窗口猾编,按照提示操作
可以看到完成基本功能
6. 不足與下一個(gè)改進(jìn)點(diǎn):
- 由于buffer調(diào)用的特性,空格間隔的消息會(huì)被截?cái)喑扇舾蓷l
- 退出還比較僵硬升敲,可以考慮用個(gè)協(xié)議對(duì)消息進(jìn)行封裝
- 缺乏連接失敗的錯(cuò)誤提醒
- 可以考慮自行指定port的方法
項(xiàng)目鏈接地址:
https://github.com/SoneMiyuki/MiddleWare/tree/main/lab1-Mychat/1.0