基于Socket编程的远程控制PC音乐播放器App(二)
2017-02-03 17:44
543 查看
三、客户端
具体的布局文件就不细说了,可以看源码,这里主要列出主要实现代码。
1、网络字节转换工具类(重要)
要想实现Java和C/C++的通信,客户端得先把数据全部转换为byte数组,int、float、double类型的转换必须进行特殊处理,这里只封装了int、String的转换,其他可根据需要自行封装。
2、首部类
3、数据类(列举两个典型: 发送数据类、响应数据类)
(1)发送数据类
每次向服务器发送这个类对象前,先转换为byte数组,再发送。
(2)响应数据类
每次接收到byte数组,先转换为Java中的变量类型,再使用。
4、Socket操作线程类
(1)发送数据,直接调用DataOutputStream.write()即可。
(2)接收数据,为了避免粘包、丢包,先接受数据包首部的长度字段,然后根据长度接收数据,最后组合成一条数据。
如果可自动进行IP获取,那最好不过了。
5、功能实现
1、登录
2、获取音乐列表,播放、停止音乐
注意,为了避免重复多次播放,我定义了一个集合Map<String, Integer> playMap,记录每首歌的播放状态。
再进行优化,客户端能显示播放进度,体验会更好!
具体的布局文件就不细说了,可以看源码,这里主要列出主要实现代码。
1、网络字节转换工具类(重要)
要想实现Java和C/C++的通信,客户端得先把数据全部转换为byte数组,int、float、double类型的转换必须进行特殊处理,这里只封装了int、String的转换,其他可根据需要自行封装。
public class NetDataTransformUtils { /** * int 转为 ByteArray * 将int转为低字节在前,高字节在后的byte数组 * @param n * @return */ public static byte[] intToByteArray(int n) { byte[] b = new byte[4]; b[0] = (byte) (n & 0xff); b[1] = (byte) (n >> 8 & 0xff); b[2] = (byte) (n >> 16 & 0xff); b[3] = (byte) (n >> 24 & 0xff); return b; } /** * ByteArray 转为 int * @param bArr * @return */ public static int byteArrayToInt(byte[] bArr) { int n = 0; for(int i=0;i<bArr.length&&i<4;i++){ int left = i*8; n+= (bArr[i] << left); } return n; } /** * ByteArray 转为 String * @param valArr * @param maxLen * @return */ public static String byteArrayToString(byte[] valArr,int maxLen) { String result=null; int index = 0; while(index < valArr.length && index < maxLen) { if(valArr[index] == 0) { break; } index++; } byte[] temp = new byte[index]; System.arraycopy(valArr, 0, temp, 0, index); try { result= new String(temp,"GBK"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return result; } /** * String 转为 ByteArray * @param str * @return */ public static byte[] stringToByteArray(String str){ byte[] temp = null; try { temp = str.getBytes("GBK"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return temp; } }
2、首部类
public class PktHeader{ private int pktLen; private int pktType; public PktHeader(){ } public PktHeader(byte[] b){ if(b.length == 4){ byte[] b1 = new byte[4]; int i; for (i=0;i<4;i++) { b1[i] = b[i]; } this.pktLen = NetDataTransformUtils.byteArrayToInt(b1); }else if(b.length >= 8){ byte[] b1 = new byte[4]; byte[] b2 = new byte[4]; int i; for (i=0;i<4;i++) { b1[i] = b[i]; } this.pktLen = NetDataTransformUtils.byteArrayToInt(b1); for (i=0;i<4;i++) { b2[i] = b[4+i]; } this.pktType = NetDataTransformUtils.byteArrayToInt(b2); } } public PktHeader(int pktLen, int pktType){ this.pktLen = pktLen; this.pktType = pktType; } public int getPktLen() { return pktLen; } public void setPktLen(int pktLen) { this.pktLen = pktLen; } public int getPktType() { return pktType; } public void setPktType(int pktType) { this.pktType = pktType; } }
3、数据类(列举两个典型: 发送数据类、响应数据类)
(1)发送数据类
每次向服务器发送这个类对象前,先转换为byte数组,再发送。
public class LoginPkt{ private PktHeader header; private String username; private String password; public LoginPkt(){ } public LoginPkt(PktHeader header, String username, String password){ this.header = header; this.username = username; this.password = password; } public PktHeader getHeader() { return header; } public void setHeader(PktHeader header) { this.header = header; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public byte[] getBuf() { byte[] buf = new byte[4+this.getHeader().getPktLen()]; byte[] temp = NetDataTransformUtils.intToByteArray(this.getHeader().getPktLen()); System.arraycopy(temp, 0, buf, 0, 4); temp = NetDataTransformUtils.intToByteArray(this.getHeader().getPktType()); System.arraycopy(temp, 0, buf, 4, 4); temp = NetDataTransformUtils.stringToByteArray(this.getUsername()); System.arraycopy(temp, 0, buf, 8, 20); byte[] temp2 = NetDataTransformUtils.stringToByteArray(this.getPassword()); System.arraycopy(temp2, 0, buf, 28, temp2.length); return buf; } }
(2)响应数据类
每次接收到byte数组,先转换为Java中的变量类型,再使用。
public class LoginReplyPkt { PktHeader header; int ret; public LoginReplyPkt(){ } public LoginReplyPkt(byte[] b){ byte[] b1 = new byte[4]; byte[] b2 = new byte[4]; byte[] b3 = new byte[4]; int i; for (i=0;i<4;i++) { b1[i] = b[i]; } for (i=0;i<4;i++) { b2[i] = b[4+i]; } for (i=0;i<4;i++) { b3[i] = b[8+i]; } this.header = new PktHeader(NetDataTransformUtils.byteArrayToInt(b1), NetDataTransformUtils.byteArrayToInt(b2)); this.ret = NetDataTransformUtils.byteArrayToInt(b3); } public PktHeader getHeader() { return header; } public void setHeader(PktHeader header) { this.header = header; } public int getRet() { return ret; } public void setRet(int ret) { this.ret = ret; } }
4、Socket操作线程类
(1)发送数据,直接调用DataOutputStream.write()即可。
(2)接收数据,为了避免粘包、丢包,先接受数据包首部的长度字段,然后根据长度接收数据,最后组合成一条数据。
如果可自动进行IP获取,那最好不过了。
public class SocketThread extends Thread { private static final String addr = "117.23.249.158"; // 自己设置同一局域网IP private static final int port = 8888; private int timeout = 3000; private Socket socket; DataInputStream dataInputStream; DataOutputStream dataOutputStream; Handler outHandler; Handler inHandler; public boolean isRun = true; public SocketThread(){ } public SocketThread(Handler outHandler, Handler inHandler){ this.outHandler = outHandler; this.inHandler = inHandler; } /** * 连接 */ public void connect(){ try { socket = new Socket(); socket.connect(new InetSocketAddress(addr, port), timeout); if(socket.isConnected()){ dataOutputStream = new DataOutputStream(socket.getOutputStream()); dataInputStream = new DataInputStream(socket.getInputStream()); } }catch (UnknownHostException e) { e.printStackTrace(); connect(); } catch (IOException e) { e.printStackTrace(); } } /** * 接收数据 */ @Override public void run() { connect(); while(isRun) { try { if (socket.isConnected()) { //接收头部包长 byte[] recvBufHeader = new byte[4]; dataInputStream.read(recvBufHeader, 0, 4); PktHeader header = new PktHeader(recvBufHeader); //根据包长接收具体数据 int pktLen = header.getPktLen(); byte[] recvBuf = new byte[pktLen]; dataInputStream.read(recvBuf, 0, pktLen); //合成数据 byte[] data = new byte[4+pktLen]; System.arraycopy(recvBufHeader, 0, data, 0, 4); System.arraycopy(recvBuf, 0, data, 4, pktLen); Message message = inHandler.obtainMessage(); message.what = 1; message.obj = data; inHandler.sendMessage(message); } else { connect(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 发送数据 * @param data */ public void send(byte[] data){ try { if(socket.isConnected()) { dataOutputStream.write(data); dataOutputStream.flush(); Message message = outHandler.obtainMessage(); message.what = 1; outHandler.sendMessage(message); }else{ Message message = outHandler.obtainMessage(); message.what = 0; outHandler.sendMessage(message); } } catch (IOException e) { Message message = outHandler.obtainMessage(); message.what = 0; outHandler.sendMessage(message); e.printStackTrace(); } } /** * 关闭资源 */ public void close(){ try{ if(socket != null){ isRun = false; dataOutputStream.close(); dataInputStream.close(); socket.close(); } }catch (IOException e){ e.printStackTrace(); } } }
5、功能实现
1、登录
public class MainActivity extends Activity { @BindView(R.id.main_username) EditText mainUsername; @BindView(R.id.main_password) EditText mainPassword; ProgressDialog progressDialog; SocketThread loginThread; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); startSocket(); progressDialog = new ProgressDialog(MainActivity.this); } public Handler outHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what){ case 0: ToastUtils.showToast(MainActivity.this, "网络错误"); break; case 1: progressDialog.setMessage("正在登录..."); progressDialog.setCanceledOnTouchOutside(false); progressDialog.show(); break; } } }; public Handler inHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: LoginReplyPkt loginReplyPkt = new LoginReplyPkt((byte[]) msg.obj); if (loginReplyPkt.getHeader().getPktType() == PktType.PKT_LOGIN_REPLY && loginReplyPkt.getRet() == PktType.LOGIN_SUCCESS) { progressDialog.dismiss(); ToastUtils.showToast(MainActivity.this, "登录成功"); loginThread.isRun = false; // 登录成功跳转 Bundle bundle = new Bundle(); bundle.putString("username",mainUsername.getText().toString().trim()); Intent goMusicPlay = new Intent(getApplicationContext(), MusicPlayActivity.class); goMusicPlay.putExtras(bundle); startActivity(goMusicPlay); finish(); } else { progressDialog.dismiss(); ToastUtils.showToast(MainActivity.this, "登录失败"); } break; } super.handleMessage(msg); } }; public void startSocket(){ loginThread = new SocketThread(outHandler, inHandler); loginThread.start(); } public void login() { String usernameStr = mainUsername.getText().toString().trim(); String passwordStr = mainPassword.getText().toString().trim(); if (!TextUtils.isEmpty(usernameStr) && !TextUtils.isEmpty(passwordStr)) { //构建登录包 LoginPkt loginPkt = new LoginPkt(); if (usernameStr.length() < 20) { for (int i = usernameStr.length(); i < 20; i++) { usernameStr += '\0'; } } loginPkt.setUsername(usernameStr); loginPkt.setPassword(passwordStr); loginPkt.setHeader(new PktHeader(4+20+20, PktType.PKT_LOGIN)); loginThread.send(loginPkt.getBuf()); } } @OnClick(R.id.main_login) public void onClick(View v) { switch (v.getId()) { case R.id.main_login: login(); break; } } @Override protected void onDestroy() { if(progressDialog != null){ progressDialog.dismiss(); } super.onDestroy(); } }
2、获取音乐列表,播放、停止音乐
注意,为了避免重复多次播放,我定义了一个集合Map<String, Integer> playMap,记录每首歌的播放状态。
public class MusicPlayActivity extends AppCompatActivity { ActionBar actionBar; @BindView(R.id.toolbar_title) TextView toolbarTitle; @BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.music_play_tips) TextView musicPlayTips; private String username; @BindView(R.id.music_play_list_view) ListView musicPlayListView; List<Music> data = new ArrayList<>(); MusicAdapter musicAdapter; TextView musicName; ImageView startStop; Map<String, Integer> playMap = new HashMap<>(); //每条音乐的播放 int[] playImage = {R.drawable.music_play_start, R.drawable.music_play_stop}; SocketThread musicThread; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_music_play); ButterKnife.bind(this); initActionBar(); initListView(); if (getIntent().getExtras() != null) { username = getIntent().getExtras().getString("username"); } startSocket(); } public void initActionBar() { // ToolBar toolbar.setTitleTextColor(getResources().getColor(R.color.white)); // 设置标题 toolbarTitle.setText("音乐台"); setSupportActionBar(toolbar); actionBar = getSupportActionBar(); if (actionBar != null) { // 去掉 ActionBar 自带标题 actionBar.setTitle(null); } } public void initListView() { if(data.size() > 0){ musicPlayTips.setVisibility(View.GONE); musicPlayListView.setVisibility(View.VISIBLE); }else{ musicPlayTips.setVisibility(View.VISIBLE); musicPlayListView.setVisibility(View.GONE); } musicAdapter = new MusicAdapter(data, this); musicPlayListView.setAdapter(musicAdapter); musicPlayListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { startStop = (ImageView) view.findViewById(R.id.music_item_music_start_stop); musicName = (TextView) view.findViewById(R.id.music_item_music_name); playStartStop(musicName.getText().toString().trim()); } }); } public void startSocket() { musicThread = new SocketThread(outHandler, inHandler); musicThread.start(); } public Handler outHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: ToastUtils.showToast(MusicPlayActivity.this, "网络错误"); break; } } }; public Handler inHandler = new Handler() { @Override public void handleMessage(Message msg) { PktHeader header = new PktHeader((byte[]) msg.obj); switch (header.getPktType()) { case PktType.PKT_GET_MUSIC_LIST_REPLY: GetMusicListReplyPkt pkt = new GetMusicListReplyPkt((byte[]) msg.obj); String name = getFileNameNoEx(pkt.getMusicName()); musicAdapter.addItem(new Music(name)); playMap.put(name, 0); if(data.size() > 0){ musicPlayTips.setVisibility(View.GONE); musicPlayListView.setVisibility(View.VISIBLE); }else{ musicPlayTips.setVisibility(View.VISIBLE); musicPlayListView.setVisibility(View.GONE); } break; case PktType.PKT_PLAY_START_STOP_REPLY: PlayStartStopReplyPkt replyPkt = new PlayStartStopReplyPkt((byte[]) msg.obj); if (replyPkt.getRet() == PktType.PLAY_START_SUCCESS) { playMap.put(musicName.getText().toString().trim(), 1); startStop.setImageResource(playImage[1]); ToastUtils.showToast(MusicPlayActivity.this, "播放成功!"); } if (replyPkt.getRet() == PktType.PLAY_START_FAIL) { ToastUtils.showToast(MusicPlayActivity.this, "播放失败!"); } if (replyPkt.getRet() == PktType.PLAY_STOP_SUCCESS) { playMap.put(musicName.getText().toString().trim(), 0); startStop.setImageResource(playImage[0]); ToastUtils.showToast(MusicPlayActivity.this, "停止成功!"); } if (replyPkt.getRet() == PktType.PLAY_STOP_FAIL) { ToastUtils.showToast(MusicPlayActivity.this, "停止失败!"); } break; } } }; public void getMusicList() { // 构建包 GetMusicListPkt pkt = new GetMusicListPkt(); pkt.setHeader(new PktHeader(4, PktType.PKT_GET_MUSIC_LIST)); musicThread.send(pkt.getBuf()); } /** * 获取不带文件类型的文件名 * @param filename * @return */ public String getFileNameNoEx(String filename) { if ((filename != null) && (filename.length() > 0)) { int dot = filename.lastIndexOf('.'); if ((dot > -1) && (dot < (filename.length()))) { return filename.substring(0, dot); } } return filename; } public void playStartStop(String name) { for(Map.Entry<String, Integer> entry : playMap.entrySet()){ if(entry.getKey().equals(name)){ if(entry.getValue() == 0){ if(!isPlaying()){ // 构建开始播放包 PlayStartStopPkt pkt = new PlayStartStopPkt(); pkt.setHeader(new PktHeader(4+4+30, PktType.PKT_PLAY_START_STOP)); pkt.setType(PktType.PLAY_START); pkt.setMusicName(name); musicThread.send(pkt.getBuf()); }else{ ToastUtils.showToast(getApplicationContext(), "不允许同时播放多首音乐"); } }else{ // 构建停止播放包 PlayStartStopPkt pkt = new PlayStartStopPkt(); pkt.setHeader(new PktHeader(4+4+30, PktType.PKT_PLAY_START_STOP)); pkt.setType(PktType.PLAY_STOP); pkt.setMusicName(name); musicThread.send(pkt.getBuf()); } } } } /** * 是否有音乐在播放 * @return */ public boolean isPlaying(){ for(Map.Entry<String, Integer> entry : playMap.entrySet()){ if(entry.getValue() == 1){ return true; } } return false; } public void logout() { // 构建包 LogoutPkt pkt = new LogoutPkt(); pkt.setHeader(new PktHeader(4+30, PktType.PKT_LOGOUT)); pkt.setUsername(username); musicThread.send(pkt.getBuf()); musicThread.close(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.music_play_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.music_play_menu_get_music_list: if(!isPlaying()){ //每获取一次必须清楚原有数据 musicAdapter.clearAll(); playMap.clear(); getMusicList(); }else { ToastUtils.showToast(getApplicationContext(), "请先关闭当前播放的音乐"); } break; case R.id.music_play_menu_logout: logout(); finish(); break; } return super.onOptionsItemSelected(item); } @Override protected void onDestroy() { super.onDestroy(); musicThread.close(); } }
再进行优化,客户端能显示播放进度,体验会更好!
相关文章推荐
- 基于Socket编程的远程控制PC音乐播放器App(一)
- 基于visual c++之windows核心编程代码分析(51)基于匿名管道实现远程控制
- 基于android手机的3G+GPS远程控制模型车工程-android手机编程6-伪视频控制远程手机端程序(文字篇)
- 基于visual c++之windows核心编程代码分析(51)基于匿名管道实现远程控制
- 基于visual c++之windows核心编程代码分析(66)实现Windows服务的远程控制
- 基于visual c++之windows核心编程代码分析(66)实现Windows服务的远程控制
- 基于visual c++之windows核心编程代码分析(29)ICMP实现远程控制
- 基于visual c++之windows核心编程代码分析(66)实现Windows服务的远程控制
- [20180313智慧餐厅推荐系统02]基于python的socket编程代码,实现PC与服务器的简单通信
- 基于visual c++之windows核心编程代码分析(51)基于匿名管道实现远程控制
- 基于android手机的3G+GPS远程控制模型车工程-android手机编程7-伪视频控制远程手机端程序(代码篇)
- 基于visual c++之windows核心编程代码分析(49)基于匿名管道实现远程控制
- 基于visual c++之windows核心编程代码分析(49)基于匿名管道实现远程控制
- 基于visual c++之windows核心编程代码分析(29)ICMP实现远程控制
- 基于局域网络应用Java语言实现远程智能终端的控制
- 基于Socket的Java网络编程集粹-Java基础-Java-编程开发
- 远程控制编程揭密
- 基于S3C44B0X的128x64单色LCD编程控制
- 基于套接字通信的远程截屏显示与控制技术
- C#利用Wmi远程控制pc或者获取远程pc的配置信息