dsg_03通信
通信
- TCP/IP
- TCP/IP協(xié)議集的主要協(xié)議
- OSI(開放系統(tǒng)互連參考模型)
- TCP協(xié)議建立連接的三次握手
- 通信的分類
- UDP通信
- send端
- receive端
- 設(shè)計報頭
- 案例(屏幕廣播,紅蜘蛛)
- 需要掌握的其他技術(shù)
- 代碼實現(xiàn)
- 教師端
- 學(xué)生端
- TCP通信
- QQ案例
- 設(shè)計報頭
- 需要掌握的其他技術(shù)
- QQServer
- QQClient
TCP/IP
- TCP:傳輸控制協(xié)議
- IP:網(wǎng)絡(luò)協(xié)議
TCP/IP協(xié)議集的主要協(xié)議
| IP | 數(shù)據(jù)報服務(wù) | 3 |
| ICMP | 差錯和控制 | 3 |
| ARP | 互聯(lián)網(wǎng)地址 -> 物理地址 | 3 |
| RARP | 物理地址 -> 互聯(lián)網(wǎng)地址 | 3 |
| TCP | 可靠流服務(wù) | 4 |
| FTP | 文件傳送 | 5~7 |
| TELNET | 終端仿真 | 5~7 |
| DNS | 域名 -> 互聯(lián)網(wǎng)地址 | 5~7 |
OSI(開放系統(tǒng)互連參考模型)
OSI分為七層結(jié)構(gòu),分別是:
- 物理層:規(guī)范有關(guān)傳輸介質(zhì)的特性標(biāo)準(zhǔn)
- 數(shù)據(jù)鏈路層:定義了在單個鏈路上如何傳輸數(shù)據(jù):SPX
- 網(wǎng)絡(luò)層:對端到端的包傳輸進(jìn)行定義,定義了能夠標(biāo)識所有結(jié)點的邏輯地址,定義了路由實現(xiàn)的方式和學(xué)習(xí)的方式,定義了如何將一個包分解成更小的包的分段方法:IP,IPX
- 傳輸層:UDP(無連接,用于廣播,無固定路由),TCP(面向連接,三次握手,身份識別)
- 會話層:
- 表示層:
- 應(yīng)用層:http,https,ftp,SMTP
TCP協(xié)議建立連接的三次握手
通信的分類
- 單工:單項通信,只能收不能發(fā)
- 雙工:雙向通信,既可以收也可以發(fā)
- 半雙工:同一時刻,只能收或者只能發(fā)
- 全雙工:同一時刻,既可以收也可以發(fā)
UDP通信
UPD通信一般用于屏幕廣播,UDP通信的特點是發(fā)完就結(jié)束,不管接收端收不收到,每個發(fā)送的數(shù)據(jù)有64k上限,發(fā)送的包無序
send端
import java.io.ByteArrayInputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress;/*** Create Time: 2020.06.07 09:38* 發(fā)送端**/ public class Sender {public static void main(String[] args) throws Exception {//數(shù)據(jù)報套接字,指定發(fā)送端的發(fā)送端口9999DatagramSocket socket = new DatagramSocket(9999);String data = "6月6日0—24時,31個省(自治區(qū)、直轄市)和新疆生產(chǎn)建設(shè)兵團(tuán)報告新增確診病例6例,其中境外輸入病例5例(陜西2例,天津1例,福建1例,廣東1例),本土病例1例(在海南);無新增死亡病例;新增疑似病例2例,均為境外輸入病例(均在上海)。當(dāng)日新增治愈出院病例3例,解除醫(yī)學(xué)觀察的密切接觸者633人,重癥病例減少1例。境外輸入現(xiàn)有確診病例66例(無重癥病例),現(xiàn)有疑似病例3例。累計確診病例1776例,累計治愈出院病例1710例,無死亡病例。截至6月6日24時,據(jù)31個省(自治區(qū)、直轄市)和新疆生產(chǎn)建設(shè)兵團(tuán)報告,現(xiàn)有確診病例70例(無重癥病例),累計治愈出院病例78332例,累計死亡病例4634例,累計報告確診病例83036例,現(xiàn)有疑似病例3例。累計追蹤到密切接觸者746744人,尚在醫(yī)學(xué)觀察的密切接觸者3389人。31個省(自治區(qū)、直轄市)和新疆生產(chǎn)建設(shè)兵團(tuán)報告新增無癥狀感染者5例(境外輸入4例);當(dāng)日轉(zhuǎn)為確診病例1例;當(dāng)日解除醫(yī)學(xué)觀察25例(境外輸入2例);尚在醫(yī)學(xué)觀察無癥狀感染者236例(境外輸入43例)。累計收到港澳臺地區(qū)通報確診病例1593例。其中,香港特別行政區(qū)1105例(出院1048例,死亡4例),澳門特別行政區(qū)45例(出院45例),臺灣地區(qū)443例(出院429例,死亡7例)。";ByteArrayInputStream bis = new ByteArrayInputStream(data.getBytes());//構(gòu)造數(shù)據(jù)緩沖區(qū),形成數(shù)據(jù)報包byte[] buf = new byte[1024];int len = 0;//192.168.12.255 255指給192.168.12這個網(wǎng)段的所有ip進(jìn)行廣播發(fā)送InetSocketAddress address = new InetSocketAddress("localhost", 8888);while ((len = bis.read(buf)) != -1){//數(shù)據(jù)報包,參數(shù):緩沖區(qū),長度DatagramPacket packet = new DatagramPacket(buf,len);//數(shù)據(jù)報包中包含接收端的地址和端口packet.setSocketAddress(address);socket.send(packet);}} }receive端
import java.net.DatagramPacket; import java.net.DatagramSocket;/*** Create Time: 2020.06.07 09:50* 接收方**/ public class Receiver {public static void main(String[] args) throws Exception {DatagramSocket socket = new DatagramSocket(8888);//數(shù)據(jù)接收緩沖區(qū)byte[] bytes = new byte[1024];String str = "";while(true){//接收方需要先創(chuàng)建一個空包去接收數(shù)據(jù)DatagramPacket packet = new DatagramPacket(bytes,bytes.length);socket.receive(packet);int dataLen = packet.getLength();str = new String(bytes, 0, dataLen);System.out.println("收到了:"+str);}} }設(shè)計報頭
報頭需要的元素:
- 幀的id,8個字節(jié),可以用時間戳表示,一幀代表一組數(shù)據(jù)
- 幀單元個數(shù),1個字節(jié)
- 幀單元編號,1個字節(jié),編號是連續(xù)的,幀單元是一組數(shù)據(jù)被切割成若干個包,每個包就是一個幀單元,用編號來確定包的順序
- 幀單元數(shù)據(jù)長度,4個字節(jié)
- 幀數(shù)據(jù),最多60K
案例(屏幕廣播,紅蜘蛛)
需要掌握的其他技術(shù)
截屏:
import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.FileOutputStream;Robot robot = new Robot();//獲取屏幕的分辨率int width = Toolkit.getDefaultToolkit().getScreenSize().width;int height = Toolkit.getDefaultToolkit().getScreenSize().height;//截屏,參數(shù):坐標(biāo)+分辨率BufferedImage image = robot.createScreenCapture(new Rectangle(0, 0, width, height));//保存圖片,參數(shù):BufferedImage,圖片格式,圖片路徑輸出流ImageIO.write(image,"jpg",new FileOutputStream("d:/1.jpg"));字節(jié)數(shù)組與長整形的轉(zhuǎn)換:
/*** 將長整形轉(zhuǎn)換成字節(jié)數(shù)組* @param i*/public static byte[] long2Bytes(long i){byte[] arr = new byte[8];arr[0] = (byte)i;arr[1] = (byte)(i >> 8);arr[2] = (byte)(i >> 16);arr[3] = (byte)(i >> 24);arr[4] = (byte)(i >> 32);arr[5] = (byte)(i >> 40);arr[6] = (byte)(i >> 48);arr[7] = (byte)(i >> 56);return arr;}/*** 將字節(jié)數(shù)組轉(zhuǎn)換成長整形*/public static long bytes2long(byte[] arr){long i0 = (long)(arr[0] & 0xFF);//這個long必須要放在里面,要先將其轉(zhuǎn)為long再移動字節(jié),不然是無法滿足那么多位讓你去移動的long i1 = ((long)(arr[1] & 0xFF) << 8);long i2 = ((long)(arr[2] & 0xFF) << 16);long i3 = ((long)(arr[3] & 0xFF) << 24);long i4 = ((long)(arr[4] & 0xFF) << 32);long i5 = ((long)(arr[5] & 0xFF) << 40);long i6 = ((long)(arr[6] & 0xFF) << 48);long i7 = ((long)(arr[7] & 0xFF) << 56);return i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7;}壓縮字節(jié)與解壓縮字節(jié):
/*** 壓縮字節(jié)*/public static byte[] zipData(byte[] zipData){try{ByteArrayOutputStream baos = new ByteArrayOutputStream();ZipOutputStream zos = new ZipOutputStream(baos);zos.putNextEntry(new ZipEntry("one"));zos.write(zipData);zos.close();return baos.toByteArray();}catch (Exception e){e.printStackTrace();}return null;}/*** 解壓縮*/public static byte[] unzipData(byte[] rawData){try {ByteArrayInputStream bais = new ByteArrayInputStream(rawData);ZipInputStream zis = new ZipInputStream(bais);ByteArrayOutputStream baos = new ByteArrayOutputStream();zis.getNextEntry();byte[] buf = new byte[1024];int len = 0;while ((len = zis.read(buf)) != -1){baos.write(buf,0,len);}zis.close();bais.close();baos.close();return baos.toByteArray();}catch (Exception e){e.printStackTrace();}return null;}代碼實現(xiàn)
教師端
import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;/*** Create Time: 2020.06.07 14:57** 屏幕廣播* 發(fā)送方**/ public class SenderPG {//每個幀單元的最大值60Kprivate static int frame_UNIT_MAX = 60 * 1024;//截屏需要的機(jī)器人類private static Robot robot = null;//屏幕的分辨率private static int width = 0;private static int height = 0;//UDP廣播需要的連接private static DatagramSocket socket = null;static {try {robot = new Robot();//獲取屏幕分辨率width = Toolkit.getDefaultToolkit().getScreenSize().width;height = Toolkit.getDefaultToolkit().getScreenSize().height;//設(shè)置發(fā)送端的端口為8888socket = new DatagramSocket(8888);} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) throws Exception {while (true){sendOneScreenData();}}/*** 發(fā)送一屛數(shù)據(jù)* 1. 截屏* 2. 切屏(切屏之前先壓縮截屏數(shù)據(jù),減小網(wǎng)絡(luò)傳輸?shù)膲毫?#xff09;* 3. 組裝UdpPackage (8 + 1 + 1 + 4 + n)* 4. 發(fā)送*/private static void sendOneScreenData() {byte[] bytes = null;List<Map<String,Object>> maps = null;try {//1. 截屏bytes = captureScreen();//2. 切屏if(bytes != null && bytes.length > 0){//切屏之前先壓縮字節(jié),將網(wǎng)絡(luò)傳輸?shù)膲毫D(zhuǎn)移到cpu上maps = splitFrame(Util.zipData(bytes));}//發(fā)送if(maps != null && maps.size() > 0){sendUnits(maps);}} catch (Exception e) {e.printStackTrace();}}/*** 發(fā)送數(shù)據(jù)* @param maps*/private static void sendUnits(List<Map<String, Object>> maps) {try {//客戶端的地址是本地地址,端口號9999InetSocketAddress address = new InetSocketAddress("localhost", 9999);for(Map<String,Object> map:maps){//組裝幀單元數(shù)據(jù)到字節(jié)數(shù)組byte[] bytes = propFrameUnit(map);DatagramPacket packet = new DatagramPacket(bytes,bytes.length);packet.setSocketAddress(address);socket.send(packet);}} catch (Exception e) {e.printStackTrace();}}/*** 組裝幀單元數(shù)據(jù)到字節(jié)數(shù)組* @param map* @return*/private static byte[] propFrameUnit(Map<String, Object> map) {byte[] bytes = new byte[14 + Integer.parseInt(BlankNull(map.get("frameLength")))];//framIdbyte[] frameIds = Util.long2Bytes((long) map.get("frameId"));System.arraycopy(frameIds,0,bytes,0,frameIds.length);//frameCountbyte frameCount = (byte) map.get("frameCount");bytes[frameIds.length] = frameCount;//frameNobyte frameNo = (byte) map.get("frameNo");bytes[frameIds.length + 1] = frameNo;//frameLengthbyte[] frameLengths = Util.int2Bytes((int) map.get("frameLength"));System.arraycopy(frameLengths,0,bytes,frameIds.length + 2,frameLengths.length);//frameDatabyte[] frameData = (byte[]) map.get("frameData");System.arraycopy(frameData,0,bytes,frameIds.length + 2 + frameLengths.length,frameData.length);return bytes;}/*** 非空判斷* @param obj* @return*/private static String BlankNull(Object obj) {if(obj != null){return obj.toString();}else{return "";}}/*** 切屏* @param bytes* @return*/private static List<Map<String, Object>> splitFrame(byte[] bytes) {List<Map<String, Object>> resultList = new ArrayList<>();long frameId = System.currentTimeMillis();//判斷數(shù)據(jù)是否大于60Kif(bytes.length > frame_UNIT_MAX){//切//求需要切除的個數(shù)byte count = (byte)(bytes.length / frame_UNIT_MAX);int ys = bytes.length % frame_UNIT_MAX;if( ys != 0) {count ++;}for(byte i = 0; i < count;i++){//map中包含:幀id(每一屏的數(shù)據(jù)為一幀,取當(dāng)前的時間戳,8個字節(jié)),幀單元的數(shù)量(1個字節(jié)),幀單元的編號(1個字節(jié)),幀單元數(shù)據(jù)的大小(除了最后一個,前面的幀單元都是最大值60K),幀單元的數(shù)據(jù)Map<String, Object> map = new HashMap<>();map.put("frameId",frameId);map.put("frameCount",count);map.put("frameNo",i);if(i == (count - 1) && ys != 0){map.put("frameLength", ys);byte[] data = new byte[ys];System.arraycopy(bytes,i*frame_UNIT_MAX,data,0,ys);map.put("frameData",data);}else{map.put("frameLength",frame_UNIT_MAX);byte[] data = new byte[frame_UNIT_MAX];System.arraycopy(bytes,i*frame_UNIT_MAX,data,0,frame_UNIT_MAX);map.put("frameData",data);}resultList.add(map);}}else{//不切Map<String, Object> map = new HashMap<>();map.put("frameId",frameId);map.put("frameCount",1);map.put("frameNo",0);map.put("frameLength",bytes.length);map.put("frameData",bytes);resultList.add(map);}return resultList;}/*** 截屏* @return*/private static byte[] captureScreen() throws IOException {BufferedImage image = robot.createScreenCapture(new Rectangle(0, 0, width, height));ByteArrayOutputStream baos = new ByteArrayOutputStream();ImageIO.write(image,"jpg",baos);return baos.toByteArray();}學(xué)生端
import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; import java.util.*;/*** Create Time: 2020.06.07 14:58* * 屏幕廣播* 接收方**/ //使用線程是為了防止接收端占用主線程導(dǎo)致線程阻塞阻礙其他項目的運行 public class ReceiverPG extends Thread {private DatagramSocket socket = null;private DatagramPacket packet = null;//一個幀單元數(shù)據(jù)的最大長度private final static int frame_UNIT_MAX = 60 * 1024;//幀單元緩沖區(qū),數(shù)據(jù)最大長度+報頭長度private byte[] buf = new byte[frame_UNIT_MAX + 14];public ReceiverPG() {try {//設(shè)置接收端的端口為9999this.socket = new DatagramSocket(9999);this.packet = new DatagramPacket(buf,buf.length);} catch (SocketException e) {e.printStackTrace();}}@Overridepublic void run() {receiver();}public void receiver(){Map<Integer, Map<String, Object>> resultMap = new HashMap<>();//記錄當(dāng)前獲取的幀單元的idlong currentFrameId = 0L;while (true){try {socket.receive(packet);//整合幀單元Map<String, Object> map = propFrameUnit();long frameId = Long.parseLong(Util.BlankNull(map.get("frameId")));int frameCount = Integer.parseInt(Util.BlankNull(map.get("frameCount")));int frameNo = Integer.parseInt(Util.BlankNull(map.get("frameNo")));//判斷當(dāng)前接收的對象是否屬于同一幀if( frameId == currentFrameId){//是就存儲resultMap.put(frameNo,map);}else if(frameId > currentFrameId){//如果不是,取最新的幀,放棄原來的幀單元currentFrameId = frameId;resultMap.clear();resultMap.put(frameNo,map);}//判斷同一幀數(shù)據(jù)是否收集完畢,完畢則組裝數(shù)據(jù),還原截圖if(resultMap.keySet().size() == frameCount){//組裝截屏數(shù)據(jù)propData(resultMap);resultMap.clear();}} catch (Exception e) {e.printStackTrace();}}}/*** 組裝圖片數(shù)據(jù)* @param maps* @return*/private BufferedImage propData(Map<Integer, Map<String, Object>> maps) {BufferedImage bufferedImage = null;try {String pictureName = (new Date()).getTime() + ".jpg";System.out.println("保存一張圖片"+pictureName);FileOutputStream fos = new FileOutputStream("D:\\aa\\pg\\" + pictureName);ByteArrayOutputStream baos = new ByteArrayOutputStream();for(int i = 0; i < maps.size();i++){byte[] bytes = (byte[]) maps.get(i).get("frameData");baos.write(bytes);}//拿到截屏字節(jié),發(fā)送端壓縮過,所以接收端要解壓fos.write(Util.unzipData(baos.toByteArray()));baos.close();fos.close();} catch (Exception e) {e.printStackTrace();}return bufferedImage;}/*** 整合幀單元* @return*/private Map<String, Object> propFrameUnit() {Map<String, Object> map = new HashMap<>();byte[] frameIds = new byte[8];System.arraycopy(buf,0,frameIds,0,8);long frameId = Util.bytes2long(frameIds);byte frameCount = buf[8];byte frameNo = buf[9];byte[] frameLengths = new byte[4];System.arraycopy(buf,10,frameLengths,0,4);int frameLength = Util.Bytes2int(frameLengths);byte[] frameData = new byte[frameLength];System.arraycopy(buf,14,frameData,0,frameLength);map.put("frameId",frameId);map.put("frameCount",frameCount);map.put("frameNo",frameNo);map.put("frameLength",frameLength);map.put("frameData",frameData);return map;}public static void main(String[] args) {new ReceiverPG().start();} }TCP通信
使用TCP通信最常見的應(yīng)用就是QQ
QQ案例
設(shè)計報頭
報頭需要的元素:
- 消息類型,4個字節(jié),(單發(fā)、群發(fā)、刷新好友列表)
- 接收方信息長度
- 接收方信息
- 消息長度,4個字節(jié)
- 消內(nèi)容
- 發(fā)送方信息長度
- 發(fā)送方信息
需要掌握的其他技術(shù)
java串行化
java串行化是指將一個對象拆分為字節(jié)數(shù)組,然后轉(zhuǎn)為流的形式,可以拆分為字節(jié)數(shù)組的對象必須實現(xiàn) Serializable接口
QQServer
import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket;/*** Create Time: 2020.06.14 09:26** QQ服務(wù)器**/ public class QQServer {//維護(hù)所有的客戶端集合//key是ip+端口號private static Map<String,Socket> socketMap = new HashMap<>();public static Map<String, Socket> getSocketMap() {return socketMap;}public static void main(String[] args) {try {//設(shè)置QQ服務(wù)器端口為8888ServerSocket ss = new ServerSocket(8888);while (true){//阻塞Socket socket = ss.accept();InetSocketAddress isa = (InetSocketAddress)socket.getRemoteSocketAddress();String ip = isa.getAddress().getHostAddress();int port = isa.getPort();String key = ip + ":" + port;//將上線的客戶端都維護(hù)到這個集合中去socketMap.put(key,socket);//處理客戶端來的消息,這里必須要開分線程,因為客戶端不可能只有一個//參數(shù)當(dāng)前發(fā)送消息的客戶端的socket和他對應(yīng)的key(new CommThread(socket,key)).start();}}catch (Exception e){e.printStackTrace();}}//服務(wù)器向客戶端發(fā)送消息public static void sendMessage(Socket socket, byte[] bytes){try {OutputStream os = socket.getOutputStream();os.write(bytes);//清理os.flush();} catch (IOException e) {e.printStackTrace();}} } import java.io.InputStream; import java.net.Socket;/*** Create Time: 2020.06.14 09:52***/ public class CommThread extends Thread {private Socket socket = null;private String sender = null;public CommThread(Socket socket,String sender){this.socket = socket;this.sender = sender;}@Overridepublic void run() {try {while (true){InputStream is = socket.getInputStream();//解析客戶端發(fā)送過來的消息并發(fā)送MessageFactory.serverParseMessage(is,sender);}} catch (Exception e) {e.printStackTrace();}} } /*** 服務(wù)器解析消息* @param is* is中包含消息類型、(接收方信息長度、接收方信息)、消息長度、消息內(nèi)容、發(fā)送方信息長度、發(fā)送方信息*/public static void serverParseMessage(InputStream is,String sender){try {if(is.available() > 0){byte[] xxBytes = new byte[is.available()];is.read(xxBytes);byte[] senderBytes = sender.getBytes();byte[] senderLengthBytes = Util.int2Bytes(senderBytes.length);//讀取消息類型byte[] xxlxBytes = new byte[4];System.arraycopy(xxBytes,0,xxlxBytes,0,4);int xxlx = Util.Bytes2int(xxlxBytes);switch (xxlx){/*** 0:私聊 需要解析接收端信息,以便發(fā)給接收端,并同時附加發(fā)送端信息* 1:群聊 ,附加發(fā)送方信息,遍歷好友列表,并發(fā)送* 2:刷新好友列表 //上線和下線*/case 0: {//讀取接收端用戶信息的長度byte[] receiverYHXXCDBytes = new byte[4];System.arraycopy(xxBytes,4,receiverYHXXCDBytes,0,4);int receiverYHXXLength = Util.Bytes2int(receiverYHXXCDBytes);//讀取接收端用戶信息byte[] receiverBytes = new byte[receiverYHXXLength];System.arraycopy(xxBytes,8,receiverBytes,0,receiverYHXXLength);String receiver = new String(receiverBytes, 0, receiverYHXXLength);Socket socket = QQServer.getSocketMap().get(receiver);//附加發(fā)送方位發(fā)送方信息byte[] bytes = new byte[xxBytes.length + 4 + senderBytes.length];System.arraycopy(xxBytes,0,bytes,0,xxBytes.length);System.arraycopy(senderLengthBytes,0,bytes,xxBytes.length,4);System.arraycopy(senderBytes,0,bytes,xxBytes.length + 4,senderBytes.length);if (socket != null) {QQServer.sendMessage(socket, bytes);}break;}case 1: {byte[] bytes = new byte[xxBytes.length + 4 + senderBytes.length];System.arraycopy(xxBytes,0,bytes,0,xxBytes.length);System.arraycopy(senderLengthBytes,0,bytes,xxBytes.length,4);System.arraycopy(senderBytes,0,bytes,xxBytes.length + 4,senderBytes.length);for (Socket client : QQServer.getSocketMap().values()) {QQServer.sendMessage(client, bytes);}break;}case 2:{List<String> clients = new ArrayList<>(QQServer.getSocketMap().keySet());byte[] bytes = Util.serializeObject((Serializable) clients);//將消息類型與好友列表組裝起來byte[] message = new byte[4 + bytes.length];System.arraycopy(xxlxBytes,0,message,0,4);System.arraycopy(bytes,0,message,4,bytes.length);for(Socket client:QQServer.getSocketMap().values()){QQServer.sendMessage(client,message);}break;}}}} catch (Exception e) {e.printStackTrace();}}QQClient
import java.io.IOException; import java.io.OutputStream; import java.net.Socket;/*** Create Time: 2020.06.14 09:26** QQ客戶端**/ public class QQClient {//好友列表private static List<String> friends = null;public static void setFriends(List<String> friends) {QQClient.friends = friends;}public static void main(String[] args) throws Exception {Socket socket = new Socket("localhost", 8888);//獲取服務(wù)端發(fā)送過來的消息,這里也必須開啟線程,因為客戶端需要發(fā)送消息,不開線程這里就會堵塞,客戶端無法發(fā)送消息new Thread(new CommThread(socket)).start();Map<String, String> map = new HashMap<>();//上線,刷新好友列表map.put("xxlx","2");byte[] bytes = MessageFactory.fzxx(map);//向服務(wù)器發(fā)送消息sendMessage(socket,bytes);//請選擇發(fā)送方式System.out.println("0:單發(fā);1:群發(fā)");Scanner scanner = new Scanner(System.in);String xxlx = scanner.nextLine();String fsdx = "";if(xxlx.equals("0")){//單發(fā)指定發(fā)送對象System.out.println("請選擇發(fā)送對象");String fsdxIndex = scanner.nextLine();fsdx = friends.get(Integer.parseInt(fsdxIndex) - 1);}//輸入消息System.out.println("請輸入發(fā)送消息");String message = scanner.nextLine();//封裝消息為字節(jié)數(shù)組map.put("xxlx",xxlx);map.put("fsdx",fsdx);map.put("message",message);bytes = MessageFactory.fzxx(map);//發(fā)送消息sendMessage(socket,bytes);}//客戶端向服務(wù)器發(fā)送消息public static void sendMessage(Socket socket, byte[] bytes){try {OutputStream os = socket.getOutputStream();os.write(bytes);//清理os.flush();} catch (IOException e) {e.printStackTrace();}} } import java.io.InputStream; import java.net.Socket;/*** Create Time: 2020.06.14 09:52***/ public class CommThread extends Thread {private Socket socket = null;public CommThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try {while (true){InputStream is = socket.getInputStream();//解析服務(wù)端發(fā)送過來的消息MessageFactory.clientParseMessage(is);}} catch (Exception e) {e.printStackTrace();}} } /*** 客戶端解析消息* @param is* @return map中包含消息類型、(接收方信息長度、接收方信息)、消息長度、消息內(nèi)容、發(fā)送方信息長度、發(fā)送方信息*/public static void clientParseMessage(InputStream is){try {if(is.available() > 0){byte[] xxBytes = new byte[is.available()];is.read(xxBytes);//讀取消息類型byte[] xxlxBytes = new byte[4];System.arraycopy(xxBytes,0,xxlxBytes,0,4);int xxlx = Util.Bytes2int(xxlxBytes);switch (xxlx){//0:私聊case 0:{//讀取接收端用戶信息的長度byte[] receiverYHXXCDBytes = new byte[4];System.arraycopy(xxBytes,4,receiverYHXXCDBytes,0,4);int receiverLength = Util.Bytes2int(receiverYHXXCDBytes);//讀取消息byte[] messageLengthBytes = new byte[4];System.arraycopy(xxBytes,8 + receiverLength,messageLengthBytes,0,4);int messageLength = Util.Bytes2int(messageLengthBytes);byte[] messageBytes = new byte[messageLength];System.arraycopy(xxBytes,12 + receiverLength,messageBytes,0,messageLength);String message = new String(messageBytes, 0, messageLength);System.arraycopy(xxBytes,12 + receiverLength,messageBytes,0,messageLength);//讀取發(fā)送方信息byte[] senderLengthBytes = new byte[4];System.arraycopy(xxBytes,12 + receiverLength + messageLength ,senderLengthBytes,0,4);int senderLength = Util.Bytes2int(senderLengthBytes);byte[] senderBytes = new byte[senderLength];System.arraycopy(xxBytes,16 + receiverLength + messageLength,senderBytes,0,senderLength);String sender = new String(senderBytes,0,senderLength);System.out.println(sender + "發(fā)送消息:" + message);break;}//1:群聊case 1:{//讀取消息byte[] messageLengthBytes = new byte[4];System.arraycopy(xxBytes,4 ,messageLengthBytes,0,4);int messageLength = Util.Bytes2int(messageLengthBytes);byte[] messageBytes = new byte[messageLength];System.arraycopy(xxBytes,8,messageBytes,0,messageLength);String message = new String(messageBytes, 0, messageLength);System.arraycopy(xxBytes,8 ,messageBytes,0,messageLength);//讀取發(fā)送方信息byte[] senderLengthBytes = new byte[4];System.arraycopy(xxBytes,8 + messageLength ,senderLengthBytes,0,4);int senderLength = Util.Bytes2int(senderLengthBytes);byte[] senderBytes = new byte[senderLength];System.arraycopy(xxBytes,12 + messageLength ,senderBytes,0,senderLength);String sender = new String(senderBytes,0,senderLength);System.out.println(sender + "發(fā)送消息:" + message);break;}//2:刷新好友列表case 2:{byte[] friendBytes = new byte[xxBytes.length - 4];System.arraycopy(xxBytes,4,friendBytes,0,friendBytes.length);List<String> hylbList = (List<String>)Util.deSerializeObject(friendBytes);QQClient.setFriends(hylbList);for (int i = 0; i < hylbList.size();i++) {System.out.print((i + 1) + ". "+hylbList.get(i)+";");}System.out.println();}}}} catch (Exception e) {e.printStackTrace();}}/*** 客戶端封裝消息*/public static byte[] fzxx(Map<String,String> message){int xxlx = Integer.parseInt(message.get("xxlx"));byte[] xxlxBytes = Util.int2Bytes(xxlx);switch (xxlx){//0:私聊case 0:{byte[] fsdx = message.get("fsdx").getBytes();byte[] fsdxLengthBytes = Util.int2Bytes(fsdx.length);byte[] messages = message.get("message").getBytes();byte[] messageLengthBytes = Util.int2Bytes(messages.length);byte[] bytes = new byte[4 + 4 + fsdx.length + 4 + messages.length];//存入消息類型System.arraycopy(xxlxBytes,0,bytes,0,4);//存入接收方信息長度System.arraycopy(fsdxLengthBytes,0,bytes,4,4);//存入接收方信息System.arraycopy(fsdx,0,bytes,8,fsdx.length);//存入消息長度System.arraycopy(messageLengthBytes,0,bytes,8 + fsdx.length , messageLengthBytes.length);//存入消息System.arraycopy(messages,0,bytes,12 + fsdx.length,messages.length);return bytes;}//1:群聊case 1:{byte[] messages = message.get("message").getBytes();byte[] messageLengthBytes = Util.int2Bytes(messages.length);byte[] bytes = new byte[4 + 4 + messages.length];//存入消息類型System.arraycopy(xxlxBytes,0,bytes,0,4);//存入消息長度System.arraycopy(messageLengthBytes,0,bytes,4 , messageLengthBytes.length);//存入消息System.arraycopy(messages,0,bytes,8,messages.length);return bytes;}//2:刷新好友列表case 2:{return xxlxBytes;}}return null;}總結(jié)
- 上一篇: 解决Adobe不能安装的问题 160注
- 下一篇: 在搜索引擎的搜索结果中屏蔽CSDN