java程序設計之基于tcp協議的socket聊天程序 ,供大家參考,具體內容如下
一、程序實現的功能
1、進入客戶端界面
2、創建昵稱
3、群發信息
4、@私聊
5、下線通知
6、在線人數統計
二、整體架構圖
三、簡單介紹
本程序實現了基于tcp通信的聊天程序:
1 服務器端:
服務器端繼承jframe框架,添加組件。創建服務器端的socket,起一個線程池,每接收到一個客戶端的連接,分配給其一個線程處理與客戶端的通信,將每個客戶端的昵稱和服務器分配給其的輸出流存儲到哈希表中。通過檢索哈希表中昵稱和輸出流實現群聊和私聊。
2 客戶端:
客戶端繼承jframe框架,添加組件。創建客戶端的socket,輸入昵稱,創建客戶端socket對應的輸入流輸出流,與服務器端建立通訊。
四、設計描述
本程序的實現是以服務器端為中繼,客戶端的所有信息都先發送給服務器端。服務器端辨別是否為私聊信息,私聊信息發送給相應的私聊對象,否則,發送給所有的客戶端對象。
(一) 服務器端:
1.1 服務器端繼承jframe框架,添加組件。
1.2 創建服務器端socket。建立一個哈希表,用于存儲客戶端的昵稱以及服務器端對于每個連接的客戶端建立的輸出流。
1.3建立一個線程池,為每個連接的客戶端分配一個執行線程。
1.3調用服務器端socket的accept()函數,等待客戶端的連接,每連接到一個客戶端,線程池給相應的客戶端分配一個線程。
1.4每個線程內,調用服務器端socket,封裝getoutputstream(),獲得服務器端socket相對應于連接的客戶端的輸出流和輸入流。輸入流首先讀取到客戶端發送來的昵稱。將客戶端的昵稱和服務器端的輸出流存儲在哈希表中,并遍歷整個哈希表,將某人上線的通知發送給所有的在線客戶端。
1.5客戶端將信息發送給服務器端,當服務器端傳來的信息符合“@私聊對象:私聊信息”的格式時,服務器端認為這是私聊信息。服務器端將把私聊對象的昵稱從信息中提取出來,通過哈希表找到與昵稱對應的輸出流。將私聊信息通過輸出流發送給私聊對象。
當不是私聊信息時,服務器遍歷整個哈希表,通過每個客戶端對應的輸出流發送給每個客戶端。
1.6當客戶端下線時,服務器端將移除哈希表中對應存儲的客戶端昵稱以及輸出流。并通知在線的每個客戶端,某人已下線。
(二) 客戶端:
2.1 客戶端繼承框架jframe,添加各種組件。
2.2 創建客戶端socket,設置請求連接服務器端ip,以及使用的端口號。
2.3 封裝客戶端socket的getinputstream()函數,獲得客戶端socket的輸入流接受服務器端發來的信息,封裝socket的getoutputstream()函數,獲得socket的輸出流向服務器端發送信息。
2.4 通過向文本框添加動作事件監聽器,監聽文本框的輸入。
2.5 當與服務器端連接成功時,系統提醒輸入昵稱。系統將對輸入的昵稱進行檢索。判斷是否有重復的昵稱。如有重復則創建不成功,繼續輸入。
2.6 向服務器端發送信息時,如想對某人發送私聊信息,則需輸入符合“@私聊對象的呢稱:私聊信息”的格式,此時只有私聊對象和本人可以接收到。否則,發送的信息為公聊信息,所有的客戶端都能夠收到。
2.7 客戶端不斷接受服務器端的信息顯示在文本域。
五、代碼說話
1、服務器端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
|
package server; import java.io.*; import java.net.*; import java.util.hashmap; import java.util.map; import java.util.concurrent.executorservice; import java.util.concurrent.executors; import java.util.concurrent.threadpoolexecutor; import javax.swing.*; import java.awt.*; public class tcpserver extends jframe{ private jtextarea m_display= new jtextarea(); private serversocket serversocket; /** * 創建線程池來管理客戶端的連接線程 * 避免系統資源過度浪費 */ private executorservice exec; // 存放客戶端之間私聊的信息 private map<string,printwriter> storeinfo; public tcpserver() { super ( "聊天程序服務器端" ); container c=getcontentpane(); c.add( new jscrollpane(m_display),borderlayout.center); try { serversocket = new serversocket( 6666 ); storeinfo = new hashmap<string, printwriter>(); exec = executors.newcachedthreadpool(); } catch (exception e) { e.printstacktrace(); } } // 將客戶端的信息以map形式存入集合中 private void putin(string key,printwriter value) { synchronized ( this ) { storeinfo.put(key, value); } } // 將給定的輸出流從共享集合中刪除 private synchronized void remove(string key) { storeinfo.remove(key); m_display.append( "當前在線人數為:" + storeinfo.size()); //for(string name: storeinfo.key) } // 將給定的消息轉發給所有客戶端 private synchronized void sendtoall(string message) { for (printwriter out: storeinfo.values()) { out.println(message); // m_display.append("已經發送了"); } } // 將給定的消息轉發給私聊的客戶端 private synchronized void sendtosomeone(string name,string message) { printwriter pw = storeinfo.get(name); //將對應客戶端的聊天信息取出作為私聊內容發送出去 if (pw != null ) pw.println( "私聊: " +message); } public void start() { try { m_display.setvisible( true ); //m_display.append("mayanshuo"); while ( true ) { m_display.append( "等待客戶端連接... ... \n" ); socket socket = serversocket.accept(); // 獲取客戶端的ip地址 inetaddress address = socket.getinetaddress(); m_display.append( "客戶端:“" + address.gethostaddress() + "”連接成功! " ); /* * 啟動一個線程,由線程來處理客戶端的請求,這樣可以再次監聽 * 下一個客戶端的連接 */ exec.execute(new listenrclient(socket)); //通過線程池來分配線程 } } catch(exception e) { e.printstacktrace(); } } /** * 該線程體用來處理給定的某一個客戶端的消息,循環接收客戶端發送 * 的每一個字符串,并輸出到控制臺 */ class listenrclient implements runnable { private socket socket; private string name; public listenrclient(socket socket) { this.socket = socket; } // 創建內部類來獲取昵稱 private string getname() throws exception { try { //服務端的輸入流讀取客戶端發送來的昵稱輸出流 bufferedreader breader = new bufferedreader( new inputstreamreader(socket.getinputstream(), "utf-8")); //服務端將昵稱驗證結果通過自身的輸出流發送給客戶端 printwriter ipw = new printwriter( new outputstreamwriter(socket.getoutputstream(), "utf-8"),true); //讀取客戶端發來的昵稱 while(true) { string namestring = breader.readline(); if ((namestring.trim().length() == 0) || storeinfo.containskey(namestring)) { ipw.println("fail"); } else { ipw.println("ok"); return namestring; } } } catch(exception e) { throw e; } } @override public void run() { try { /* * 通過服務器端的socket分配給每一個 * 用來將消息發送給客戶端 */ printwriter pw = new printwriter( new outputstreamwriter(socket.getoutputstream(), "utf-8"), true); /* * 將客戶昵稱和其所說的內容存入共享集合hashmap中 */ name = getname(); putin(name, pw); thread.sleep(100); // 服務端通知所有客戶端,某用戶上線 sendtoall("*系統消息* “" + name + "”已上線"); /* * 通過客戶端的socket獲取輸入流 * 讀取客戶端發送來的信息 */ bufferedreader breader = new bufferedreader( new inputstreamreader(socket.getinputstream(), "utf-8" )); string msgstring = null ; while ((msgstring = breader.readline()) != null ) { // 檢驗是否為私聊(格式:@昵稱:內容) if (msgstring.startswith( "@" )) { int index = msgstring.indexof( ":" ); if (index >= 0 ) { //獲取昵稱 string thename = msgstring.substring( 1 , index); string info = msgstring.substring(index+ 1 , msgstring.length()); info = name + ":" + info; //將私聊信息發送出去 sendtosomeone(thename, info); sendtosomeone(name,info); continue ; } } // 遍歷所有輸出流,將該客戶端發送的信息轉發給所有客戶端 m_display.append(name+ ":" + msgstring+ "\n" ); sendtoall(name+ ":" + msgstring); } } catch (exception e) { // e.printstacktrace(); } finally { remove(name); // 通知所有客戶端,某某客戶已經下線 sendtoall( "*系統消息* " +name + "已經下線了。\n" ); if (socket!= null ) { try { socket.close(); } catch (ioexception e) { e.printstacktrace(); } } } } } public static void main(string[] args) { tcpserver server = new tcpserver(); server.setsize( 400 , 400 ); server.setvisible( true ); server.setdefaultcloseoperation(jframe.exit_on_close); server.start(); } } |
2、客戶端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
package server; import java.io.*; import java.net.*; import java.util.scanner; import java.util.concurrent.executorservice; import java.util.concurrent.executors; import java.util.concurrent.threadpoolexecutor; import javax.swing.*; import java.awt.*; import java.awt.event.actionlistener; import java.awt.event.actionevent; public class tcpclient extends jframe { private jtextfield m_enter= new jtextfield(); private jtextarea m_display= new jtextarea(); private int m_count= 0 ; private static socket clientsocket; //private executorservice exec = executors.newcachedthreadpool(); private bufferedreader br; private printwriter pw; public tcpclient() { super ( "聊天程序客戶端" ); container c=getcontentpane(); //m_enter.setsize(100,99); //m_display.setsize(200,100); m_enter.setvisible( true ); m_display.setvisible( true ); m_enter.requestfocusinwindow(); //轉移輸入焦點到輸入區域 //將光標放置在文本區域的尾部 m_display.setcaretposition(m_display.gettext().length()); c.add(m_enter,borderlayout.south); c.add( new jscrollpane(m_display),borderlayout.center); // this.add(panel); // this.setcontentpane(jp); } public static void main(string[] args) throws exception { tcpclient client = new tcpclient(); client.setvisible( true ); client.setdefaultcloseoperation(jframe.exit_on_close); client.setsize( 470 , 708 ); client.start(); } public void start() { try { m_display.append( "請創建用戶名:" ); clientsocket= new socket( "localhost" , 6666 ); bufferedreader br = new bufferedreader( new inputstreamreader(clientsocket.getinputstream(), "utf-8" )); printwriter pw = new printwriter( new outputstreamwriter(clientsocket.getoutputstream(), "utf-8" ), true ); //listenrservser l=new listenrservser(); m_enter.addactionlistener( new actionlistener(){ public void actionperformed(actionevent event) { try { string s=event.getactioncommand(); m_enter.settext( "" ); if (m_count== 0 ) { pw.println(s); m_display.append( "\n'" +s+ "'" + "昵稱設置成功。\n" ); } else { pw.println(s); } m_count++; } catch (exception e) { e.printstacktrace(); } } }); string msgstring; while ((msgstring = br.readline())!= null ) { m_display.append(msgstring+ "\n" ); } } catch (exception e) { e.printstacktrace(); } finally { if (clientsocket != null ) { try { clientsocket.close(); } catch (ioexception e) { e.printstacktrace(); } } } } } |
六、運行結果
1、這里是服務器端,顯示當前連接人數,以及公聊信息:
2、此時為群內成員公聊:
這里創建了三個角色:馬衍碩、李琦琦、小紅。
3、私聊:
私聊格式“@名稱:”+內容。
私聊時,只有私聊的兩個人可以接收到信息,其余人接收不到交流信息。
例:馬衍碩和李琦琦私聊,小紅接收不到私聊信息。
馬衍碩和李琦琦接收到了私聊信息:
小紅沒有接收到私聊信息:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/mmayanshuo/article/details/78511771