2010-11-10 32 views
17

Tôi có một lớp máy chủ UDP không đồng bộ với một ổ cắm ràng buộc trên IPAddress.Any, và tôi muốn biết IPAddress gói nhận được đã được gửi đến (... hoặc nhận được trên). Có vẻ như tôi không thể chỉ sử dụng thuộc tính Socket.LocalEndPoint, vì nó luôn trả về 0.0.0.0 (có nghĩa là nó bị ràng buộc với điều đó ...).C# Ổ cắm UDP: Nhận địa chỉ người nhận

Dưới đây là những phần thú vị của mã Tôi hiện đang sử dụng:

private Socket udpSock; 
private byte[] buffer; 
public void Starter(){ 
    //Setup the socket and message buffer 
    udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 
    udpSock.Bind(new IPEndPoint(IPAddress.Any, 12345)); 
    buffer = new byte[1024]; 

    //Start listening for a new message. 
    EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); 
    udpSock.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, udpSock); 
} 

private void DoReceiveFrom(IAsyncResult iar){ 
    //Get the received message. 
    Socket recvSock = (Socket)iar.AsyncState; 
    EndPoint clientEP = new IPEndPoint(IPAddress.Any, 0); 
    int msgLen = recvSock.EndReceiveFrom(iar, ref clientEP); 
    byte[] localMsg = new byte[msgLen]; 
    Array.Copy(buffer, localMsg, msgLen); 

    //Start listening for a new message. 
    EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); 
    udpSock.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, udpSock); 

    //Handle the received message 
    Console.WriteLine("Recieved {0} bytes from {1}:{2} to {3}:{4}", 
         msgLen, 
         ((IPEndPoint)clientEP).Address, 
         ((IPEndPoint)clientEP).Port, 
         ((IPEndPoint)recvSock.LocalEndPoint).Address, 
         ((IPEndPoint)recvSock.LocalEndPoint).Port); 
    //Do other, more interesting, things with the received message. 
} 

Như đã đề cập trước đó này luôn in một dòng như:

nhận 32 byte từ 127.0.0.1:1678 để 0.0.0.0: 12345

Và tôi muốn nó trở thành một cái gì đó như:

nhận 32 byte từ 127.0.0.1:1678 để 127.0.0.1:12345

Cảm ơn, trước, đối với bất kỳ ý tưởng về vấn đề này! --Adam

CẬP NHẬT

Vâng, tôi tìm thấy một giải pháp, mặc dù tôi không thích nó ... Về cơ bản, thay vì mở một socket UDP đơn ràng buộc để IPAddress.Any, tôi tạo ra một ổ cắm duy nhất cho mọi IPAddress có sẵn. Vì vậy, chức năng khởi xướng mới trông giống như:

public void Starter(){ 
    buffer = new byte[1024]; 

    //create a new socket and start listening on the loopback address. 
    Socket lSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 
    lSock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345); 

    EndPoint ncEP = new IPEndPoint(IPAddress.Any, 0); 
    lSock.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref ncEP, DoReceiveFrom, lSock); 

    //create a new socket and start listening on each IPAddress in the Dns host. 
    foreach(IPAddress addr in Dns.GetHostEntry(Dns.GetHostName()).AddressList){ 
     if(addr.AddressFamily != AddressFamily.InterNetwork) continue; //Skip all but IPv4 addresses. 

     Socket s = new Socket(addr.AddressFamily, SocketType.Dgram, ProtocolType.Udp); 
     s.Bind(new IPEndPoint(addr, 12345)); 

     EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); 
     s.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, s); 
    } 
} 

Đây chỉ là để minh họa cho khái niệm, vấn đề lớn nhất với mã này như là, là mỗi ổ cắm đang cố gắng sử dụng cùng một bộ đệm ... mà nói chung là một ý tưởng tồi ...

Phải có giải pháp tốt hơn cho điều này; Ý tôi là, nguồn và đích là một phần của tiêu đề gói UDP! Oh well, tôi đoán tôi sẽ đi với điều này cho đến khi có một cái gì đó tốt hơn.

Trả lời

14

Tôi chỉ có cùng một vấn đề. Tôi không thấy cách nào, sử dụng ReceiveFrom hoặc các biến thể không đồng bộ của nó, để truy xuất địa chỉ đích của gói nhận được.

