개요
안드로이드 클라이언트에서 파이썬 서버에 오디오 파일을 전송하는 기능을 구현해보겠다.
클라이언트 (Android)
1. Layout 구성
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/tv_state"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Wait"/>
<Button
android:id="@+id/btn_send"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="Send"/>
</LinearLayout>
파일을 전송하는 버튼과 전송 상태를 표시하는 텍스트뷰를 구성하였다.
2. Manifest 권한 추가
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
휴대폰에 저장되어 있는 음악파일을 전송하기 위한 외부 저장소 권한과
소켓 통신을 이용하여 서버로 파일을 전송하기 때문에 인터넷 권한을 허용한다.
3. Socket 변수
// Socket
private Socket socket;
private static String SERVER_IP = "192.168.0.169";
private static int SERVER_PORT = 50000;
private static String folderName = "Music";
private static String fileName = "Jeremy Zucker - comethru (Official Video).m4a";
private DataInputStream dis;
private DataOutputStream dos;
private PrintWriter writer;
private BufferedReader br;
서버 IP와 PORT를 설정해주고 Music 폴더에 있는 음악파일을 전송하기 위해 폴더와 파일명을 설정해준다.
4. 권한 요청
private void requestPermission(){
if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(
this,
new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, 0
);
}
}
외부 저장소는 위험권한이기 때문에 권한 요청을 하는 함수를 구현한다.
5. 스레드 실행
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv_state.setText("서버 연결 중");
sThread.start();
}
});
버튼 클릭 시 스레드를 실행한다.
안드로이드에서 네트워크를 연결하는 부분은 반드시 스레드로 구현해야만 한다.
6. 소켓 연결
private Thread sThread = new Thread("Socket Thread"){
@Override
public void run() {
try {
socket = new Socket(SERVER_IP, SERVER_PORT);
dos = new DataOutputStream(socket.getOutputStream());
writer = new PrintWriter(socket.getOutputStream(), true);
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
tv_state.setText("전송 중");
// 파일 경로 가져오기
File filepath = Environment.getExternalStorageDirectory();
String path = filepath.getPath(); // /storage/emulated/0
// 파일 사이즈 추출
File mFile = new File(path+"/"+folderName+"/"+fileName);
long fileSize = mFile.length();
String strFileSize = Long.toString(fileSize);
// 파일명, 파일 사이즈 전송
String fileInfo = fileName+"/"+strFileSize;
writer.printf(fileInfo);
Log.d(TAG, "fileInfo: "+fileInfo);
// 파일 전송
dis = new DataInputStream(new FileInputStream(mFile));
int read;
byte[] buf = new byte[1024];
while((read = dis.read(buf)) > 0) {
dos.write(buf, 0, read);
dos.flush();
}
Log.d(TAG, "Data Transmitted OK!");
String recvData = br.readLine(); // 한 줄씩 받기 때문에 개행문자(\n)를 받아야 대기상태에 안머무름
Log.d(TAG, "recvData : "+recvData);
tv_state.setText(recvData);
dis.close();
dos.close();
writer.close();
socket.close();
} catch (IOException e) {
Log.d(TAG, "No Connect");
tv_state.setText("서버 연결 실패");
e.printStackTrace();
}
}
};
처음에 파일명과 파일 사이즈를 전송한다.
파일 사이즈를 전송하는 이유는 파일을 다 전송했는지 확인하기 위함이다.
전송이 끝나면 서버에서 전송한 메시지를 수신한다.
서버 (Python)
1. import
import socket
import _thread
소켓과 스레드 라이브러리를 import한다.
2. 다중 클라이언트 연결을 위한 스레드 함수 선언
def threaded(conn, addr, group):
print('Connect by',addr)
while True:
try:
data = conn.recv(1024) # 파일명, 사이즈 수신
if not data: # 소켓 연결 끊기면 연결 해제
print('Disconnected by',addr)
group.remove(conn)
break
file_info = data.decode()
file_name, file_size = file_info.split("/")
print('Receive File Path:',file_name)
print('Receive File Size:',file_size)
data = conn.recv(1024) # 파일 수신
data_transferred = len(data)
with open(file_name, "wb") as f:
try:
while data:
f.write(data)
data = conn.recv(1024)
data_transferred += len(data)
if data_transferred == int(file_size): # 파일 다 받으면 break
f.write(data)
break
except Exception as ex:
print(ex)
print("File is saved [byte:"+str(data_transferred)+"]")
sendData = "완료\n"
for c in group:
if c is conn:
c.sendall(bytes(sendData,'UTF-8')) # 수신된 파일을 보낸 Client에게만 전송
print('Send Data : '+sendData,end="")
except:
# 클라이언트 소켓 강제 종료 시 (ex : 네트워크 변경)
print('예외발생')
print('Disconnected by',addr)
group.remove(conn)
break
conn.close()
수신받은 파일을 저장하고 완료되었다는 메시지를 전송한다.
3. 소켓 생성
host = ''
port = 50000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen()
print("Listening")
소켓을 생성한다.
4. 소켓 연결 대기
group = []
while True:
conn, addr = server_socket.accept()
group.append(conn)
_thread.start_new_thread(threaded, (conn, addr, group))
server_socket.close()
print('서버 종료')
소켓 연결을 대기한다.
여러 클라이언트와 연결하기 위한 코드로 구현되어있다.
결과
<Client>
<Server>
Connect by ('192.168.0.37', 49729)
Receive File Path: Jeremy Zucker - comethru (Official Video).m4a
Receive File Size: 2951153
File is saved [byte:2951153]
Send Data : 완료
Disconnected by ('192.168.0.37', 49729)
전체 코드(Android)
<activity_main.xml>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/tv_state"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Wait"/>
<Button
android:id="@+id/btn_send"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="Send"/>
</LinearLayout>
<AndroidManifest.xml>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sendaudiofile">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SendAudioFile">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<MainActivity.java>
package com.example.sendaudiofile;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainTag";
//Button
Button btn_send;
//TextView
TextView tv_state;
// Socket
private Socket socket;
private static String SERVER_IP = "192.168.0.169";
private static int SERVER_PORT = 50000;
private static String folderName = "Music";
private static String fileName = "Jeremy Zucker - comethru (Official Video).m4a";
private DataInputStream dis;
private DataOutputStream dos;
private PrintWriter writer;
private BufferedReader br;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermission();
tv_state = findViewById(R.id.tv_state);
btn_send = findViewById(R.id.btn_send);
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv_state.setText("서버 연결 중");
sThread.start();
}
});
}
private Thread sThread = new Thread("Socket Thread"){
@Override
public void run() {
try {
socket = new Socket(SERVER_IP, SERVER_PORT);
dos = new DataOutputStream(socket.getOutputStream());
writer = new PrintWriter(socket.getOutputStream(), true);
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
tv_state.setText("전송 중");
// 파일 경로 가져오기
File filepath = Environment.getExternalStorageDirectory();
String path = filepath.getPath(); // /storage/emulated/0
// 파일 사이즈 추출
File mFile = new File(path+"/"+folderName+"/"+fileName);
long fileSize = mFile.length();
String strFileSize = Long.toString(fileSize);
// 파일명, 파일 사이즈 전송
String fileInfo = fileName+"/"+strFileSize;
writer.printf(fileInfo);
Log.d(TAG, "fileInfo: "+fileInfo);
// 파일 전송
dis = new DataInputStream(new FileInputStream(mFile));
int read;
byte[] buf = new byte[1024];
while((read = dis.read(buf)) > 0) {
dos.write(buf, 0, read);
dos.flush();
}
Log.d(TAG, "Data Transmitted OK!");
String recvData = br.readLine(); // 한 줄씩 받기 때문에 개행문자(\n)를 받아야 대기상태에 안머무름
Log.d(TAG, "recvData : "+recvData);
tv_state.setText(recvData);
dis.close();
dos.close();
writer.close();
socket.close();
} catch (IOException e) {
Log.d(TAG, "No Connect");
tv_state.setText("서버 연결 실패");
e.printStackTrace();
}
}
};
private void requestPermission(){
if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(
this,
new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, 0
);
}
}
}
전체 코드(Python)
<SendAudioFile.ipynb>
import socket
import _thread
def threaded(conn, addr, group):
print('Connect by',addr)
while True:
try:
data = conn.recv(1024) # 파일명, 사이즈 수신
if not data: # 소켓 연결 끊기면 연결 해제
print('Disconnected by',addr)
group.remove(conn)
break
file_info = data.decode()
file_name, file_size = file_info.split("/")
print('Receive File Path:',file_name)
print('Receive File Size:',file_size)
data = conn.recv(1024) # 파일 수신
data_transferred = len(data)
with open(file_name, "wb") as f:
try:
while data:
f.write(data)
data = conn.recv(1024)
data_transferred += len(data)
if data_transferred == int(file_size): # 파일 다 받으면 break
f.write(data)
break
except Exception as ex:
print(ex)
print("File is saved [byte:"+str(data_transferred)+"]")
sendData = "완료\n"
for c in group:
if c is conn:
c.sendall(bytes(sendData,'UTF-8')) # 수신된 파일을 보낸 Client에게만 전송
print('Send Data : '+sendData,end="")
except:
# 클라이언트 소켓 강제 종료 시 (ex : 네트워크 변경)
print('예외발생')
print('Disconnected by',addr)
group.remove(conn)
break
conn.close()
host = ''
port = 50000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen()
print("Listening")
group = []
while True:
conn, addr = server_socket.accept()
group.append(conn)
_thread.start_new_thread(threaded, (conn, addr, group))
server_socket.close()
print('서버 종료')
마무리
자바와 파이썬 간 소켓 통신 방법에 대해서 알아보았다.
오디오 파일 뿐만 아니라 다른 파일도 같은 방법으로 구현할 수 있다.
GitHub - jaemin-Yoo/SendAudioFile
Contribute to jaemin-Yoo/SendAudioFile development by creating an account on GitHub.
github.com
'개발 > Java & Android' 카테고리의 다른 글
[Java & Android] 안드로이드 통화 녹음 파일 가져와 재생시키기 (0) | 2021.11.01 |
---|---|
[Java & Android] Android 지속적인 음성인식 기능 구현 (SpeechRecognizer) (0) | 2021.10.28 |
[Java & Android] Android SpeechRecognizer API 구현하기 (음성을 텍스트로 변환 - STT변환) (0) | 2021.10.27 |
[Java & Android] 포그라운드 서비스 (Foreground Service) (0) | 2021.10.26 |