一、Socket是什么?
百度百科的解释:
套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
简单理解:
分客户端与服务端,Socket以服务端的ip地址和定好的端口号进行组合得到套接字进行两端的相互连接,达到能够互相收发消息的目的。
需要注意:
socket通信只能在同一子网下,也就是说两个手机(Android设备)必须连接同一WIFI,不能说一个手机连着WIFI,一个手机用流量。
二、双向文本信息通信
首先需要创建两个app工程,一个作为客户端,一个作为服务器端。具体实现过程如下:
1.为两个app工程的 AndroidManifest.xml 添加权限
<!--允许应用程序改变网络状态-->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<!--允许应用程序改变WIFI连接状态-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!--允许应用程序访问WIFI网卡的网络信息-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--允许应用程序访问有关的网络信息-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--允许应用程序完全使用网络-->
<uses-permission android:name="android.permission.INTERNET" />
2.服务器端
- 服务器端负责监听和接收来自客户端的连接请求
- 先创建ServerSocket对象,用来绑定监听的端口
- 再调用accept()方法来监听客户端的请求
- 连接建立后,通过输入流读取客户端发送的请求信息,通过输出流向客户端发送响应信息
- 最后关闭相关资源
代码如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
ServerSocket serverSocket;//ServerSocket对象,用来绑定监听的端口
Socket socket;
Thread Thread1 = null;
TextView tvIP, tvPort;
TextView tvConnectionStatus, tvMessages;
EditText etMessage;
Button btnSend;
public static String SERVER_IP = "";//ip
public static final int SERVER_PORT = 8080;// 设置服务器监听的端口号
String message;
private PrintWriter printWriter;//通过输入流读取客户端发送的请求信息
private BufferedReader reader;//通过输出流向客户端发送响应信息
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvIP = findViewById(R.id.tvIP);//192.168.236.147
tvPort = findViewById(R.id.tvPort);//8080
tvConnectionStatus = findViewById(R.id.tvConnectionStatus);
tvMessages = findViewById(R.id.tvMessages);
etMessage = findViewById(R.id.etMessage);
btnSend = findViewById(R.id.btnSend);
try {
SERVER_IP = getLocalIpAddress();//获取ip
Log.d(TAG, "onCreate IP:" + SERVER_IP);
} catch (UnknownHostException e) {
e.printStackTrace();
}
Thread1 = new Thread(new Thread1());
Thread1.start();
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
message = etMessage.getText().toString().trim();
if (!message.isEmpty()) {
new Thread(new Thread3(message)).start();
}
}
});
}
//获取ip
private String getLocalIpAddress() throws UnknownHostException {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
assert wifiManager != null;
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ipInt = wifiInfo.getIpAddress();
return InetAddress.getByAddress(
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ipInt).array())
.getHostAddress();
}
/**
* 接收连接请求,读取客户端发送来的信息
*/
class Thread1 implements Runnable {
@Override
public void run() {
try {
serverSocket = new ServerSocket(SERVER_PORT);
runOnUiThread(new Runnable() {
@Override
public void run() {
tvIP.setText("IP: " + SERVER_IP);
tvPort.setText("Port: " + String.valueOf(SERVER_PORT));
tvConnectionStatus.setText("Not connected");
}
});
try {
socket = serverSocket.accept();//调用accept()方法来监听客户端的请求
printWriter = new PrintWriter(socket.getOutputStream());
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
runOnUiThread(new Runnable() {
@Override
public void run() {
tvConnectionStatus.setText("Connected");
}
});
InputStream is = socket.getInputStream();//服务端获取输入流
byte []b =new byte[1024];//采用byte数组 按字节进行数据的接收 避免readline()方法的阻塞机制
int x = is.read(b, 0, b.length);//相较于readline()读取数据 字节读取慢 不适合大量数据的通信
String string = new String(b, 0, x);
if (string != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvMessages.append("我是server,我收到了来自client的信息:" + string + "\n");
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
Log.d(TAG, "run: Thread1");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 向客户端发送信息
*/
class Thread3 implements Runnable {
private String message;
Thread3(String message) {
this.message = message;
}
@Override
public void run() {
printWriter.println(message);
printWriter.flush();
runOnUiThread(new Runnable() {
@Override
public void run() {
tvMessages.append("我是server,我向client发送了: " + message + "\n");
etMessage.setText("");
}
});
//关闭操作
try {
printWriter.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
Log.d(TAG, "run: Thread3");
}
}
}
3.客户端
- 客户端负责建立与服务器的连接并发送请求
- 先创建Socket对象,要指明需要链接的服务器的地址和端号,即两个参数;
- 链接建立后,通过输出流向服务器发送请求信,通过输入流获取服务器响应的信息;
- 最后关闭相关资源
代码如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
Thread Thread1 = null;
Socket socket;
EditText etIP, etPort;
Button btnConnect;
TextView tvMessages;
EditText etMessage;
Button btnSend;
String SERVER_IP;
int SERVER_PORT;
private PrintWriter output;//使用output将数据发送到服务器
private BufferedReader input;//使用InputStream可以从服务器接收数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etIP = findViewById(R.id.etIP);
etPort = findViewById(R.id.etPort);
etIP.setText("192.168.236.198");
etPort.setText("8080");
tvMessages = findViewById(R.id.tvMessages);
btnConnect = findViewById(R.id.btnConnect);
etMessage = findViewById(R.id.etMessage);
btnSend = findViewById(R.id.btnSend);
btnConnect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tvMessages.setText("");
SERVER_IP = etIP.getText().toString().trim();
SERVER_PORT = Integer.parseInt(etPort.getText().toString().trim());
Thread1 = new Thread(new Thread1());
Thread1.start();
}
});
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String message = etMessage.getText().toString().trim();
if (!message.isEmpty()) {
new Thread(new Thread3(message)).start();
}
}
});
}
/**
* 请求连接
*/
class Thread1 implements Runnable {
@Override
public void run() {
try {
socket = new Socket(SERVER_IP, SERVER_PORT);//创建Socket对象,指明需要链接的服务器的地址和端号
output = new PrintWriter(socket.getOutputStream());//输出流
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));//输入流
runOnUiThread(new Runnable() {
@Override
public void run() {
tvMessages.setText("Connected\n");
}
});
/**
* 接收到来自服务端的信息
*/
InputStream is=socket.getInputStream();//服务端获取输入流
byte []b =new byte[1024];//采用byte数组 按字节进行数据的接收 避免readline()方法的阻塞机制
int x= is.read(b, 0, b.length);//相较于readline()读取数据 字节读取慢 不适合大量数据的通信
String string=new String(b, 0, x);
if (string != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvMessages.append("我是client,我收到了来自server的信息: " + string + "\n");
}
});
}
Log.d(TAG, "run: Thread1");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 向服务端发送信息
*/
class Thread3 implements Runnable {
private String message;
Thread3(String message) {
this.message = message;
}
@Override
public void run() {
//通过输出流向服务器发送请求信
output.println(message);
output.flush();
runOnUiThread(new Runnable() {
@Override
public void run() {
tvMessages.append("我是client,我向server发送了: " + message + "\n");
etMessage.setText("");
}
});
Log.d(TAG, "run: Thread3");
}
}
}
4.实现效果
三、文件传输(服务器端向客户端发送文件,客户端接收文件)
1.与上面的文本传输相同,需要在AndroidManifest.xml文件中添加权限,还要在mainifests文件里加android:requestLegacyExternalStorage="true"
2.服务器端
准备好要传输的文件,本人实测服务器端代码和客户端代码是跑到同一模拟器的,所以服务器端是在sdcard/Pictures目录下放入要传输的文件,如下:
server:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
ServerSocket serverSocket;
TextView tvIP, tvPort, tvConnectionStatus;
TextView fileName, progress;
Button btnSend;
Thread Thread0 = null;
static int vis;
private FileInputStream fis;
private DataOutputStream dos;
private File file;
private byte[] bytes;
public static String SERVER_IP = "";
public static final int SERVER_PORT = 8080;// 设置服务器监听的端口号
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvIP = findViewById(R.id.tvIP);//192.168.236.198
tvPort = findViewById(R.id.tvPort);//8080
tvConnectionStatus = findViewById(R.id.tvConnectionStatus);
fileName = findViewById(R.id.text_file);
progress = findViewById(R.id.text_progress);
btnSend = findViewById(R.id.btnSend);
try {
SERVER_IP = getLocalIpAddress();//获取ip
Log.d(TAG, "onCreate: IP:" + SERVER_IP);
} catch (UnknownHostException e) {
e.printStackTrace();
}
Thread0 = new java.lang.Thread(new Thread0());
Thread0.start();
/*在实测时发现文件读取时android 8.1版本的手机好像不能用Environment.getExternalStorageDirectory()
读取路径下的文件,因此我们提前要把手机android版本号获取到,然后对此进行不同的操作*/
char Vision[] = null;
Vision = Build.VERSION.RELEASE.toCharArray();
//获取android版本号
for (int i = 0, j = 1; (i < Vision.length) && (Vision[i] != '.'); i++, j = j * 10) {
vis = vis * j + ((int) Vision[i] - 48);
}
// //动态获取权限
// if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
// }
// if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
// }
//根据版本进行不同操作来获取文件
//文件名称和路径一定要设对,不然后面的操作都无法进行
if (vis > 9) {
file = new File(Environment.getExternalStorageDirectory().getPath() + "/Pictures/1.jpg");
} else {
file = new File("sdcard/Pictures/" + "1.jpg");
}
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Thread1(file)).start();
}
});
}
//获取ip
private String getLocalIpAddress() throws UnknownHostException {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
assert wifiManager != null;
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ipInt = wifiInfo.getIpAddress();
return InetAddress.getByAddress(
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ipInt).array())
.getHostAddress();
}
class Thread0 implements Runnable {
@Override
public void run() {
try {
serverSocket = new ServerSocket(SERVER_PORT);
runOnUiThread(new Runnable() {
@Override
public void run() {
tvConnectionStatus.setText("Not connected");
tvIP.setText("IP: " + SERVER_IP);
tvPort.setText("Port: " + String.valueOf(SERVER_PORT));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Thread1 implements Runnable {
private File file;
Thread1(File file) {
this.file = file;
}
@Override
public void run() {
Socket socket;
try {
socket = serverSocket.accept();
runOnUiThread(new Runnable() {
@Override
public void run() {
tvConnectionStatus.setText("Connected");
}
});
while (true) {
runOnUiThread(new Runnable() {
@Override
public void run() {
progress.setText("发送中");
}
});
dos = new DataOutputStream(socket.getOutputStream());
fis = new FileInputStream(file);
bytes = new byte[(int) file.length()];
//传输文件名称
dos.writeUTF(file.getName());
dos.flush();
//传输文件长度
dos.writeLong((int) file.length());
dos.flush();
int len = -1;
// 将文件读取到字节数组
while ((len = fis.read(bytes)) != -1) {
// 把字节数组输出
dos.write(bytes);
}
dos.flush();
fis.close();
dos.close();
socket.close();
progress.setText(100 + "%");
fileName.setText("文件(" + file.getName() + ")发送完毕");
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, "run: Thread1");
}
}
}
3. 客户端,接收数据存入到sdcard/Movies目录下
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
Thread Thread1 = null;
EditText etIP, etPort;
Button btnConnect;
TextView tvMessages;
TextView fileName,progress;
Button btnReceive;
String SERVER_IP;
int SERVER_PORT;
private DataOutputStream dos;
private DataInputStream dis;
static String name =null;
static int vis;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etIP = findViewById(R.id.etIP);
etPort = findViewById(R.id.etPort);
etIP.setText("172.30.121.126");
etPort.setText("8080");
tvMessages = findViewById(R.id.tvMessages);
btnConnect = findViewById(R.id.btnConnect);
fileName = findViewById(R.id.text_file);
progress = findViewById(R.id.text_progress);
btnReceive = findViewById(R.id.btnReceive);
btnConnect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tvMessages.setText("");
SERVER_IP = etIP.getText().toString().trim();
SERVER_PORT = Integer.parseInt(etPort.getText().toString().trim());
Thread1 = new Thread(new Thread1());
Thread1.start();
}
});
btnReceive.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(){
@Override
public void run() {
new Thread(new Thread2()).start();
}
}.start();
}
});
}
class Thread1 implements Runnable {
@Override
public void run() {
Socket socket;
try {
socket = new Socket(SERVER_IP, SERVER_PORT);
runOnUiThread(new Runnable() {
@Override
public void run() {
tvMessages.setText("Connected\n");
}
});
dos = new DataOutputStream(socket.getOutputStream());
dis = new DataInputStream(socket.getInputStream());
Log.d(TAG, "run: Connected");
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Thread2 implements Runnable {
@Override
public void run() {
try {
name = dis.readUTF();// 文件名读取
long lengths = dis.readLong();
byte[] bt = new byte[(int) lengths];
long p = 0;
for (int i = 0; i < bt.length; i = i + 2 * 1024 * 1024, p = p + 2 * 1024 * 1024) {
//每次接收2*1024*1024字节的数据,接收到最后不足2*1024*1024字节时,将剩余数据全部接收
if (i + 2 * 1024 * 1024 > bt.length) {
dis.readFully(bt, i, bt.length - i);
//将传输的完成情况显示出来
} else {
dis.readFully(bt, i, 2 * 1024 * 1024);
//将传输的完成情况显示出来
}
// runOnUiThread(new Runnable() {
// @Override
// public void run() {
// progress.setText(String.valueOf(p * 100 / bt.length) + "%");
// }
// });
Log.d(TAG, "Client: "+p * 100 / bt.length + "%");
}
//接收发送过来的文件
File file;
if (vis > 9) {
file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pictures/" + name);
} else {
file = new File("sdcard/" + "Movies/" + name);
}
//文件夹或文件不存在就创建
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
//将字节数组中的数据写入本地已创建好的文件里
FileOutputStream fops = new FileOutputStream(file.getAbsoluteFile());
fops.write(bt);
fops.flush();
fops.close();
dis.close();
dos.close();
// socket.close();
runOnUiThread(new Runnable() {
@Override
public void run() {
fileName.setText("文件("+name+")接收完毕");
progress.setText( "100%");
}
});
Log.d(TAG, "run: 文件接收完毕");
} catch(IOException e){
e.printStackTrace();
}
}
}
}
4.实现效果,这是一个动态过程,这里只截取了最终结果图。
去看看客户端获取到的文件,如下:
大功告成,其他类型文件传输大家可以自行实测,这里就不做演示了。