Tuy nhiên ...Nếu bạn sử dụng ReceiveMessageFrom hoặc các biến thể của nó, bạn sẽ nhận được một IPPacketInformation (bằng cách tham chiếu cho ReceiveMessageFromEndReceiveMessageFrom hoặc làm tài sản của SocketAsyncEventArgs được chuyển đến cuộc gọi lại của bạn theo số ReceiveMessageFromAsync). Đối tượng đó sẽ chứa địa chỉ IP và số giao diện mà gói tin được nhận.

(Lưu ý, mã này chưa được thử nghiệm, như tôi đã từng ReceiveMessageFromAsync chứ không phải là fakey giả Bắt đầu/kết thúc cuộc gọi.)

private void ReceiveCallback(IAsyncResult iar) 
{ 
    IPPacketInformation packetInfo; 
    EndPoint remoteEnd = new IPEndPoint(IPAddress.Any, 0); 
    SocketFlags flags = SocketFlags.None; 
    Socket sock = (Socket) iar.AsyncState; 

    int received = sock.EndReceiveMessageFrom(iar, ref flags, ref remoteEnd, out packetInfo); 
    Console.WriteLine(
     "{0} bytes received from {1} to {2}", 
     received, 
     remoteEnd, 
     packetInfo.Address 
    ); 
} 

Lưu ý, bạn rõ ràng nên gọi SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true) như một phần của thiết lập socket, trước khi bạnBind. Các phương thức ... ReceiveMessageFrom ... sẽ thiết lập nó cho bạn, nhưng có thể bạn sẽ chỉ thấy thông tin hợp lệ trên bất kỳ gói nào mà Windows đã thấy sau khi tùy chọn được thiết lập. (Trong thực tế, đây không phải là vấn đề lớn - nhưng khi/nếu nó đã từng xảy ra, lý do sẽ là một bí ẩn. Tốt hơn để ngăn chặn nó hoàn toàn.)

+0

Nó đã được thử nghiệm bây giờ giao phối và nó hoạt động. Đây là câu trả lời tốt nhất cho một số câu hỏi về chủ đề này. Tôi rất muốn xem phiên bản ReceiveMessageFromAsync nhưng có lẽ có thể tự làm việc đó nếu bạn không trả lời. –

+0

@StephenKennedy: Tôi không nghĩ rằng tôi thậm chí có mã đó xung quanh nữa, phải trung thực. : P Nó không phải là phức tạp khủng khiếp, mặc dù, IIRC; chỉ cần thiết lập một 'SocketAsyncEventArgs', đính kèm một trình xử lý vào sự kiện' Completed', sau đó chuyển đối tượng đó tới 'ReceiveMessageFromAsync'. – cHao

+0

Không phải lo lắng. Tôi có SendToAsync() làm việc để có thể chơi với ReceiveMessageFromAsync() tmw :) Cảm ơn. –

0

Tôi nghĩ rằng nếu bạn liên kết với 127.0.0.1 thay vì IPAddress.Any bạn sẽ nhận được hành vi mà bạn muốn.

0.0.0.0 cố tình có nghĩa là "mọi địa chỉ IP có sẵn" và nó sẽ mang nó theo nghĩa đen là hậu quả của tuyên bố ràng buộc của bạn.

+1

Trong khi thực, tôi không muốn liên kết với 127.0.0.1 (hoặc bất kỳ Địa chỉ IP cụ thể nào khác). Tôi xin lỗi nếu tôi không rõ, nhưng lý tưởng, tôi muốn chạy máy chủ UDP này trên một máy có nhiều bộ điều hợp mạng (lắng nghe tất cả chúng), và tôi muốn biết gói nào được gửi đi. – chezy525

-1

Adam

này không được kiểm tra ... chúng ta hãy cố gắng

private void DoReceiveFrom(IAsyncResult iar){ 
//Get the received message. 
Socket recvSock = (Socket)iar.AsyncState; 
//EndPoint clientEP = new IPEndPoint(IPAddress.Any, 0); 
Socket clientEP = recvSock.EndAccept(iar); 
int msgLen = recvSock.EndReceiveFrom(iar, ref clientEP); 
byte[] localMsg = new byte[msgLen]; 
Array.Copy(buffer, localMsg, msgLen); 

//Start listening for a new message. 
EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); 
udpSock.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, udpSock); 

