1. 程式人生 > >java網絡編程基礎——TCP網絡編程一

java網絡編程基礎——TCP網絡編程一

字符串長度 其他 values real ane 實現 網絡通信 而在 機器

基於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網絡編程一