mono/mono — pull request #1014 by txdv (2014-05-02 14:33:12+00:00)
Title: Expose full epoll_ctl functionality.

Now the user has access to all struct elements in EpollEvent when
using epoll_ctl. This is in particular very intersting when the
user wants to access the ptr field.

The user can use GCHandle.ToIntPtr to give the struct a GCHandle
and then handle the pointer later on when using epoll_wait.

Here is an example of what you can do:

```csharp

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Mono.Unix.Native;

namespace Test
{
interface IDispatchable
{
void Dispatch(EpollEvent ee);
}

class Epoll
{
public int FileDescriptor { get; protected set; }
public int Size { get; protected set; }

public Epoll(int size)
{
Size = size;
FileDescriptor = Syscall.epoll_create(size);
}

public int Wait(EpollEvent[] events, int max_events, int timeout)
{
return Syscall.epoll_wait(FileDescriptor, events, max_events, timeout);
}

public void Add(int fd, EpollEvents events, IntPtr ptr)
{
Syscall.epoll_ctl(FileDescriptor, EpollOp.EPOLL_CTL_ADD, fd, events, ptr);
}

public void Close()
{
Syscall.close(FileDescriptor);
}

public void Dispatch(int count, int timeout)
{
EpollEvent[] events = new EpollEvent[count];
int n = Wait(events, events.Length, timeout);

for (int i = 0; i < n; i++) {
// and here we can use GCHandle.FromIntPtr to resolve the pointer to an object
var obj = GCHandle.FromIntPtr(events[i].ptr).Target;
if (obj is IDispatchable) {
(obj as IDispatchable).Dispatch(events[i]);
}
}
}
}

public class Loop
{
}

class Tcp : IDispatchable
{
Socket socket;
Epoll loop;

public Tcp(Epoll epoll)
{
loop = epoll;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Blocking = false;
}

public bool IsConnecting { get; private set; }
public bool IsConnected { get; protected set; }

public void Dispatch(EpollEvent ev)
{
if ((ev.events & EpollEvents.EPOLLOUT) > 0) {
if (IsConnecting) {
IsConnecting = false;
IsConnected = true;
OnConnect(null);
} else if (IsConnected) {
Console.WriteLine("Spin");
}
}
}

protected void OnConnect(Exception exception)
{
if (connectCallback != null) {
connectCallback(exception);
}
}

Action connectCallback = null;
public void Connect(IPAddress ipAddress, int port, Action callback)
{
connectCallback = callback;
IsConnecting = true;
try {
socket.Connect(IPAddress.Parse("127.0.0.1"), 8000);
OnConnect(null);
} catch (SocketException ex) {
if (ex.ErrorCode != 10035) {
throw;
}
}

// Here we are passing a gchandle of this object to the event loop
loop.Add(socket.Handle.ToInt32(), EpollEvents.EPOLLIN | EpollEvents.EPOLLOUT | EpollEvents.EPOLLET, GCHandle.ToIntPtr(GCHandle.Alloc(this)));
}

public void Write(byte[] array, int index, int count)
{
Write(new ArraySegment(array, index, count));
}

public void Write(ArraySegment segment)
{
int n = socket.Send(segment.Array, segment.Offset, segment.Count, SocketFlags.None);
if (n < segment.Count) {
// queue
Console.WriteLine(n);
}
}

}

class MainClass
{
public static void Main(string[] args)
{
var thread = new Thread((_) => {
var listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8000);
Console.WriteLine ("Listening");
listener.Start();
var client = listener.AcceptTcpClient();
var sw = new StreamWriter(client.GetStream());
sw.Write("Hello World!");
sw.Flush();

var sr = new StreamReader(client.GetStream());
sr.ReadLine();
client.Close();
Console.WriteLine("Done");
});

thread.Start();

Thread.Sleep(300);

var loop = new Epoll(100);
var t = new Tcp(loop);
t.Connect(IPAddress.Parse("127.0.0.1"), 8000, (ex) => {
var bytes = Encoding.ASCII.GetBytes("Hello World!\n");
t.Write(bytes, 0, bytes.Length);
});

while (true) {
loop.Dispatch(100, 0);
}

thread.Join();
}
}
}
```