//Handle the received message 
/* 
Console.WriteLine("Recieved {0} bytes from {1}:{2} to {3}:{4}", 
        msgLen, 
        ((IPEndPoint)recvSock.RemoteEndPoint).Address, 
        ((IPEndPoint)recvSock.RemoteEndPoint).Port, 
        ((IPEndPoint)recvSock.LocalEndPoint).Address, 
        ((IPEndPoint)recvSock.LocalEndPoint).Port); 
//Do other, more interesting, things with the received message. 
*/ 
Console.WriteLine("Recieved {0} bytes from {1}:{2} to {3}", 
        msgLen, 
        ((IPEndPoint)recvSock.RemoteEndPoint).Address, 
        ((IPEndPoint)recvSock.RemoteEndPoint).Port, 
        clientEP.RemoteEP.ToString(); 
} 
+1

Cảm ơn, nhưng tôi khá chắc chắn mã này sẽ không hoạt động, hoặc thậm chí biên dịch ... bạn không thể kết thúc một cuộc gọi chấp nhận async (Socket.EndAccept) mà không cần bắt đầu một (Socket.BeginAccept), và chấp nhận doesn ' t có ý nghĩa trên một ổ cắm không kết nối. Ngoài ra, các cuộc gọi Socket.EndReceiveFrom yêu cầu tham chiếu đến một EndPoint, không phải là một Socket. – chezy525

0

Một cách bạn sẽ nhận được một địa chỉ từ ổ cắm đó sẽ là để kết nối nó với người gửi. Khi bạn làm điều đó, bạn sẽ có thể nhận địa chỉ cục bộ (hoặc ít nhất, một địa chỉ có thể định tuyến cho người gửi), tuy nhiên, bạn sẽ chỉ có thể nhận tin nhắn từ điểm cuối được kết nối.

Để ngắt kết nối, bạn sẽ cần sử dụng lại kết nối, lần này chỉ định địa chỉ với họ là AF_UNSPEC. Thật không may, tôi không biết làm thế nào điều này sẽ đạt được trong C#.

(Disclaimer: Tôi chưa bao giờ viết một dòng của C#, điều này áp dụng cho Winsock nói chung)

0

Về vấn đề bộ đệm, hãy thử như sau:

Tạo một lớp có tên StateObject để lưu trữ bất kỳ dữ liệu nào bạn muốn có trong bộ gọi lại, với bộ đệm, cũng bao gồm cả ổ cắm nếu bạn cần (như tôi thấy bạn hiện đang chuyển udpSock làm stateObject). Vượt qua đối tượng mới được tạo thành phương thức async và sau đó bạn sẽ có quyền truy cập vào đối tượng đó trong cuộc gọi lại của bạn.

public void Starter(){ 
    StateObject state = new StateObject(); 
    //set any values in state you need here. 

    //create a new socket and start listening on the loopback address. 
    Socket lSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 
    lSock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345); 

    EndPoint ncEP = new IPEndPoint(IPAddress.Any, 0); 
    lSock.BeginReceiveFrom(state.buffer, 0, state.buffer.Length, SocketFlags.None, ref ncEP, DoReceiveFrom, state); 

    //create a new socket and start listening on each IPAddress in the Dns host. 
    foreach(IPAddress addr in Dns.GetHostEntry(Dns.GetHostName()).AddressList){ 
     if(addr.AddressFamily != AddressFamily.InterNetwork) continue; //Skip all but IPv4 addresses. 

     Socket s = new Socket(addr.AddressFamily, SocketType.Dgram, ProtocolType.Udp); 
     s.Bind(new IPEndPoint(addr, 12345)); 

     EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); 

     StateObject objState = new StateObject(); 
     s.BeginReceiveFrom(objState.buffer, 0, objState.buffer.length, SocketFlags.None, ref newClientEP, DoReceiveFrom, objState); 
    } 
} 

Trong tìm kiếm câu hỏi này tôi thấy:

http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.beginreceivefrom.aspx

Sau đó bạn có thể cast StateObject từ AsyncState như bạn đang làm với udpSock và đệm của bạn, cũng như anyother dữ liệu bạn cần sẽ được lưu trữ ở đó.

Tôi cho rằng vấn đề duy nhất là cách và nơi lưu trữ dữ liệu, nhưng vì tôi không biết triển khai của bạn nên tôi không thể trợ giúp ở đó.

Các vấn đề liên quan