目录
1.项目说明
2.Socket通信
2.1C#实现socket通信
2.2python实现socket通信
3.结果
3.1C#socket通信结果
3.2python和C# socket通信结果
项目使用unity3D搭建环境,并通过python程序控制unity环境。在控制过程中,需要使用socket通信将python处理后的数据发送到unity环境中。由于第一次接触socket通信,因此,首先使用C#搭建了客户端和服务器端进行实验,然后使用python搭建服务器,C#搭建客户端进行实验,最后在unity上进行测试。由于unity和python程序都运行在同一台电脑上,我们使用IP地址为127.0.0.1。整个通信过程是,建立连接后,客户端向服务器端发送字符串“location”,服务器端接收到字符串后,将一个坐标发送到客户端。
Socket封装了TCP/IP协议,可以视为TCP/IP协议向程序员开发所提供的接口,从而简化了网络编程。Socket在网络通信中的位置如图所示。
我们使用的是异步socket通信,主要流程为建立socket连接,收发数据,关闭连接。
(1)引入命名空间
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
(2)建立socket对象和事件状态
由于异步通信,当socket进行通信时,主线程将会执行后面的内容。但后面的程序可能需要在 socket通信之后才能进行,因此需要事件状态来阻塞主线程的运行。
public Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public static ManualResetEvent allDone = new ManualResetEvent(false);
private static ManualResetEvent receiveDone =
new ManualResetEvent(false);
private static ManualResetEvent connectDone = new ManualResetEvent(false);
private static ManualResetEvent sendDone =
new ManualResetEvent(false);
private static String response = String.Empty;
static void Main(string[] args)
(3)建立socket连接
biginconnect对远程主机发送建立连接的异步请求,这里异步方法除了一般的参数外,还有两个参数,一个是回调函数,另一个是状态对象,状态对象向回调函数提供异步通信的状态,对应于回调函数中的参数IAsyncResult。IAsyncResult是接口,里面有几个属性用来查看可以查看异步操作的状态:AsyncState 获取用户定义的对象,它限定或包含关于异步操作的信息。回调函数主要对后续任务和通信的状态进行处理。回调函数中,应调用 EndConnect 方法。 当应用程序调用 BeginConnect 时,系统将使用单独的线程执行指定的回调方法,并在 EndConnect 上一直阻止到 Socket成功连接或引发异常为止。 此外,我们不希望在建立socket连接时,主线程执行其他任务,因此主线程中使用waitone进行阻塞。当需要原始线程继续执行时,在回调方法中调用ManualResetEvent 的 Set 方法。
string address = "127.0.0.1";
IPAddress hostIP = IPAddress.Parse(address);
int port = 50088;
clientSocket.BeginConnect(hostIP, port, new AsyncCallback(connectCallback), clientSocket);
connectDone.WaitOne();
private void connectCallback(IAsyncResult asyncConnect)
{
try
{
Socket client = (Socket)asyncConnect.AsyncState;
client.EndConnect(asyncConnect);
Console.WriteLine("Socket connected to {0}", client.RemoteEndPoint.ToString());
// Signal that the connection has been made.
connectDone.Set();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
(4)发送数据
在成功建立连接后,客户端向服务器发送字符串“location”,首先将字符串进行ASCII编码,并调用异步方法beginsend发送数据,该异步方法同样有回调函数,对发送状态进行处理,解除主线程的阻塞。
Send(clientSocket, content);
sendDone.WaitOne();
private static void Send(Socket handler, String data)
{
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.ASCII.GetBytes(data);
// Begin sending the data to the remote device.
handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
}
private static void SendCallback(IAsyncResult ar)
{
try
{
// Retrieve the socket from the state object.
Socket handler = (Socket)ar.AsyncState;
// Complete sending the data to the remote device.
int bytesSent = handler.EndSend(ar);
Console.WriteLine("Sent {0} bytes to server.", bytesSent);
sendDone.Set();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
(5)接收数据
服务器接收到“location”后,将坐标(12.3,13.6)发送给客户端。因此客户端开始接收数据。首先创建stateobject对象存放接收的数据对象,然后调用异步方法beginreceive。在回调函数中,由于传送的数据量可能大于buffer容量,因此需要多次读取,才能接收到全部的数据,因此使用if (state.sb.Length > 1)进行判断,如果满足条件,则再次调用beginrreceive方法。
Receive(clientSocket);
receiveDone.WaitOne();
private static void Receive(Socket client)
{
try
{
// Create the state object.
StateObject state = new StateObject();
state.workSocket = client;
// Begin receiving the data from the remote device.
Console.WriteLine("async receive location");
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private static void ReceiveCallback(IAsyncResult ar)
{
try
{
// Retrieve the state object and the client socket
// from the asynchronous state object.
StateObject state = (StateObject)ar.AsyncState;
Socket client = state.workSocket;
// Read data from the remote device.
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
// Get the rest of the data.
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
else
{
// All the data has arrived; put it in response.
if (state.sb.Length > 1)
{
response = state.sb.ToString();
Console.WriteLine(response.ToString());
}
// Signal that all bytes have been received.
receiveDone.Set();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
(6) 关闭套接字连接
项目中仅需要进行坐标的传递,在接收到坐标后即可关闭套接字连接。clientSocket.Shutdown通知服务器端或客户端停止接收和发送数据,参数SocketShutdown.Both意味着客服端和服务器端都停止。
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
(7)服务器端
以上操作都是针对客户端的,而服务器端收发数据的操作也大致相同,服务器端和客户端的套接字对应相同的IP和端口,其余主要的区别是套接字建立连接时的操作不同。服务器端需要将套接字和IP端口绑定。BeginAccept开始一个异步操作来接受一个传入的连接尝试。回调函数中,套接字连接成功后,从客户端接收数据,接收到“location”时,再将坐标发送过。
IPAddress local = IPAddress.Parse("127.0.0.1");
IPEndPoint iep = new IPEndPoint(local, 50088);
server.Bind(iep);
server.Listen(100);
while (true)
{
allDone.Reset();
Console.WriteLine("Waiting for a connection...");
server.BeginAccept(new AsyncCallback(Acceptcallback), server);
allDone.WaitOne();
}
public static void Acceptcallback(IAsyncResult iar)
{
// Signal the main thread to continue.
//还原传入的原始套接字
Socket listener = (Socket)iar.AsyncState;
//在原始套接字上调用EndAccept方法,返回新的套接字
Socket handler = listener.EndAccept(iar);
// Create the state object.
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
receiveDone.WaitOne();
allDone.Set();
// Create the state object.
//StateObject state = new StateObject();
//state.workSocket = handler;
//handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
}
(8)C#代码链接
https://github.com/wzyzyw/Socket
python主要介绍关于服务器端的创建。
(1)创建socket对象并绑定IP端口
s=socket.socket()
s.bind(('127.0.0.1',50088))
s.listen()
(2)接收客户端的请求连接
accept()
接受一个客户端的连接请求,并返回一个新的套接字,与客户端通信是通过这个新的套接字上发送和接收数据来完成的。
c, addr = s.accept()
(3)从套接字读取数据并发送数据
c.recv(1024).decode('ascii')
c.send(str_content.encode('ascii'))
(4)完整python代码
import socket
s=socket.socket()
s.bind(('127.0.0.1',50088))
s.listen()
sync_robot_loc=[12.3,13.6]
def def_socket_thread():
# global loop
# loop = asyncio.get_event_loop()
try:
while True:
c, addr = s.accept()
content = read_from_client(c)
if content.find('location') > -1:
global sync_robot_loc
print('receive request')
print('sycn position=', sync_robot_loc)
x = sync_robot_loc[0]
y = sync_robot_loc[1]
str_content = 'x=' + str(x) + ',y=' + str(y)
c.send(str_content.encode('ascii'))
print('finish location send')
else:
print('no request')
except IOError as e:
print(e.strerror)
print('start socket thread!!!')
def read_from_client(c):
try:
return c.recv(1024).decode('ascii')
except IOError as e:
# 如果异常的话可能就是会话中断 那么直接删除
print(e.strerror)
if __name__=='__main__':
def_socket_thread()
consoleapp1是客户端显示的结果,consoleapp2是服务器端显示的结果,意味着成功实现了socket通信。
下图是python服务器端的结果
下图是C#客户端的结果
意味着成功实现了socket通信。