內(nèi)容
1.客戶端和服務(wù)器端連接介紹
2.實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)對(duì)聊
3.實(shí)現(xiàn)群聊
一.客戶端和服務(wù)器端連接介紹
1.注意點(diǎn)
①一般來講,有時(shí)候沒有嚴(yán)格意義的服務(wù)器端和客戶端
比如A端去連接遠(yuǎn)程網(wǎng)絡(luò)中的B端,那么此時(shí)B端就扮演服務(wù)器的角色,A端扮演客戶端的角色
②緩存:當(dāng)B不在線時(shí),A先發(fā)給騰訊的服務(wù)器(這里假如是QQ),這里面的內(nèi)容包括B的IP地址,還有端口號(hào),還有具體的內(nèi)容,當(dāng)B上線時(shí),就發(fā)給B。這里就相當(dāng)于做了一個(gè)緩存。
2.作為一個(gè)客戶端必須提供的兩個(gè)內(nèi)容
①IP地址(通過這個(gè)找到某個(gè)設(shè)備)
②端口號(hào)(找到提供服務(wù)的程序,比如qq)
3.客戶端和服務(wù)器端如何連接(socket)
(1)使用Socket實(shí)現(xiàn)連接
Socket的英文原義是“孔”或“插座”。在網(wǎng)絡(luò)編程中,網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket。Socket本質(zhì)是編程接口(API),對(duì)TCP/IP的封裝,TCP/IP也要提供可供程序員做網(wǎng)絡(luò)開發(fā)所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數(shù)據(jù)的具體形式;Socket是發(fā)動(dòng)機(jī),提供了網(wǎng)絡(luò)通信的能力。 Socket實(shí)質(zhì)上提供了進(jìn)程通信的端點(diǎn)。進(jìn)程通信之前,雙方首先必須各自創(chuàng)建一個(gè)端點(diǎn),否則是沒有辦法建立聯(lián)系并相互通信的。正如打電話之前,雙方必須各自擁有一臺(tái)電話機(jī)一樣。
而serversocket 建立的是socket的服務(wù)端,socket建立的是客戶端。所以服務(wù)器端使用ServerSocket
客戶端使用Socket
(2)使用socket實(shí)現(xiàn)網(wǎng)絡(luò)編程的步驟
1.分別在服務(wù)器端和客戶端完成ServerSocket和Socket的創(chuàng)建,這也是我們實(shí)現(xiàn)網(wǎng)絡(luò)通信的基礎(chǔ)
2.打開連接到Socket的相關(guān)的輸入輸出流,進(jìn)行數(shù)據(jù)的通信
3.按照協(xié)議對(duì)Socket進(jìn)行讀寫操作
4.在通信完成以后關(guān)閉輸入輸出流,關(guān)閉socket
如果分兩步的話,就是
第一步是監(jiān)聽(等待數(shù)據(jù)發(fā)送過來),用來接收數(shù)據(jù),需要指定監(jiān)聽的端口號(hào)
第二步是發(fā)送,需要指定發(fā)送到哪個(gè)計(jì)算機(jī)(IP地址),需要指定發(fā)送到這個(gè)計(jì)算機(jī)的哪個(gè)端口。
(3)具體來說
對(duì)于服務(wù)器端可以
①創(chuàng)建ServerSocket
②使用accept等待客戶端連接
③使用OutputStream發(fā)送數(shù)據(jù)
④使用InputStream接收數(shù)據(jù)
⑤在通信完成以后關(guān)閉輸入輸出流,關(guān)閉socket
對(duì)于客戶端可以
①創(chuàng)建Socket對(duì)象,連接服務(wù)器端
②使用InputStream接收數(shù)據(jù)
③使用OutputStream發(fā)送數(shù)據(jù)
④在通信完成以后關(guān)閉輸入輸出流,關(guān)閉socket
二.實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)對(duì)聊
1.簡(jiǎn)單的接收數(shù)據(jù)
在進(jìn)行較為復(fù)雜的操作之前,我們不妨先進(jìn)行一些簡(jiǎn)單的接收數(shù)據(jù)的操作。比如下面
服務(wù)器端
import java.io.*;
import java.net.*;
//創(chuàng)建一個(gè)類Server管理服務(wù)器端的內(nèi)容
//1.服務(wù)器端的程序運(yùn)行在服務(wù)器上,所以IP地址默認(rèn)是當(dāng)前電腦的IP地址
class Server{
private int port;//端口號(hào)
private ServerSocket serverSocket;
public Server(int port) {
this.port = port;
}
public void start() throws IOException {
//創(chuàng)建提供服務(wù)的socket
serverSocket = new ServerSocket(port);
//等待用戶連接accept
//如果有客戶端來連接,就得到這個(gè)客戶端的socket對(duì)象
//如果沒有客戶端來連接,就阻塞(一直等待)
Socket socket = serverSocket.accept();
//有了連接,就可以向客戶端發(fā)送響應(yīng)信息
//輸出流如果輸出完畢,必須關(guān)閉,表示輸出結(jié)束
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("連接成功,可以通信了!");
socket.shutdownOutput();//關(guān)閉輸出流
//接收客戶端的響應(yīng)信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
//讀取數(shù)據(jù)
System.out.println(br.readLine());
}
}
public class Myclass {
public static void main(String[] args) throws IOException {
Server server = new Server(8888);
server.start();
}
}
客戶端
import java.io.*;
import java.net.*;
//創(chuàng)建一個(gè)類Client管理客戶端的內(nèi)容
class Client{
private int port;//客戶端的端口號(hào)
private String ipAddr;//客戶端的IP地址
private Socket socket;
public Client(int port,String ipAddr) {
this.port = port;
this.ipAddr = ipAddr;
}
public void start() throws UnknownHostException, IOException {
//向服務(wù)器端發(fā)起連接
socket = new Socket(ipAddr,port);
//接收服務(wù)器端的響應(yīng)信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
//讀取數(shù)據(jù)
System.out.println(br.readLine());
//向服務(wù)器端發(fā)送數(shù)據(jù)
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter(osw);
bw.write("你好!");
bw.flush();//注意,要flush
//關(guān)閉輸出流
socket.shutdownOutput();
}
}
public class Myclass {
public static void main(String[] args) throws UnknownHostException, IOException {
Client client = new Client(8888,"127.0.0.1");
client.start();
}
}
以上程序?qū)崿F(xiàn)了服務(wù)器端的簡(jiǎn)單連接和數(shù)據(jù)的簡(jiǎn)單發(fā)送。下面,讓程序稍微復(fù)雜一點(diǎn)
服務(wù)器端
//服務(wù)器端不斷輸入
Scanner scanner = new Scanner(System.in);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
while(scanner.hasNext()) {
ps.println(scanner.nextLine());
}
客戶端
//接收服務(wù)器端的響應(yīng)信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
//讀取數(shù)據(jù)(不斷接收)
String line = null;
while((line = br.readLine()) != null) {
System.out.println(line);
}
這樣,在服務(wù)器端發(fā)送數(shù)據(jù),客戶端就可以接收到并且打印出來,這樣就實(shí)現(xiàn)了點(diǎn)對(duì)點(diǎn)的對(duì)聊
注意:readLine()這個(gè)方法會(huì)阻塞,也就是說它會(huì)一直等待內(nèi)容,沒有內(nèi)容就一直等著。包括scanner的nextLine()也是,一直等著你輸入,也會(huì)阻塞。
三.實(shí)現(xiàn)群聊
1.引
上面實(shí)現(xiàn)的點(diǎn)對(duì)點(diǎn)對(duì)聊只能是單方向的,不能既發(fā)送數(shù)據(jù)又接收數(shù)據(jù),因?yàn)?strong>只有一個(gè)主線程,程序執(zhí)行是從上到下的。所以若想真正實(shí)現(xiàn)兩者之間的”對(duì)話“,就必須使用多線程。一個(gè)線程是接收數(shù)據(jù)的,一個(gè)線程是發(fā)送數(shù)據(jù)的。不論是客戶端還是服務(wù)器端,都是如此。這里就不再寫,之間實(shí)現(xiàn)群聊功能。
2.介紹
做群聊的時(shí)候,服務(wù)器起的作用就是 數(shù)據(jù)的緩存和分發(fā)。當(dāng)一個(gè)客戶端發(fā)送數(shù)據(jù)給服務(wù)器端之后,服務(wù)器端要把這個(gè)數(shù)據(jù)向所有的(除了發(fā)送此數(shù)據(jù)的客戶端)客戶端發(fā)送這個(gè)數(shù)據(jù),從而造成的結(jié)果是一個(gè)人在群里面發(fā)送消息,群里面所有人都能接收到。
3.注意點(diǎn)
①一定要在receive接收之后再分發(fā)。
②要把每個(gè)客戶端的socket保存在數(shù)組中
4.具體代碼
服務(wù)器端
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Scanner;
//創(chuàng)建一個(gè)類Server管理服務(wù)器端的內(nèi)容
//1.服務(wù)器端的程序運(yùn)行在服務(wù)器上,所以IP地址默認(rèn)是當(dāng)前電腦的IP地址
class Server{
private int port;
private ServerSocket serverSocket;
//private Socket socket;
private ArrayList<Socket> sockets;
public Server(int port) {
this.port = port;
sockets = new ArrayList<>();
}
public void start() {
//創(chuàng)建服務(wù)的socket對(duì)象
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}
//等待連接
while(true) {
try {
//接收客戶端的連接
Socket socket = serverSocket.accept();
//保存這個(gè)客戶端對(duì)應(yīng)的socket對(duì)象
sockets.add(socket);
//創(chuàng)建一個(gè)線程用于發(fā)送數(shù)據(jù)
new Send(socket).start();
//創(chuàng)建一個(gè)線程用于接收數(shù)據(jù)
new Receive(socket,sockets).start();
} catch (Exception e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}
}
}
}
//發(fā)送線程
class Send extends Thread{
private Socket socket;
public Send(Socket socket) {
this.socket = socket;
}
public void run() {
Scanner scanner = null;
PrintStream ps = null;
try {
scanner = new Scanner(System.in);
ps = new PrintStream(socket.getOutputStream());
while(scanner.hasNext()) {
ps.println(scanner.nextLine());
}
} catch (IOException e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}finally{
scanner.close();
ps.close();
}
}
}
//接收線程
class Receive extends Thread{
private Socket socket;
private ArrayList<Socket> lists;
public Receive(Socket socket,ArrayList<Socket> lists) {
this.socket = socket;
this.lists = lists;
}
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while((line = br.readLine()) != null) {
//分發(fā)內(nèi)容
for(Socket s:lists) {
//判斷是不是當(dāng)前這個(gè)客戶端
if(s != socket) {
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println(line);
}
}
}
} catch (IOException e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}finally {
try {
br.close();
} catch (IOException e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}
}
}
}
public class Myclass {
public static void main(String[] args) throws IOException {
Server server = new Server(8888);
server.start();
}
}
客戶端(客戶端可以有多個(gè),但是代碼都可以是一樣的,所以我在這里就寫出一個(gè))
import java.io.*;
import java.net.*;
import java.util.Scanner;
//創(chuàng)建一個(gè)類Client管理客戶端的內(nèi)容
class Client{
private int port;
private String ip;
private Socket socket;
public Client(int port,String ip) {
this.port = port;
this.ip = ip;
}
public void start() {
//連接服務(wù)器端
try {
socket = new Socket(ip,port);
}catch (IOException e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}
//開啟線程發(fā)送數(shù)據(jù)
new Send(socket).start();
//開啟線程接收數(shù)據(jù)
new Receive(socket).start();
}
}
class Send extends Thread{
private Socket socket;
public Send(Socket socket) {
this.socket = socket;
}
public void run() {
Scanner scanner = null;
PrintStream ps = null;
try {
scanner = new Scanner(System.in);
ps = new PrintStream(socket.getOutputStream());
while(scanner.hasNext()) {
ps.println(scanner.nextLine());
}
} catch (IOException e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}finally {
scanner.close();
ps.close();
}
}
}
class Receive extends Thread{
private Socket socket;
public Receive(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
} catch (IOException e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}
}
}
}
public class Myclass {
public static void main(String[] args) throws UnknownHostException, IOException {
Client client = new Client(8888,"127.0.0.1");
client.start();
}
}
總結(jié)
一開始socket沒有搞懂,后來通過查閱很多資料就大概了解是什么意思了。在網(wǎng)絡(luò)編程的這些代碼中,很多代碼思路都是一樣的,比如客戶端和服務(wù)器端的代碼就有很多相同點(diǎn)和類似的地方。比如都要有端口號(hào)呀,然后都有接收和發(fā)送數(shù)據(jù)等等。到今天為止網(wǎng)絡(luò)編程學(xué)習(xí)結(jié)束,但是我自知自己還未完全掌握,甚至連百分之五十都可能沒掌握到,沒真正學(xué)會(huì),所以還是得抽時(shí)間復(fù)習(xí)網(wǎng)絡(luò)編程的這些內(nèi)容,包括之前Java的部分內(nèi)容。