上一篇文章记录的是最基础的Socket和服务端连接,这篇文章记录的内容是:客户端与服务端相互传输数据的过程,其中客户端为安卓。如有不正确(有争议)的地方希望大家予以指正。
服务端
服务端如题使用Java编写,继续使用上一篇文章的部分代码。
服务端代码
package com.imudges.j2se.network;
import com.google.gson.Gson;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class Server {
private Socket socket;
//声明一个ServerSocket对象
private ServerSocket serverSocket;
//输入流
private BufferedReader bufferedReader;
//输出流
private PrintWriter printWriter;
//储存所有连接的socket
private Map<String ,Socket> clientSocket = new HashMap<>();
private Gson gson = new Gson();
/**
* 构造函数
* */
public Server() {}
public static void main(String args[]){
Server server = new Server();
server.getServer();
}
/**
* 获取连接
* */
public void getServer(){
try {
//绑定的端口为6666,此端口要与客户端请求的一致
serverSocket = new ServerSocket(6666);
while(true){
System.err.println("等待客户端连接......");
//从队列中取出Socket或等待连接
socket = serverSocket.accept();
if (socket.isConnected()){
System.out.println("连接成功!");
}
//开新的线程,为这个socket服务
new HandlerThread(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 实现一个Runnable接口,处理和client的一些事物
* */
private class HandlerThread implements Runnable{
private Socket socket;
public HandlerThread(Socket socket) {
this.socket = socket;
new Thread(this).start();
}
@Override
public void run() {
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//处理客户端发来的数据
String clientMsg = null;
while ((clientMsg = bufferedReader.readLine())!=null){
System.out.println("客户端发来的消息:" + clientMsg);
//向客户端回复信息
printWriter = new PrintWriter(socket.getOutputStream());
printWriter.println("ok");
printWriter.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(socket != null){
try {
socket.close();
} catch (IOException e) {
socket = null;
System.err.println("服务器finally异常,异常信息:" + e.getMessage());
}
}
}
}
}
}
服务端启动之后的控制台如图:
有客户端连接后的控制台输出如图:
客户端发来消息后的控制台输出如图:
- 注:其中clientID为安卓的IMEI
客户端
客户端为Java,同时将Message封装为了一个Bean。
客户端代码
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.demo.imudges.socketdemo.MainActivity"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="服务器说:"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tv_server_msg"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我给服务器说:"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/et_cilent_send_msg"
/>
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送信息"
android:gravity="center"
android:id="@+id/btn_send_msg"
/>
</LinearLayout>
Message.class
package com.demo.imudges.socketdemo;
import android.content.Context;
import android.widget.Toast;
import java.io.*;
import java.net.Socket;
public class Client {
private Socket socket;
private PrintWriter printWriter;//输出
private BufferedReader bufferedReader;
private static String URL = "183.175.12.168";
private static int port = 6666;
public String msg = "";
private Context context;
public Client(Context context) {
this.context = context;
//设置服务器IP,绑定端口
try {
socket = new Socket(URL,port);
System.out.println("连接完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void sendMessage(){
try {
//向服务器发送数据
//初始化输出流 用来向服务器传递数据
if(socket.isOutputShutdown()){
System.err.println("OutputStream 被关闭");
}
printWriter = new PrintWriter(socket.getOutputStream(),true);
printWriter.println(msg);
//清空缓冲区的数据流 数据流向:内存->缓冲区->文件(或输出),如果不用.flush(),可能缓冲区内部还有数据残留,.flush()会将缓冲区内部的数据强制输出
printWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public interface Listener{
void update(String msg);
}
private Listener listener;
public void setListener(Listener l){
this.listener = l;
}
public void getServerMsg(){
//接收服务器数据
//初始化输入流 用来获取服务器下发的数据
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String reply = null;
while(!((reply = bufferedReader.readLine()) ==null)){
System.out.println("服务器发送的数据为:" + reply);
listener.update(reply);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
MainActivity.class
package com.demo.imudges.socketdemo;
import Bean.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.google.gson.Gson;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tvServerMsg;
private EditText etCLientSendMsg;
private Button btnSendMsg;
private TelephonyManager telephonyManager;
private String IMEI = "";
private Client client;
private Message message;
private Gson gson = new Gson();
/**
* 初始化控件
* */
private void initViews(){
etCLientSendMsg = (EditText) findViewById(R.id.et_cilent_send_msg);
tvServerMsg = (TextView) findViewById(R.id.tv_server_msg);
btnSendMsg = (Button) findViewById(R.id.btn_send_msg);
btnSendMsg.setOnClickListener(this);
telephonyManager = (TelephonyManager) this.getSystemService(this.TELEPHONY_SERVICE);
//获取设备唯一ID
IMEI = telephonyManager.getDeviceId();
}
/**
* 初始化事件
* */
private void initEvents(){
//判断是否获取到IMEI
if(IMEI != null && !IMEI.equals("")){
new Thread(new Runnable() {
@Override
public void run() {
client = new Client(MainActivity.this);
client.setListener(new Client.Listener() {
@Override
public void update(final String msg) {
if(msg!=null){
//子线程中更新界面
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
//Message messageFromServer = gson.fromJson(msg,Message.class);
tvServerMsg.setText(msg);
}
});
}
}).start();
}
}
});
client.getServerMsg();
}
}).start();
message = new Message();
message.setClientID(IMEI);
} else {
Toast.makeText(this,"获取IMEI失败",Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initEvents();
}
@Override
public void onClick(View view) {
if(!TextUtils.isEmpty(etCLientSendMsg.getText())){
message.setMsg(etCLientSendMsg.getText().toString());
String msg = gson.toJson(message);
client.setMsg(msg);
new Thread(new Runnable() {
@Override
public void run() {
client.sendMessage();
}
}).start();
} else {
}
}
}
Client.class
package com.demo.imudges.socketdemo;
import android.content.Context;
import android.widget.Toast;
import java.io.*;
import java.net.Socket;
public class Client {
private Socket socket;
private PrintWriter printWriter;//输出
private BufferedReader bufferedReader;
private static String URL = "183.175.12.168";
private static int port = 6666;
public String msg = "";
private Context context;
public Client(Context context) {
this.context = context;
//设置服务器IP,绑定端口
try {
socket = new Socket(URL,port);
System.out.println("连接完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void sendMessage(){
try {
//向服务器发送数据
//初始化输出流 用来向服务器传递数据
if(socket.isOutputShutdown()){
System.err.println("OutputStream 被关闭");
}
printWriter = new PrintWriter(socket.getOutputStream(),true);
printWriter.println(msg);
//清空缓冲区的数据流 数据流向:内存->缓冲区->文件(或输出),如果不用.flush(),可能缓冲区内部还有数据残留,.flush()会将缓冲区内部的数据强制输出
printWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public interface Listener{
void update(String msg);
}
private Listener listener;
public void setListener(Listener l){
this.listener = l;
}
public void getServerMsg(){
//接收服务器数据
//初始化输入流 用来获取服务器下发的数据
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String reply = null;
while(!((reply = bufferedReader.readLine()) ==null)){
System.out.println("服务器发送的数据为:" + reply);
listener.update(reply);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端发送消息后的截图:
贴上一张在网上找到的图片,个人觉得对于理解服务端和客户端Socket工作原理有所帮助。
- 最后总结一下我所踩的坑,希望大家引以为戒:
- 安卓端对于新开的线程,要用
.start()
方法启动,而不是.run()
。- 对于Socket接受和发送数据,无论是
socket.shutdownInput()
socket.shutdownOutput()
还是socket.close()
他们都会将Socket关闭,所以我使用printWriter.println()
发送数据,使用bufferReader.readLint()
接受数据,酱紫每次发送一行,每次读取一行,有明确的结束。同时不会使Scoket断开。(下面简单说说其他实现的方法)
2.1 使用Byte数组发送数据,每次发送的数据的第一位是数据长度,读取方只需要按照发送方给予的长度进行读取就可以,酱紫也不会使Socket断开。
2.2 发完就断开,断开重新连接。- 理清楚思路再写代码,不要让服务端和客户端同时等待输入或者是同时输出。酱紫会造成死循环?
socket.shutdownOutput()
socket.shutdownInput()
是单向关闭流。