关于linux与android传输代码tcp -传文件
感谢原作者
Android(客户端)与Linux(服务器端)进行TCP数据通信__Programmer_的博客-优快云博客_linux+安卓apptcp图传
原作者提供了linux与android 通过tcp连接 的代码
经过尝试,遇到一些bug,但最终解决,感谢作者的源代码!
本文主要将提取出传输部分,并改造成可以传输文件的代码
源代码(传输msg版)
默认是服务器先启动监听,然后客户端运行,建立连接,然后客户端发什么信息,服务端接受到会显示出来,并且重新发送给客户端。已经内置到自己的应用中,很成功。
服务端
采用的是8080端口
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#define PORT 8080 //服务器端监听端口号
#define MAX_BUFFER 1024 //数据缓冲区最大值
int main()
{
struct sockaddr_in server_addr, client_addr;
int server_sockfd, client_sockfd;
int size, write_size;
char buffer[MAX_BUFFER];
if ((server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) //创建Socket
{
perror("Socket Created Failed!\n");
exit(1);
}
printf("Socket Create Success!\n");
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
bzero(&(server_addr.sin_zero), 8);
int opt = 1;
int res = setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //设置地址复用
if (res < 0)
{
perror("Server reuse address failed!\n");
exit(1);
}
if (bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) //绑定本地地址
{
perror("Socket Bind Failed!\n");
exit(1);
}
printf("Socket Bind Success!\n");
if (listen(server_sockfd, 5) == -1) //监听
{
perror("Listened Failed!\n");
exit(1);
}
printf("Listening ....\n");
socklen_t len = sizeof(client_addr);
printf("waiting connection...\n");
if ((client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len)) == -1) //等待客户端连接
{
perror("Accepted Failed!\n");
exit(1);
}
printf("connection established!\n");
printf("waiting message...\n");
while (1)
{
memset(buffer, 0, sizeof(buffer)); //清空数据缓冲区
if ((size = read(client_sockfd, buffer, MAX_BUFFER)) == -1) //读取客户端的数据
{
perror("Recv Failed!\n");
exit(1);
}
if (size != 0)
{
buffer[size] = '\0';
printf("Recv msg from client: %s\n", buffer);
if ((write_size = write(client_sockfd, buffer, MAX_BUFFER)) > 0) //把收到的数据回发给客户端
{
printf("Sent msg to client successfully!\n");
}
}
}
close(client_sockfd); //关闭Socket
close(server_sockfd);
return 0;
}
编译出可执行文件
g++ con.cpp -o server
运行
./server
移动端
TcpClientConnector.java
package com.google.ar.core.examples.java.helloar;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class TcpClientConnector {
private static TcpClientConnector mTcpClientConnector;
private Socket mClient;
private ConnectListener mListener;
private Thread mConnectThread;
public interface ConnectListener {
void onReceiveData(String data);
}
public void setOnConnectListener(ConnectListener listener) {
this.mListener = listener;
}
public static TcpClientConnector getInstance() {
if (mTcpClientConnector == null)
mTcpClientConnector = new TcpClientConnector();
return mTcpClientConnector;
}
Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 100:
if (mListener != null) {
mListener.onReceiveData(msg.getData().getString("data"));
}
break;
}
}
};
public void createConnect(final String mSerIP, final int mSerPort) {
if (mConnectThread == null) {
mConnectThread = new Thread(new Runnable() {
@Override
public void run() {
try {
connect(mSerIP, mSerPort);
} catch (IOException e) {
e.printStackTrace();
}
}
});
mConnectThread.start();
}
}
/**
* 与服务端进行连接
*
* @throws IOException
*/
private void connect(String mSerIP, int mSerPort) throws IOException {
if (mClient == null) {
mClient = new Socket(mSerIP, mSerPort);
}
InputStream inputStream = mClient.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1) {
String data = new String(buffer, 0, len);
Message message = new Message();
message.what = 100;
Bundle bundle = new Bundle();
bundle.putString("data", data);
message.setData(bundle);
mHandler.sendMessage(message);
}
}
/**
* 发送数据
*
* @param data 需要发送的内容
*/
public void send(String data) throws IOException {
if(mClient!=null)
{
Log.d("socket",mClient.toString());
OutputStream outputStream = mClient.getOutputStream();
outputStream.write(data.getBytes());
}
}
/**
* 断开连接
*
* @throws IOException
*/
public void disconnect() throws IOException {
if (mClient != null) {
mClient.close();
mClient = null;
}
}
}
main_activity.java
// 连接
private TcpClientConnector connector;
....
@Override
protected void onCreate(Bundle savedInstanceState) {
....
// 创建 连接
// 尝试连接
connector = TcpClientConnector.getInstance(); //获取connector实例
if(true){
//连接Tcp服务器端
connector.createConnect("10.112.146.228",8080); //调试使用
connector.setOnConnectListener(new TcpClientConnector.ConnectListener() {
@Override
public void onReceiveData(String data) {
//Received Data,do somethings.
System.out.println(data);
}
});
}else{
try{ //断开与服务器的连接
connector.disconnect();
}catch(IOException e){
e.printStackTrace();
}
}
.....
}
把发送信息的函数,加到你想加的函数里。记得android7以后,tcp连接必须放在线程中
new Thread(new Runnable(){
@Override
public void run() {
try {
connector.send("hello world");
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
源代码(传输文件版)
需求:需要从服务器传输一个文件,到客户端,客户端接收到后,保存至手机中。
服务器需要传输多个文件,客户端需要对每个文件分别命名并进行保存
服务器首先发送启示符”START" 表明传输的开始
然后 下一个块 发送文件名
接下来所有的块都发送文件的块
发送完毕后 ,发送中止符 “END”
代表该文件的发送结束
传输一个文件版
tips
android 8以后禁止往外部存储写文件,使用openFileOutput 可以
自动在data/data/包…/files/…下自动写文件(经尝试,该方法在主线程下可以创建哦)
OutputStream os = openFileOutput("file.txt", Activity.MODE_PRIVATE); String str1 = "abc"; os.write(str1.getBytes("utf-8"));
tips
bug,当服务器发送字节块,采用的是1024字节的byte数组
比如start只会占前5位,后面全填0
而客户端接受的会将整个字节数组转化为字符串,就变成这个样子
解决这个bug有两个方向
一个是找到android端能够截取byte数组的函数
感谢作者[Java中去掉byte]中填充的0,并且转为String_银龙gg的博客-优快云博客
但这样会有点担心,(因为平常的字节流为有0,会被提前中止)
经过探寻 该0为彼0(实际上传递的是0的assci码 48)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HF6EAdki-1668068832149)(%E5%85%B3%E4%BA%8Elinux%E4%B8%8Eandroid%E4%BC%A0%E8%BE%93%E4%BB%A3%E7%A0%81tcp%20-%E4%BC%A0%E6%96%87%E4%BB%B6.assets/image-20221110104956116.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m09BDfRH-1668068832150)(%E5%85%B3%E4%BA%8Elinux%E4%B8%8Eandroid%E4%BC%A0%E8%BE%93%E4%BB%A3%E7%A0%81tcp%20-%E4%BC%A0%E6%96%87%E4%BB%B6.assets/image-20221110105506360.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4EBQI8db-1668068832150)(%E5%85%B3%E4%BA%8Elinux%E4%B8%8Eandroid%E4%BC%A0%E8%BE%93%E4%BB%A3%E7%A0%81tcp%20-%E4%BC%A0%E6%96%87%E4%BB%B6.assets/image-20221110105515496.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-srDbWKnt-1668068832151)(%E5%85%B3%E4%BA%8Elinux%E4%B8%8Eandroid%E4%BC%A0%E8%BE%93%E4%BB%A3%E7%A0%81tcp%20-%E4%BC%A0%E6%96%87%E4%BB%B6.assets/image-20221110105555830.png)]
而ascci码为0的是字符null,根本不需要有这方面的担心(后续还是有bug)
/指定长度100 private byte[] bytes = new byte[100]; //此处省略获取数据 byte[]中的数据实际长度为30 剩余的70将会自动填充0 //直接转为String String str = new String(bytes); //str中前30位为正常字符 后70位为填充 直接页面展示会乱码 System.out.print(str); //通过下面的方法去掉自动填充的0 String str1 = StringUtils.byteToStr(bytes); System.out.print(str1); //正确数据 只有实际的30位 没有填充的乱码 /** * 去掉byte[]中填充的0 转为String * @param buffer * @return */ public static String byteToStr(byte[] buffer) { try { int length = 0; for (int i = 0; i < buffer.length; ++i) { if (buffer[i] == 0) { length = i; break; } } return new String(buffer, 0, length, "UTF-8"); } catch (Exception e) { return ""; } }
一个是增加服务端与客户端发送的逻辑(比如也发送字长,或者为每个块添加中止符什么的)
前面方法已经可以了,因此放弃该思路
bug1
把发送文件块的全部截取成0了…
通过增加逻辑,对于文件名,启示符,结束符进行截取
对于文件块进行不截取的方式,解决问题。
bug2
按说应该传完start和文件名,再传文件块
文件块传完毕后,会传送最后一个文件块,以END开头
但不知为什么,END的块,与最后一个文件包混到了一起
发送完文件块后,等待1s
服务端代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include<string>
#include<iostream>
#define PORT 8080 //服务器端监听端口号
#define MAX_BUFFER 1024 //数据缓冲区最大值
using namespace std;
int main()
{
struct sockaddr_in server_addr, client_addr;
int server_sockfd, client_sockfd;
int size, write_size;
char buffer[MAX_BUFFER];
if ((server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) //创建Socket
{
perror("Socket Created Failed!\n");
exit(1);
}
printf("Socket Create Success!\n");
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
bzero(&(server_addr.sin_zero), 8);
int opt = 1;
int res = setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //设置地址复用
if (res < 0)
{
perror("Server reuse address failed!\n");
exit(1);
}
if (bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) //绑定本地地址
{
perror("Socket Bind Failed!\n");
exit(1);
}
printf("Socket Bind Success!\n");
if (listen(server_sockfd, 5) == -1) //监听
{
perror("Listened Failed!\n");
exit(1);
}
printf("Listening ....\n");
socklen_t len = sizeof(client_addr);
printf("waiting connection...\n");
if ((client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len)) == -1) //等待客户端连接
{
perror("Accepted Failed!\n");
exit(1);
}
printf("connection established!\n");
// 开始传文件
//发送一个文件名
//bytes[]
string start="START";
char buf[1024] = { 0 };
strcpy(buf, start.c_str());
write(client_sockfd,buf,MAX_BUFFER);
memset(buf, 0, sizeof(buf));
string name ="1.txt";
strcpy(buf, name.c_str());
write(client_sockfd,buf,MAX_BUFFER);
memset(buf, 0, sizeof(buf));
int bytes;
FILE* fp = fopen("test1.txt", "rb");
while ((bytes = fread(buf, 1, 1024, fp) )> 0)//读取文件内容,每次一个1024字节
{
cout<<bytes<<endl;
// for(auto byte:buf)
// {
// cout<<byte<<" ";
// }
// cout<<endl;
write(client_sockfd,buf,MAX_BUFFER);//把buf里存的东西send给服务端
memset(buf, 0, sizeof(buf));
}
sleep(1);
string end="END";
strcpy(buf, end.c_str());
for(auto byte:buf)
{
cout<<byte<<" ";
}
cout<<endl;
write(client_sockfd,buf,MAX_BUFFER);
memset(buf, 0, sizeof(buf));
// printf("waiting message...\n");
// while (1)
// {
// memset(buffer, 0, sizeof(buffer)); //清空数据缓冲区
// if ((size = read(client_sockfd, buffer, MAX_BUFFER)) == -1) //读取客户端的数据
// {
// perror("Recv Failed!\n");
// exit(1);
// }
// if (size != 0)
// {
// buffer[size] = '\0';
// printf("Recv msg from client: %s\n", buffer);
// if ((write_size = write(client_sockfd, buffer, MAX_BUFFER)) > 0) //把收到的数据回发给客户端
// {
// printf("Sent msg to client successfully!\n");
// }
// }
// }
close(client_sockfd); //关闭Socket
close(server_sockfd);
return 0;
}
客户端代码
on create中创建连接
// 尝试连接
connector = TcpClientConnector.getInstance(); //获取connector实例
if(true){
//连接Tcp服务器端
connector.createConnect("10.112.146.228",8080); //调试使用
connector.setOnConnectListener(new TcpClientConnector.ConnectListener() {
@Override
public void onReceiveData(String data) {
//Received Data,do somethings.
Log.i("received", String.valueOf(data.length()));
Log.i("received",data);
if(receive_state==0 && data.equals("START")) //准备接受文件名
{
Log.i("file1","文件传输开始,准备接受文件名噢");
os = null;
receive_state=1;
}
else if (receive_state==1)//接受文件名
{
file_name=byteToStr(data.getBytes());
Log.i("file1","接受到文件名:"+file_name);
try {
os = openFileOutput(file_name, Context.MODE_PRIVATE);
} catch (IOException e) {
e.printStackTrace();
}
receive_state=2;
}
else if(receive_state==2 && !data.equals("END") && data.length()!=0) //接受文件块
{
Log.i("file1","正在写入块:"+data.getBytes().length);
try {
os.write(data.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
else if(data.equals("END")) //该文件传输结束
{
Log.i("file1","文件写入结束");
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
os=null;
receive_state=0;
file_name=null;
}
}
});
}else{
try{ //断开与服务器的连接
connector.disconnect();
}catch(IOException e){
e.printStackTrace();
}
}
截取函数
public String byteToStr(byte[] buffer) {
try {
int length = 0;
for (int i = 0; i < buffer.length; ++i) {
if (buffer[i] == 0) {
length = i;
break;
}
}
return new String(buffer, 0, length, "UTF-8");
} catch (Exception e) {
return "";
}
}
TcpClientConnector
package com.google.ar.core.examples.java.helloar;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class TcpClientConnector {
private static TcpClientConnector mTcpClientConnector;
private Socket mClient;
private ConnectListener mListener;
private Thread mConnectThread;
private BufferedOutputStream bos= null;
public interface ConnectListener {
void onReceiveData(String data);
}
public void setOnConnectListener(ConnectListener listener) {
this.mListener = listener;
}
public static TcpClientConnector getInstance() {
if (mTcpClientConnector == null)
mTcpClientConnector = new TcpClientConnector();
return mTcpClientConnector;
}
Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 100:
if (mListener != null) {
mListener.onReceiveData(msg.getData().getString("data"));
}
break;
}
}
};
public void createConnect(final String mSerIP, final int mSerPort) {
if (mConnectThread == null) {
mConnectThread = new Thread(new Runnable() {
@Override
public void run() {
try {
connect(mSerIP, mSerPort);
} catch (IOException e) {
e.printStackTrace();
}
}
});
mConnectThread.start();
}
}
/**
* 去掉byte[]中填充的0 转为String
* @param buffer
* @return
*/
public static String byteToStr(byte[] buffer) {
try {
int length = 0;
for (int i = 0; i < buffer.length; ++i) {
if (buffer[i] == 0) {
length = i;
break;
}
}
return new String(buffer, 0, length, "UTF-8");
} catch (Exception e) {
return "";
}
}
/**
* 与服务端进行连接
*
* @throws IOException
*/
private void connect(String mSerIP, int mSerPort) throws IOException {
if (mClient == null) {
mClient = new Socket(mSerIP, mSerPort);
}
InputStream inputStream = mClient.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1) {
// String data = new String(buffer, 0, len);
Log.i("real_string", String.valueOf(len));//len_byte能看出接受到的块大小
String real_data=new String(buffer,0,len);
// 如果是start,end 就放截取后的
// 其他 放未截取的 文件名则主线程截取
String data =byteToStr(buffer);
Message message = new Message();
message.what = 100;
Bundle bundle = new Bundle();
Log.i("real_string","一块:");
Log.i("real_string",real_data);//real_string表明实际接收到的数据
if (data.equals("START") || data.equals("END"))
{
bundle.putString("data", data);
}
else
{
bundle.putString("data", real_data);
}
message.setData(bundle);
mHandler.sendMessage(message);
}
if (bos!=null){
bos.close();
}
}
/**
* 发送数据
*
* @param data 需要发送的内容
*/
public void send(String data) throws IOException {
if(mClient!=null)
{
Log.d("socket",mClient.toString());
OutputStream outputStream = mClient.getOutputStream();
outputStream.write(data.getBytes());
}
}
/**
* 断开连接
*
* @throws IOException
*/
public void disconnect() throws IOException {
if (mClient != null) {
mClient.close();
mClient = null;
}
}
}