java網絡編程基礎——TCP網絡編程一
基於TCP協議的網絡編程
TCP/IP協議是一種可靠的網絡協議,它的通信的兩端各自建立一個Socket,從而在通信的兩端之間形成網絡虛擬鏈路。
Java使用Socket對象來代表兩端的通信端口,並通過Socket產生IO流來進行網絡通信。
1、ServerSocket
java中能接收其他通信實體連接請求的類是ServerSocket,他會監聽來自客戶端Socket連接,如果沒有連接,它將一直處於等待狀態。
ServerSocket常用方法:
Socket accept():如果接收到客戶端Socket的連接請求,返回一個與客戶端Socket對應的Socket(每個TCP連接有兩個Socket),否則一直處於等待狀態,線程被阻塞。
ServerSocket構造器:
ServerSocket(int port):指定端口創建ServerSocket.
ServerSocket(int port ,int backlog):增加一個用來改變連接隊列長度的參數backlog.
ServerSocket(int port ,int backlog ,InetAddress localAddr):機器有多IP時候,可以指定IP創建ServerSocket.
2、使用Socket進行通信
客戶端可以使用Socket的構造器連接到指定服務器,Socket有兩個構造器:
Socket(InetAddress/String remoteAddress,int port):創建連接到指定遠程主機、遠程端口的Socket,該構造器沒有指定本地地址、本地端口,默認使用本機默認ip,默認使用系統動態分配的端口。
Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort):穿件Socket帶有本地IP和port
package net; import java.io.DataInputStream; import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; public class SocketServer {public static void main(String[] args) throws IOException { //定義服務端口號 int port = 7000; //獲取ServerSocket對象 ServerSocket serverSocket = new ServerSocket(port); while(true) { //監聽來自客戶端的連接 Socket socket =serverSocket.accept(); PrintStream pis = new PrintStream(socket.getOutputStream()); pis.print("Hi:來自服務端的問候"); pis.close(); socket.close(); } } } package net; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import java.net.UnknownHostException; public class SocketClient { public static void main(String[] args) { //定義客戶端端口 int port = 7000; //獲取Socket Socket socket = null; BufferedReader br = null; try { socket = new Socket("localhost",port); //設置10s超時 socket.setSoTimeout(10000); br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //獲取數據 System.out.println(br.readLine()); } catch (Exception e) { e.printStackTrace(); }finally { try { br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
3、加入多線程
上面程序只是簡單的實現了Server和Client的簡單通信:服務器接收到客戶端的連接後想客戶端發送了一個字符串,客戶端讀取字符串後就退出了。
實際應用中客戶端可能需要跟服務器保持長時間通信,在使用傳統的BufferReader的readLine方法讀取數據時,在該方法返回成功之前,線程被阻塞,考慮到此點,所以服務器端應該為每個Socket單獨啟動一個線程,負責與客戶端通信。
下面程序實現了命令行聊天大廳功能(參考瘋狂java講義)
服務端:
package chatServer; /** * 協議字符 * @author rdb * */ public interface ChatProtocol { //協議字符串長度 int PROTOCOL_LEN = 2; //公發前後端字符 String MSG_ROUND = "?△"; //用戶前後字符 String USER_ROUND = "△▽"; //登錄成功表示 String LOGIN_SUCCESS = "1"; //用戶重復標識 String NAME_REP = "-1" ; //私發用戶標識 String PRIVATE_ROUND = "◆◆" ; String SPLIT_SIGN = "◆" ; } package chatServer; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * * ChaMap:保存用戶和對應的輸出流,一一對應,不允許出現value重復 * @author rdb * @param <K> * @param <V> */ public class ChatMap<K,V> { //創建一個線程安全的HashMap public Map<K,V> map = Collections.synchronizedMap(new HashMap<>()); //獲取value的set集合 public synchronized Set<V> valueSet(){ Set<V> set = new HashSet<>(); map.forEach((key,value) -> set.add(value)); return set ; } //實現put方法,不允許出現重復value public synchronized V put(K key,V value) { for(V v : valueSet()) { if(v.equals(value) && v.hashCode() == value.hashCode()) { throw new RuntimeException("ChatMap中不允許有重復value"); } } return map.put(key, value); } //根據value查找key public synchronized K getKeyByValue(V value) { for(K k : map.keySet()) { if(map.get(k) == value && map.get(k).equals(value)) { return k; } } return null; } //根據value刪除 public synchronized void removeByValue(V value){ for(K k : map.keySet()) { if(map.get(k) == value) { map.remove(k); break; } } } } package chatServer; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; public class ChatServer { private static final int SERVER_PORT = 30000 ; //用戶保存客戶端用戶和對應socket輸出流 public static ChatMap<String, PrintStream> chatMap = new ChatMap<>(); public void init() { try { ServerSocket serverSocket = new ServerSocket(SERVER_PORT); while(true) { Socket socket = serverSocket.accept(); System.out.println("******"); //啟動對應的線程處理對應的客戶端 new Thread(new ServerThread(socket)).start(); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { ChatServer chatServer = new ChatServer(); chatServer.init(); } } package chatServer; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; public class ServerThread implements Runnable { private Socket socket = null; private BufferedReader br = null; private PrintStream ps = null; public ServerThread(Socket socket) { this.socket = socket; } @Override public void run() { try { // 獲取socket對應的輸入流 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 獲取socket對應的輸出流 ps = new PrintStream(socket.getOutputStream()); // 獲取客戶端的消息 String line = null; while ((line = br.readLine()) != null) { // 判斷信息類型 // 用戶登錄 if (line.startsWith(ChatProtocol.USER_ROUND) && line.endsWith(ChatProtocol.USER_ROUND)) { String userName = getRealMsg(line); // 如果包含該用戶,返回重復登錄 if (ChatServer.chatMap.map.containsKey(userName)) { System.out.println("重復"); ps.println(ChatProtocol.NAME_REP); } else { System.out.println("成功"); ps.println(ChatProtocol.LOGIN_SUCCESS); ChatServer.chatMap.put(userName, ps); } } // 私聊 else if (line.startsWith(ChatProtocol.PRIVATE_ROUND) && line.endsWith(ChatProtocol.PRIVATE_ROUND)) { String userAndMsg = getRealMsg(line); // 私聊時 SPLIT_SIGN前是私聊對象,後面是私聊信息 String user = userAndMsg.split(ChatProtocol.SPLIT_SIGN)[0]; String msg = userAndMsg.split(ChatProtocol.SPLIT_SIGN)[1]; // 向對應的客戶端發送消息 ChatServer.chatMap.map.get(user). println(ChatServer.chatMap.getKeyByValue(ps) + "悄悄的對你說:" + msg); } // 公聊 else { String msg = getRealMsg(line); // 向每個用戶發送消息 ChatServer.chatMap.map.forEach((key, value) -> value.println(ChatServer.chatMap.getKeyByValue(ps) + "說:" + msg)); } } } // 捕獲到異常表示對應的Socket的客戶端出現問題,在map中移除該客戶端信息 catch (IOException e) { ChatServer.chatMap.removeByValue(ps); System.out.println(ChatServer.chatMap.map.size()); // 關閉相關資源 try { if (br != null) { br.close(); } if (ps != null) { ps.close(); } if (socket != null) { socket.close(); } } catch (IOException e1) { e1.printStackTrace(); } } } private String getRealMsg(String msg) { return msg.substring(ChatProtocol.PROTOCOL_LEN, msg.length() - ChatProtocol.PROTOCOL_LEN); } }
客戶端:
package charClient; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import javax.swing.JOptionPane; /** * 客戶端 * @author rdb * */ public class ChatClient { private static final int SERVER_PORT = 30000; private static final String SERVER_IP = "127.0.0.1"; private Socket socket = null ; private BufferedReader br = null; private PrintStream ps = null ; //鍵盤輸入 private BufferedReader keyIn = null ; public void init() { try { socket = new Socket(SERVER_IP,SERVER_PORT); br = new BufferedReader(new InputStreamReader(socket.getInputStream())); ps = new PrintStream(socket.getOutputStream()); keyIn = new BufferedReader(new InputStreamReader(System.in)); String tip = ""; //要求輸入用戶名登錄 while(true) { String userName =JOptionPane.showInputDialog(tip + "輸入用戶名"); //將用戶名發送到服務器 ps.println(ChatProtocol.USER_ROUND + userName + ChatProtocol.USER_ROUND); //獲取服務器返回的消息 String result = br.readLine(); if(result.equals(ChatProtocol.NAME_REP)) { System.out.println("用戶名重復,請重試"); continue; } if(result.equals(ChatProtocol.LOGIN_SUCCESS)) { break ; } } } catch (IOException e) { System.out.println("網絡異常,請重新登錄"); closeRs(); System.exit(1); } //啟動對應socket的讀取服務器消息的線程 new Thread(new ClientThread(br)).start(); String line = null; try { while((line = keyIn.readLine()) != null) { //如果發送信息中有:號,且以//開頭,則認為是發送私聊信息://張三:你最近怎麽樣 if(line.indexOf(":") >0 && line.startsWith("||")) { line = line.substring(2); System.out.println(ChatProtocol.PRIVATE_ROUND+line.split(":")[0] +ChatProtocol.SPLIT_SIGN+line.split(":")[1]+ChatProtocol.PRIVATE_ROUND); ps.println(ChatProtocol.PRIVATE_ROUND+line.split(":")[0] +ChatProtocol.SPLIT_SIGN+line.split(":")[1]+ChatProtocol.PRIVATE_ROUND); }else { ps.println(ChatProtocol.MSG_ROUND + line + ChatProtocol.MSG_ROUND); } } } catch (IOException e) { System.out.println("網絡異常,請重新登錄"); closeRs(); System.exit(1); } } private void closeRs() { try { if(br != null) { br.close(); } if(ps != null) { ps.close(); } if(socket != null) { socket.close(); } if(keyIn != null) { keyIn.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { ChatClient chatClient = new ChatClient(); chatClient.init(); } } package charClient; import java.io.BufferedReader; import java.io.IOException; public class ClientThread implements Runnable{ private BufferedReader br = null; public ClientThread(BufferedReader br) { this.br = br ; } @Override public void run() { String line = null; try { while((line = br.readLine()) != null){ System.out.println(line); } } catch (IOException e) { e.printStackTrace(); }finally { try { if(br != null) { br.close(); } } catch (IOException e) { e.printStackTrace(); } } } } package charClient; /** * 協議字符 * @author rdb * */ public interface ChatProtocol { //協議字符串長度 int PROTOCOL_LEN = 2; //公發前後端字符 String MSG_ROUND = "?△"; //用戶前後字符 String USER_ROUND = "△▽"; //登錄成功表示 String LOGIN_SUCCESS = "1"; //用戶重復標識 String NAME_REP = "-1" ; //私發用戶標識 String PRIVATE_ROUND = "◆◆" ; String SPLIT_SIGN = "◆" ; }
java網絡編程基礎——TCP網絡編程一