《Java開發實戰1200例》(第2卷)學習筆記—TCP網路通訊
例項313:使用Socket通訊
通過Socket類的getInputStream()方法獲得輸入流物件,並藉助InputStreamReader類將輸入流物件轉換為BufferedReader物件讀取接收到的資訊,使用getOutputStream()方法獲得輸出流物件,並建立了PrintWriter物件傳送資訊。
主要程式碼如下:
//客戶端程式
private void connect() { // 連線套接字方法
ta_info.append("嘗試連線......\n"); // 文字域中資訊資訊
try { // 捕捉異常
socket = new Socket("192.168.1.193", 1978); // 例項化Socket物件
while (true) {
writer = new PrintWriter(socket.getOutputStream(), true);
reader = new BufferedReader(new InputStreamReader(socket
.getInputStream())); // 例項化BufferedReader物件
ta_info.append("完成連線。\n" ); // 文字域中提示資訊
getClientInfo();
}
} catch (Exception e) {
e.printStackTrace(); // 輸出異常資訊
}
}
//獲取資訊
private void getClientInfo() {
try {
while (true) { // 如果套接字是連線狀態
if (reader != null) {
String line = reader.readLine();
if (line != null)
ta_info.append("接收到伺服器傳送的資訊:" + line + "\n"); // 獲得客戶端資訊
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();// 關閉流
}
if (socket != null) {
socket.close(); // 關閉套接字
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//伺服器程式
public void getserver() {
try {
server = new ServerSocket(1978); // 例項化Socket物件
ta_info.append("伺服器套接字已經建立成功\n"); // 輸出資訊
while (true) { // 如果套接字是連線狀態
ta_info.append("等待客戶機的連線......\n"); // 輸出資訊
socket = server.accept(); // 例項化Socket物件
reader = new BufferedReader(new InputStreamReader(socket
.getInputStream())); // 例項化BufferedReader物件
writer = new PrintWriter(socket.getOutputStream(), true);
getClientInfo(); // 呼叫getClientInfo()方法
}
} catch (Exception e) {
e.printStackTrace(); // 輸出異常資訊
}
}
//獲取資訊
private void getClientInfo() {
try {
while (true) { // 如果套接字是連線狀態
String line = reader.readLine();
if (line != null)
ta_info.append("接收到客戶機發送的資訊:" + line + "\n"); // 獲得客戶端資訊
}
} catch (Exception e) {
ta_info.append("客戶端已退出。\n"); // 輸出異常資訊
} finally {
try {
if (reader != null) {
reader.close();// 關閉流
}
if (socket != null) {
socket.close(); // 關閉套接字
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
例項314:防止Socket傳遞漢字亂碼
之所以會在傳遞中出現漢字亂碼,是因為傳送資料和接收資料所使用的編碼不同,因此,要解決傳遞漢字亂碼問題,只需要在建立輸入流和輸出流物件時,使用相同的編碼,在使用OutputStreamWriter類和InputStreamReader類建立物件時,在構造方法中指定相同的編碼。
主要程式碼如下:
reader=new BufferedReader(new InputStreamReader(socket
.getInputStream(),"UTF-8"));
out=new OutputStreamWriter(socket.getOutputStream(),"UTF-8");
注意:由於使用Socket通訊是通過IO流實現的,因此為了避免傳遞漢字亂碼,只需要記住一個原則,那就是通過什麼字符集傳送的資料,就使用什麼字符集接收資料,這樣就不會發生Socket傳遞漢字亂碼的問題。
例項315:使用Socket傳遞物件
只有序列化的物件才能被Socket傳遞,在Java中實現Serializable介面的類所建立的物件就是序列化物件。
在例項314的基礎上修改程式碼,主要程式碼如下:
out=new ObjectOutputStream(socket.getOutputStream());//建立輸出流物件
in=new ObjectInputStream(socket.getInputStream());//建立輸入流物件
//getClientInfo()方法用於接收客戶端傳送的資訊,該方法的關鍵程式碼如下:
while(true){
Student stud=(Student)in.readObject();
if(stud!=null)
ta_info.append("接收到客戶機發送的 編號為:"+stud.getId()+" 名稱為:"+stud.getName()+"\n");//獲得客戶端資訊
}
例項316:使用Socket傳輸圖片
本例項通過使用DataInputStream類的read()方法,將圖片檔案讀取到位元組陣列,然後使用DataOutputStream類從DataOutput類繼承的write()方法輸出位元組陣列,從而實現了使用Socket傳輸圖片的功能。
主要程式碼如下:
//伺服器接收客戶端傳送的圖片的關鍵程式碼
long lengths=in.readLong();//讀取圖片檔案的長度
byte[] bt=new byte[(int)lengths];//建立位元組陣列
for(int i=0;i<bt.length;i++){
bt[i]=in.readByte();//讀取位元組資訊並存儲到位元組陣列
}
receiveImg=new ImageIcon(bt).getImage();//建立影象物件
receiveImagePanel.repaint();//重新繪製圖像
//客戶端傳送事件的程式碼如下:
DataInputStream inStream=null;//定義資料輸入流物件
if(imgFile!=null){
lengths=imgFile.length();//獲得選擇圖片的大小
inStream=new DataInputStream(new FileInputStream(imgFile));//建立輸入流物件
}else{
JOptionPane.showMessageDialog(null,"還沒有選擇圖片檔案。");
return;
}
out.writeLong(lengths);//將檔案的大小寫入輸出流
byte[] bt=new byte[(int)lengths];//建立位元組陣列
int len=-1;
while((len=inStream.read(bt))!=-1){//將圖片檔案讀取到位元組陣列
out.write(bt);//將位元組陣列寫入輸出流
}
例項317:使用Socket傳輸音訊
同例項316一樣,使用DataInputStream類的read()方法和DataOutputStream類從DataOutput類繼承的write()方法實現了對音訊檔案的讀寫操作。
使用儲存對話方塊,將接收到的音訊檔案儲存到接收方的主機上。
主要程式碼入下:
//接受客戶端傳送的音訊檔案
String name=in.readUTF();//讀取檔名
long lengths=in.readLong();//讀取檔案的長度
byte[] bt=new byte[(int)lengths];//建立位元組陣列
for(int i=0;i<bt.length;i++){
bt[i]=in.readByte();//讀取位元組資訊並存儲到位元組陣列
}
FileDialog dialog=new FileDialog(ServerSocketFrame.this,"儲存");//建立對話方塊
dialog.setMode(FileDialog.SAVE);//設定對話方塊為儲存對話方塊
dialog.setFile(name);//設定儲存對話方塊顯示的檔名
dialog.setVisible(true);//顯示儲存對話方塊
String newFileName=dialog.getFile();//獲得檔案的儲存路徑
String newFileName=dialog.getFile();//獲得儲存的檔名
if(path==null||newFileName==null){
return;
}
String pathAndName=path+"\\"+newFileName;//檔案的完整路徑
FileOutputStream fOut=new FIleOutputStream(pathAndName);//建立輸出流物件
fOut.write(bt);//向輸出流寫資訊
fOut.flush();//更新緩衝區
fOut.close();關閉輸出流物件
ta_info.append("檔案接收完畢。\n");
//客戶端中傳送事件程式碼
DataInputStream inStream=null;//定義資料輸入流物件
if(file!=null){
lengths=file.length();//獲得所選擇音訊檔案的大小
inStream=new DataInputStream(new FileInputStream(file));//建立輸入流物件
}else{
JOptionPane.showMessageDialog(null,"還沒有選擇音訊檔案。");
return;
}
out.writeUTF(fileName);//寫入音訊檔名
out.writeLong(lengths);//將檔案的大小寫入輸出流
byte[]bt=new byte[(int)lengths];//建立位元組陣列
int len=-1;//用於儲存讀取到的位元組數
while(len=inStream.read(bt)!=-1){//將音訊檔案讀取到位元組陣列
out.write(bt);
}
out.flush();//更新輸出流物件
out.close();//關閉輸出流物件
ta_info.append("檔案傳送完畢。\n");
例項318:使用Socket傳輸視訊
本例項與例項317基本相同,只是在檔案選擇對話方塊中指定的用於傳送的檔案型別不同而已。
主要程式碼如下:
//為檔案選擇對話方塊指定音訊型別的檔案,程式碼如下:
JFileChooser fileChoose=new JFileChooser();//建立檔案選擇器
FileFilter filter=new FileNameExtensionFilter("音訊檔案(WAV/MIDI/MP3/AU)","WAV","MID","MP3","AU");//建立音訊過濾器
fileChooser.setFileFilter(filter);//設定過濾器
int flag=flag=fileChooser.showOpenDialog(null);//顯示開啟對話方塊
例項319:一個伺服器與一個客戶端通訊
伺服器端程式:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JButton;
import java.awt.Font;
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.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class ServerSocketFrame extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private JTextArea textArea;
private ServerSocket server;
private Socket socket;//該類實現客戶端套接字(也稱為“套接字”)。 套接字是兩臺機器之間通訊的端點。
private BufferedReader reader;
private PrintWriter writer;
private JTextField textField;
/**
* 主程式
*/
public static void main(String[] args) {
ServerSocketFrame frame = new ServerSocketFrame();
frame.setVisible(true);
frame.getServer();
}
/**
* 建立窗體
*/
public ServerSocketFrame() {
super();
setTitle("伺服器端程式");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 379, 260);
final JScrollPane scrollPane = new JScrollPane();//新增滾動窗體
getContentPane().add(scrollPane, BorderLayout.CENTER);
textArea= new JTextArea();
scrollPane.setViewportView(textArea);
final JPanel panel = new JPanel();
getContentPane().add(panel, BorderLayout.SOUTH);
final JLabel label = new JLabel();
label.setText("伺服器傳送的資訊:");
panel.add(label);
textField = new JTextField();
textField.setPreferredSize(new Dimension(150, 25));
panel.add(textField);
final JButton button = new JButton();
button.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
writer.println(textField.getText()); // 將文字框中資訊寫入流
textArea.append("伺服器傳送的資訊是:" + textField.getText() + "\n"); // 將文字框中資訊顯示在文字域中
textField.setText(""); // 將文字框清空
}
});
button.setText("發 送");
panel.add(button);
final JPanel panel_1 = new JPanel();
getContentPane().add(panel_1, BorderLayout.NORTH);
final JLabel label_1 = new JLabel();
label_1.setForeground(new Color(0, 0, 255));
label_1.setFont(new Font("", Font.BOLD, 22));
label_1.setText("一對一通訊——伺服器端程式");
panel_1.add(label_1);
}
/**
* 建立伺服器端套接字
監聽客戶端程式
建立向客戶端傳送資訊的輸出流物件和用於接收客戶端傳送資訊的輸入流物件
*
*/
public void getServer(){
try {
//ServerSocket()建立未繫結的伺服器套接字。
server = new ServerSocket(1978); // 例項化Socket物件
textArea.append("伺服器套接字已經建立成功\n"); // 輸出資訊
while (true) { // 如果套接字是連線狀態
textArea.append("等待客戶機的連線......\n"); // 輸出資訊
socket = server.accept(); // 例項化Socket物件,偵聽要連線到此套接字並接受它。 該方法將阻塞直到建立連線。
reader = new BufferedReader(new InputStreamReader(socket
.getInputStream())); // 例項化BufferedReader物件,建立一個使用預設字符集的InputStreamReader。
writer = new PrintWriter(socket.getOutputStream(), true);//從現有的OutputStream建立一個新的PrintWriter。
getClientInfo(); // 呼叫getClientInfo()方法
}
} catch (Exception e) {
e.printStackTrace(); // 輸出異常資訊
}
}
/**
* 接收客戶端傳送的資訊
*/
private void getClientInfo() {
// TODO Auto-generated method stub
try {
while(true){
//readLine()
//讀一行文字。 一行被視為由換行符('\ n'),回車符('\ r')中的任何一個或隨後的換行符終止。
String line=reader.readLine();
if(line!=null)
textArea.append("接收到客戶機發送的資訊:"+line+"\n");
}
} catch (Exception e) {
// TODO: handle exception
textArea.append("客戶端已退出。\n");
}
finally {
try {
if (reader != null) {
reader.close();// 關閉流
}
if (socket != null) {
socket.close(); // 關閉套接字
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客戶端程式:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JButton;
import java.awt.Font;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class ClientSocketFrame extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private PrintWriter writer; // 宣告PrintWriter類物件
private BufferedReader reader; // 宣告BufferedReader物件
private Socket socket; // 宣告Socket物件
private JTextArea textArea; // 建立JtextArea物件
private JTextField textField; // 建立JtextField物件
/**與伺服器連線的套接字物件、輸入流物件和輸出流物件
* 以及在文字域中顯示與伺服器的連線資訊和接收到伺服器端傳送的資訊
*/
private void connect() { // 連線套接字方法
textArea.append("嘗試連線......\n"); // 文字域中資訊資訊
try { // 捕捉異常
socket = new Socket("10.82.25.166", 1978); // 例項化Socket物件
while (true) {
writer = new PrintWriter(socket.getOutputStream(), true);
reader = new BufferedReader(new InputStreamReader(socket
.getInputStream())); // 例項化BufferedReader物件
textArea.append("完成連線。\n"); // 文字域中提示資訊
getServerInfo();
}
} catch (Exception e) {
e.printStackTrace(); // 輸出異常資訊
}
}
public static void main(String[] args) { // 主方法
ClientSocketFrame clien = new ClientSocketFrame(); // 建立本例物件
clien.setVisible(true); // 將窗體顯示
clien.connect(); // 呼叫連線方法
}
/**
* 接收服務端傳送的資訊
*/
private void getServerInfo() {
try {
while (true) {
if (reader != null) {
String line = reader.readLine();// 讀取伺服器傳送的資訊
if (line != null)
textArea.append("接收到伺服器傳送的資訊:" + line + "\n"); // 顯示伺服器端傳送的資訊
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();// 關閉流
}
if (socket != null) {
socket.close(); // 關閉套接字
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 建立客戶端窗體
*/
public ClientSocketFrame() {
super();
setTitle("客戶端程式");
setBounds(100, 100, 361, 257);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel();
getContentPane().add(panel, BorderLayout.NORTH);
final JLabel label = new JLabel();
label.setFont(new Font("", Font.BOLD, 22));
label.setText("一對一通訊——客戶端程式");
panel.add(label);
final JPanel panel_1 = new JPanel();
getContentPane().add(panel_1, BorderLayout.SOUTH);
final JLabel label_1 = new JLabel();
label_1.setText("客戶端傳送的資訊:");
panel_1.add(label_1);
textField = new JTextField();
textField.setPreferredSize(new Dimension(140, 25));
panel_1.add(textField);
final JButton button = new JButton();
button.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
writer.println(textField.getText()); // 將文字框中資訊寫入流
textArea.append("客戶端傳送的資訊是:" + textField.getText()
+ "\n"); // 將文字框中資訊顯示在文字域中
textField.setText(""); // 將文字框清空
}
});
button.setText("發 送");
panel_1.add(button);
final JScrollPane scrollPane = new JScrollPane();
getContentPane().add(scrollPane, BorderLayout.CENTER);
textArea = new JTextArea();
scrollPane.setViewportView(textArea);
//
}
}
例項320:一個伺服器與多個客戶端通訊
本例項通過執行緒處理不同客戶傳送的資訊,當多個客戶連線到伺服器時,伺服器會為每一個客戶建立一個執行緒來處理接收到的資訊,而不會產生阻塞。
主要程式碼如下:
//在伺服器端建立執行緒類ServerThread,用於接收客戶端傳送的資訊以及處理客戶端的退出資訊,該執行緒類中run()方法的關鍵程式碼如下:
public void run(){
try{
if(socket!=null){
reder=new BufferedReader(new InputStreamReader(socket.getInputStream()));
int index=-1;
try{
while(true){
String line=reader.readLine();
try{
index=Integer.parseInt(line);
}catch(Exception ex){
index=-1;
}
if(line!=null){
ta_info.append("接收到客戶機發送的資訊:"+line+"\n");
}
}
}catch(Exception e){
if(line!=null){
vector.set(index,null);
ta_info.append("第"+(index+1)+"個客戶端已經退出。\n");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
//客戶端窗體的關閉事件,用於向伺服器傳送退出客戶的索引值
writer.println(String.valueOf(index));
//建立並啟動執行緒物件的關鍵程式碼
new ServerThread(socket).start();
例項321:客戶端一對多通訊
本例項主要是在伺服器端通過執行緒對客戶端傳送的資訊進行監聽,當有客戶端傳送資訊時,就會將該資訊傳送給其他已經登入到伺服器的客戶端,但是不會向傳送方傳送該資訊,在客戶端也通過執行緒來監聽伺服器轉發的資訊。
關鍵程式碼如下:
//在伺服器端建立執行緒類,用於接收客戶端傳送的資訊,並轉發,run()方法的關鍵程式碼:
while(true){
String info=in.readLine();
for(Socket s:vector){
if(s!=socket){
PrintWriter out=new PrintWriter(s.getOutputStream(),true);
out.println(info);
out.flush();
}
}
}
//在客戶端建立執行緒類,用於接收伺服器端傳送的客戶資訊,並顯示。run()方法的關鍵程式碼如下:
while(true){
String info=in.readLine();
ta_info.append(info+"\n");
if(info.equals("88")){
break;
}
}