서버와 클라이언트는 소켓이라는 보관함을 만든다. 이들은 서로 데이터를 이 보관함으로 주고받으며 통신을 할 수 있다. 이 중 OSI 7 Layers의 네번째 계층의 TCP 계층에서 동작하는 소켓을 쓴다
TCP 특성상 일방적인 요청으로 통신이 이루어지지 않고, 한쪽이 요청하면 한쪽이 받아줘야 한다. 이 때 요청을 받는 쪽이 Server, 요청을 하는 쪽이 Client가 되겠다.
Client Socket
1. create socket
2. connect to server socket
3. send & receive
4. close
Server Socket
1. create socket
2. bind - 사용할 IP 주소와 포트번호를 소켓에 결합
3. listen - 클라이언트로부터 수신요청이 오는지 주시
4. accept - 요청이 수신되면 받아들임 + 데이터 송수신을 위한 소켓 생성
5. sned & receive
6. close
Python과 C#간의 통신 이전에 파이썬끼리 소켓 프로그래밍을 먼저 해보면서 감을 좀 익혔다.
server.py
from socket import *
serverSock = socket(AF_INET, SOCK_STREAM)
serverSock.bind(('', 25001))
serverSock.listen(1)
connectionSock, addr = serverSock.accept()
print('Connection from ',str(addr))
data = connectionSock.recv(1024)
print('Received Data : ', data.decode('utf-8'))
connectionSock.send('Yo I\'m Server'.encode('utf-8'))
print('Message sent successfully')
serverSock.close()
connectionSock.close()
client.py
from socket import *
clientSock = socket(AF_INET, SOCK_STREAM)
clientSock.connect(('127.0.0.1', 25001))
print('connection has been made!')
clientSock.send('I am Client'.encode('utf-8'))
print('Message sent')
data = clientSock.recv(1024)
print('Received Data : ', data.decode('utf-8'))
clientSock.close()
connect(('127.0.0.1', 25001))
AF가 튜플로 인자로 들어감
send(msg.encode('utf-8'))
데이터를 전송하는 함수
* encode() - 문자열을 byte로 변환(문자열은 파이썬 내부에서 작동하는 객체로, 이를 바로 통신에 쓸 수는 없음)
recv(1024)
recv()실행시 소켓에 메세지가 수신될 때까지 대기하게 됨
1024: 소켓에서 1024 bytes만큼 가져오겠다는 의미
* decode('utf-8'): encode와 마찬가지로 byte로 데이터를 수신 후 문자열로 변환해야 함
쓰레드를 이용하여 데이터 송수신을 순서없이 할 수 있도록 한다
server.py
from socket import *
import threading
import time
def send(sock):
while True:
sendingData = input('enter message: ')
sock.send(sendingData.encode('utf-8'))
def receive(sock):
while True:
recvData = sock.recv(1024)
print("client: ", recvData.decode('utf-8'))
port = 25001
serverSock = socket(AF_INET, SOCK_STREAM)
serverSock.bind(('', port))
serverSock.listen(1)
print("%d port waiting for connection..."%port)
connectionSock, addr = serverSock.accept()
print('Connection from ',str(addr))
sender = threading.Thread(target=send, args=(connectionSock,))
receiver = threading.Thread(target=receive, args=(connectionSock,))
# target: 실제로 쓰레드가 실행할 함수
# args: 그 함수에게 전달할 인자 - 튜플같이 iterable한 변수만 입력가능! but 인자가
# 하나면 튜플이 아닌 var로 인식하기에 (soc,)로 입력해야 함
sender.start()
receiver.start()
# start()로 생성되었던 쓰레드가 실행됨, 전부 수행하면 소멸함
# -> 소멸하지 않게 하기위해 while True를 send, receive 함수에 넣음
# 그치만 이 프로세스가 종료되면 분신과 같은 쓰레드 역시 소멸
while True:
time.sleep(1)
pass
# 그냥 pass하면 쉬지않고 코드를 실행함 -> 1초씩 쉬어가며 반복하도록
connectionSock.close()
serverSock.close()
client.py
from socket import *
import threading
import time
def send(sock):
while True:
sendingData = input('enter message: ')
sock.send(sendingData.encode('utf-8'))
def receive(sock):
while True:
recvData = sock.recv(1024)
print("server: ", recvData.decode('utf-8'))
ip = '127.0.0.1'
port = 25001
clientSock = socket(AF_INET, SOCK_STREAM)
clientSock.connect((ip, port))
print('connection has been made!')
sender = threading.Thread(target=send, args=(clientSock,))
receiver = threading.Thread(target=receive, args=(clientSock,))
sender.start()
receiver.start()
while True:
time.sleep(1)
pass
clientSock.close()
여기서
이제 문제는 C#과의 소켓 통신과 리스트를 어떻게 send/recv할 것이냐이다. 소켓으로 보내는 형식은 무조건 byte여야 한다. 리스트를 특정 규칙에 따라 전송하고 receive할 때에도 해당 규칙에 따라 풀어서 받아야 할 듯 하다...
아니 잠만 구체적으로 내가 보내려고 하는 게 뭐지? 이렇게 쓸 시간에 생각을 하는 게 낫지않나? 쓴다고 해결이 되나? 쓰는동안에 이 쓰는 거에 집중을 하게되면서 오히려 생각은 못하고 시간낭비만 하고있는데ㅋㅋㅋ 언제까지 이러나 보자
그만
우선 무조건적으로 보내야 하는 건 "좌표"이다. 이건 문자열로 전송이니까 ㄱㅊ
근데 좌표만 보내는 게 아니지? 그때그때 프레임도 보내야될까? 아니면 영상의 위치와 영상 중 몇번째 프레임인지 숫자를 보내면 되려나?
그래서 준비했습니다...
이미지 송수신시 해당 이미지 데이터의 크기를 같이 보내야 한다!
TCP socket에서 한번에 보낼 수 있는 데이터 크기가 제한적이라 이미지를 string으로 변환하여 보낼 때 이미지 크기를 받고 그 크기만큼 socket에서 데이터를 받아서 다시 이미제 데이터로 변환하는 과정이 필요
근데 나는 이미지 데이터 크기가 다 일정하쥬? 다 2400x496이니까 미리 소켓에서 이만큼으로 받거나 크다 싶으면 잘라서 받아주면 되쥬?
server.py
class ServerSocket:
def __init__(self, ip, port):
self.TCP_IP = ip
self.TCP_PORT = port
self.socketOpen()
self.receiveThread = threading.Thread(target=self.receiveImages)
self.receiveThread.start()
def socketClose(self):
self.sock.close()
print(u'Server socket [ TCP_IP: ' + self.TCP_IP + ', TCP_PORT: ' + str(self.TCP_PORT) + ' ] is close')
def socketOpen(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.TCP_IP, self.TCP_PORT))
self.sock.listen(1)
print(u'Server socket [ TCP_IP: ' + self.TCP_IP + ', TCP_PORT: ' + str(self.TCP_PORT) + ' ] is open')
self.conn, self.addr = self.sock.accept()
print(u'Server socket [ TCP_IP: ' + self.TCP_IP + ', TCP_PORT: ' + str(self.TCP_PORT) + ' ] is connected with client')
def receiveImages(self):
try:
while True:
length = self.recvall(self.conn, 64)
length1 = length.decode('utf-8')
stringData = self.recvall(self.conn, int(length1))
stime = self.recvall(self.conn, 64)
print('send time: ' + stime.decode('utf-8'))
now = time.localtime()
print('receive time: ' + datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'))
data = numpy.frombuffer(base64.b64decode(stringData), numpy.uint8)
decimg = cv2.imdecode(data, 1)
cv2.imshow("image", decimg)
cv2.waitKey(1)
except Exception as e:
print(e)
self.socketClose()
cv2.destroyAllWindows()
self.socketOpen()
self.receiveThread = threading.Thread(target=self.receiveImages)
self.receiveThread.start()
def recvall(self, sock, count):
buf = b''
while count:
newbuf = sock.recv(count)
if not newbuf: return None
buf += newbuf
count -= len(newbuf)
return buf
def main():
server = ServerSocket('localhost', 8080)
if __name__ == "__main__":
main()
client.py - local video 송신
class ClientVideoSocket:
def __init__(self, ip, port, video_path):
self.TCP_SERVER_IP = ip
self.TCP_SERVER_PORT = port
self.video_path = video_path
self.connectCount = 0
self.connectServer()
def connectServer(self):
try:
self.sock = socket.socket()
self.sock.connect((self.TCP_SERVER_IP, self.TCP_SERVER_PORT))
print(u'Client socket is connected with Server socket [ TCP_SERVER_IP: ' + self.TCP_SERVER_IP + ', TCP_SERVER_PORT: ' + str(self.TCP_SERVER_PORT) + ' ]')
self.connectCount = 0
self.sendImages()
except Exception as e:
print(e)
self.connectCount += 1
if self.connectCount == 10:
print(u'Connect fail %d times. exit program'%(self.connectCount))
sys.exit()
print(u'%d times try to connect with server'%(self.connectCount))
time.sleep(1)
self.connectServer()
def sendImages(self):
cnt = 0
capture = cv2.VideoCapture(self.video_path)
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 315)
try:
while capture.isOpened():
ret, frame = capture.read()
resize_frame = cv2.resize(frame, dsize=(480, 315), interpolation=cv2.INTER_AREA)
now = time.localtime()
stime = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')
encode_param=[int(cv2.IMWRITE_JPEG_QUALITY),90]
result, imgencode = cv2.imencode('.jpg', resize_frame, encode_param)
data = numpy.array(imgencode)
stringData = base64.b64encode(data)
length = str(len(stringData))
self.sock.sendall(length.encode('utf-8').ljust(64))
self.sock.send(stringData)
self.sock.send(stime.encode('utf-8').ljust(64))
print(u'send images %d'%(cnt))
cnt+=1
time.sleep(0.02)
except Exception as e:
print(e)
self.sock.close()
time.sleep(1)
self.connectServer()
self.sendImages()
def main():
TCP_IP = 'localhost'
TCP_PORT = 8080
video_path = './big_buck_bunny_720p_10mb.mp4'
client = ClientVideoSocket(TCP_IP, TCP_PORT, video_path)
if __name__ == "__main__":
main()
https://www.youtube.com/watch?v=VeIiU9qTsGI
여기 코드가 있다. python과 c#에서 socket을 사용해서 서로 데이터를 실시간으로 주고받는다는 것. 이 사람은 Unity에서의 스크립트와 파이썬을 연동했고, String으로 주고받았는데, 일단 주고받는 게 된다는 점을 참고하면 좋을 것 같다. 나는 numpy? array? list? 를 Mat으로 주는 것과 좌표정도는 String으로 넘기는 거, 그렇게 둘을 해보면 될 것 같다.
이건 C#의 코드
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
using System.Threading;
public class CSharpForGIT : MonoBehaviour
{
Thread mThread;
public string connectionIP = "127.0.0.1";
public int connectionPort = 25001;
IPAddress localAdd;
TcpListener listener;
TcpClient client;
Vector3 receivedPos = Vector3.zero;
bool running;
private void Update()
{
transform.position = receivedPos; //assigning receivedPos in SendAndReceiveData()
}
private void Start()
{
ThreadStart ts = new ThreadStart(GetInfo);
mThread = new Thread(ts);
mThread.Start();
}
void GetInfo()
{
localAdd = IPAddress.Parse(connectionIP);
listener = new TcpListener(IPAddress.Any, connectionPort);
listener.Start();
client = listener.AcceptTcpClient();
running = true;
while (running)
{
SendAndReceiveData();
}
listener.Stop();
}
void SendAndReceiveData()
{
NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize];
//---receiving Data from the Host----
int bytesRead = nwStream.Read(buffer, 0, client.ReceiveBufferSize); //Getting data in Bytes from Python
string dataReceived = Encoding.UTF8.GetString(buffer, 0, bytesRead); //Converting byte data to string
if (dataReceived != null)
{
//---Using received data---
receivedPos = StringToVector3(dataReceived); //<-- assigning receivedPos value from Python
print("received pos data, and moved the Cube!");
//---Sending Data to Host----
byte[] myWriteBuffer = Encoding.ASCII.GetBytes("Hey I got your message Python! Do You see this massage?"); //Converting string to byte data
nwStream.Write(myWriteBuffer, 0, myWriteBuffer.Length); //Sending the data in Bytes to Python
}
}
public static Vector3 StringToVector3(string sVector)
{
// Remove the parentheses
if (sVector.StartsWith("(") && sVector.EndsWith(")"))
{
sVector = sVector.Substring(1, sVector.Length - 2);
}
// split the items
string[] sArray = sVector.Split(',');
// store as a Vector3
Vector3 result = new Vector3(
float.Parse(sArray[0]),
float.Parse(sArray[1]),
float.Parse(sArray[2]));
return result;
}
/*
public static string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
return ip.ToString();
}
}
throw new System.Exception("No network adapters with an IPv4 address in the system!");
}
*/
}
이건 Python의 코드
import socket
import time
host, port = "127.0.0.1", 25001
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
startPos = [0, 0, 0] #Vector3 x = 0, y = 0, z = 0
while True:
time.sleep(0.5) #sleep 0.5 sec
startPos[0] +=1 #increase x by one
posString = ','.join(map(str, startPos)) #Converting Vector3 to a string, example "0,0,0"
print(posString)
sock.sendall(posString.encode("UTF-8")) #Converting string to Byte, and sending it to C#
receivedData = sock.recv(1024).decode("UTF-8") #receiveing data in Byte fron C#, and converting it to String
print(receivedData)
https://www.youtube.com/watch?v=_k-E5GbOeQg
C#
//Imports libraries
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace SocketClient
{
public partial class Form1 : Form
{
// Main Variables
int port; //Port number of server
string message; //Message to send
int byteCount; //Raw bytes to send
NetworkStream stream; //Link stream
byte[] sendData; //Raw data to send
TcpClient client; //TCP client variable
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
//toolStripButton1 -- Open Connection
private void toolStripButton1_Click(object sender, EventArgs e)
{
//Convert port number to text with error handling
if(!int.TryParse(textBox1.Text, out port))
{
//Adds debug to list box and shows message box
MessageBox.Show("Port number not valied");
listBox1.Items.Add("Port number invalied");
}
//Attemps to make connection
try
{
client = new TcpClient(textBox3.Text, port);
//Adds debug to list box and shows message box
MessageBox.Show("Connection Made");
listBox1.Items.Add("Made connection with " + textBox3.Text);
}
catch (System.Net.Sockets.SocketException)
{
//Adds debug to list box and shows message box
MessageBox.Show("Connection Failed");
listBox1.Items.Add("Connection failed");
}
}
//toolStripButton2 -- Send
private void toolStripButton2_Click(object sender, EventArgs e)
{
try
{
message = textBox2.Text; //Set message variable to input
byteCount = Encoding.ASCII.GetByteCount(message); //Measures bytes required to send ASCII data
sendData = new byte[byteCount]; //Prepares variable size for required data
sendData = Encoding.ASCII.GetBytes(message); //Sets the sendData variable to the actual binary data (from the ASCII)
stream = client.GetStream(); //Opens up the network stream
stream.Write(sendData, 0, sendData.Length); //Transmits data onto the stream
listBox1.Items.Add("Sent Data " + message); //Adds debug to the list box
}
catch (System.NullReferenceException) //Error if socket not open
{
//Adds debug to list box and shows message box
MessageBox.Show("Connection not installised");
listBox1.Items.Add("Failed to send data");
}
}
//toolStripButton3 -- Close Connection
private void toolStripButton3_Click(object sender, EventArgs e)
{
stream.Close(); //Closes data stream
client.Close(); //Closes socket
listBox1.Items.Add("Connection terminated"); //Adds debug message to list box
}
}
}
파이썬
#Imports modules
import socket
import RPi.GPIO as GPIO
import time
listensocket = socket.socket() #Creates an instance of socket
Port = 8000 #Port to host server on
maxConnections = 999
IP = socket.gethostname() #IP address of local machine
listensocket.bind(('',Port))
#Starts server
listensocket.listen(maxConnections)
print("Server started at " + IP + " on port " + str(Port))
#Accepts the incomming connection
(clientsocket, address) = listensocket.accept()
print("New connection made!")
running = True
#Sets up the GPIOs --Can only be used on Raspberry Pi
GPIO.setmode(GPIO.BOARD)
GPIO.setup(7,GPIO.OUT)
while running:
message = clientsocket.recv(1024).decode() #Gets the incomming message
print(message)
if not message == "":
GPIO.output(7,True)
time.sleep(5)
GPIO.output(7,False)
Serialization? 직렬화? 와 유사한 개념이라고 한다. 한 프로세스에서 데이터를 마샬링해서 다른 서버나 프로세스로 내보내면 그걸 받는 곳에서 언마샬링을 해서 쓰는 식으로 통신하는 것이다. 각 프로세스나 프로그램마다 데이터를 적용하는 방식이 다르니 데이터를 주고받을 때에는 표준에 맞게 데이터를 가공해주는? 그런 거라고 한다.
소켓으로 데이터가 일단 넘어가도록 구현한 다음에 생각해 볼 문제!