InetAddress:用于描述網路中的計算機,是對域名、IP地址的封裝
ServerSocket:服務端用的Socket,用于監聽服務端的指定埠,當客戶端連接到服務端的這個埠后,ServerSocket會為客戶端創建一個Socket并分配給這個客戶端,然后ServerSocket繼續監聽這個埠等待其他的客戶端請求連接
Socket:客戶端用的Socket以及服務端為每一個客戶端連接請求建立的Socket
簡單的Socket通信
服務端:
public class SocketServer {
public static void main(String... args) throws IOException {
//1 創建一個服務端的ServerSocket,指定一個埠,監聽此埠
ServerSocket serverSocket = new ServerSocket(12346);
//2 呼叫accept方法等待客戶端連接
System.out.println("呼叫accept()等待客戶端連接...");
Socket socket = serverSocket.accept();//accept()方法會一直阻塞,直到有客戶端連接到服務端
System.out.println("socket建立成功...");
//3 連接后獲取輸入流,輸出流
InputStream is = socket.getInputStream();//獲取輸入流,用于讀取客戶端發過來的資訊
OutputStream os = socket.getOutputStream();//獲取輸出流,用于向客戶端發送資訊
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String data = null;
while ((data = br.readLine()) != null) { //讀取客戶端發過來的資訊
System.out.println("客戶端發送過來的資訊: " + data);
}
//向客戶端發送資訊
// ...
socket.shutdownInput();
socket.close();
}
}
客戶端:
public class SocketClient {
public static void main(String ... args) throws IOException {
// 1 創建一個客戶端Socket 指定服務器的ip地址和埠
Socket socket = new Socket("127.0.0.1",12346);
// 2 獲取輸出流,向服務器發送資訊
OutputStream os = socket.getOutputStream();//向服務器寫資訊
PrintWriter pw = new PrintWriter(os);//將輸出流包裝成列印流
pw.write("Hello 服務器");//PrintWriter有緩沖佇列,write的資料并不會立馬發送出去,如果想立馬發送需要呼叫flush()方法
pw.flush();
socket.shutdownOutput();//關閉輸出流
socket.close();
}
}
以上就是簡單的Socket通信模型,任何其他復雜的Socket通信框架都是基于這個最基礎的Socket通信模型實作的,比如服務端實作多客戶連接,高并發,客戶端實作TCP心跳包機制等,
簡單版的聊天室
簡單版聊天室的服務端
/**
* 簡單聊天室的服務端
*/
public class ChartServer {
private ServerSocket server = null; //服務器的ServerSocket
private static final int PORT = 10065;
private List<Socket> mClients = new ArrayList<>();//保存所有的client
private ExecutorService mExec = null;
public static void main(String ... args){
new ChartServer();
}
public ChartServer(){
//開啟服務
System.out.println("服務器運行中,,,");
try {
server = new ServerSocket(PORT);
//創建一個執行緒池
mExec = Executors.newCachedThreadPool();
Socket client = null;
while (true){
System.out.println("等待客戶上門,,,");
client = server.accept();
System.out.println("有客戶來了,,, ,客戶是: " + client.getInetAddress());
mClients.add(client);
mExec.execute(new Service(client));
}
} catch (IOException e) {
e.printStackTrace();
}
}
class Service implements Runnable{
private Socket socket;
private BufferedReader br = null;
private String msg = "";
public Service(Socket socket){
this.socket = socket;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.sendMsg();
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendMsg(){//會給客戶端的訊息
int num = mClients.size();
OutputStream os = null;//向服務器寫資訊
try {
os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);//將輸出流包裝成列印流
pw.write("你好 你是第"+ num + "個客戶");
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
try {
if(( msg = br.readLine())!=null){
System.out.println("客戶端說:" + msg);
if("bye".equals(msg)){//應用自己定義的協議
socket.close();
break;
}else{
sendMsg();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
簡單版聊天室的客戶端app
/**
* 簡單聊天室的客戶端,沒有做心跳包機制
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Zero";
@BindView(R.id.tv_ip)
TextView tvIp;
@BindView(R.id.tv_show)
TextView tvShow;
@BindView(R.id.et_send)
EditText etSend;
//定義相關變數,完成初始化
private static final String HOST = "192.168.0.185";
// private static final String HOST = "169.254.177.122";
private static final int PORT = 10065;
private Socket socket = null;
private BufferedReader in = null;
private PrintWriter out = null;
private String content = "";
private StringBuilder sb = null;
private boolean writerFlag = false;
//這里引入了阻塞佇列LinkedBlockingQueue作為生產者執行緒和消費者執行緒的緩沖佇列,避免了生產者執行緒和消費者執行緒同步造成的復雜操作,
private LinkedBlockingQueue<String> msgs = new LinkedBlockingQueue<>();
//定義一個handler物件,用來重繪界面
public Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
sb.append(content);
tvShow.setText(sb.toString());
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tvIp.setText(NetworkUtils.getIPAddress(this));
sb = new StringBuilder();
//網路操作不能放在UI主執行緒 4.0
new Thread(){
public void run(){
try {
//和服務器連接
socket = new Socket(HOST, PORT);
//獲取輸入流,讀取服務器發送過來的資訊
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 獲取輸出流 向服務器寫資料
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
new Thread(readRunnable).start();
}
private Runnable readRunnable = new Runnable() {
@Override
public void run() {
while (true){
if(socket ==null)continue;
if(socket.isConnected() && !socket.isInputShutdown()){
try {
if((content = in.readLine()) !=null){
content += "\n";
handler.sendEmptyMessage(0x123);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};
private Runnable writeRunnable = new Runnable() {
@Override
public void run() {
while (true){
if(socket ==null)break;
if(socket.isConnected() && !socket.isOutputShutdown()){
try {
out.println(msgs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
out.flush();
}
}
}
};
@OnClick(R.id.btn_send)
public void onViewClicked() {
if(!writerFlag){
new Thread(writeRunnable).start();
}
writerFlag = true;
String msg = etSend.getText().toString();
try {
msgs.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
這里引入了阻塞佇列LinkedBlockingQueue作為生產者執行緒和消費者執行緒的緩沖佇列,避免了生產者執行緒和消費者執行緒同步造成的復雜操作,
長連接的實作
新增心跳包機制的原因
新增心跳包機制是為了實作TCP長連接,
TCP長連接被斷開主要有幾個原因:
- TCP長連接所在行程被殺死,解決方案:行程保活
- NAT超時,解決方案:新增心跳包機制
- 網路狀態發生變化,解決方案:斷線重連機制
- 其他不可抗因素
其中,TCP長連接所在行程被作業系統殺死和NAT超時是兩個完全不同的原因,前者是手機作業系統對于后臺行程會殺死的機制,后者是網路運營商(比如移動,電信等)針對NAT超時的處理機制,








心跳包機制的具體實作
- 一般是把發送心跳包的Socket放到HeartbeatService(繼承Service)里面,即Socket作為HeartbeatService的成員變數,
- HeartbeatService單獨運行在push子行程,這樣當app退出后主行程已經kill掉,但是push子行程可以繼續運行
- app主行程與HeartbeatService行程通信:app主行程通過Binder與HeartbeatService進行通信
- HeartbeatService行程與app主行程通信:HeartbeatService的Socket收到服務端發來的訊息之后通過Broadcast廣播把訊息發送到app主行程
- 發送心跳包的時間間隔可能不準(包括Handler發送延遲訊息和rxjava可能都有這個問題),解決方法是可以使用Android的鬧鐘服務AlarmManager
比較知名的長連接框架
騰訊的Mars
美團的Shark長連接框架
一般互聯網大廠都有自己的長連接框架
更多相關內容可以參考:
Android架構之長連接技術
android socket通信框架_阿里P8典藏:Java多執行緒與Socket實戰微服務框架筆記
Android-OkSocket一個Android輕量級Socket通訊框架
青春互撩——詳解基于Socket通信的聊天軟體開發(附專案原始碼)
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/257490.html
標籤:其他
下一篇:ctfshow-misc-WP
