Saya sudah menulis sesuatu yang mirip dengan ini di masa lalu. Dari penelitian saya tahun lalu menunjukkan bahwa menulis implementasi soket Anda sendiri adalah yang terbaik, menggunakan soket Asynchronous. Ini berarti bahwa klien tidak benar-benar melakukan sesuatu sebenarnya membutuhkan sumber daya yang relatif sedikit. Apa pun yang terjadi ditangani oleh kumpulan .net thread.
Saya menulisnya sebagai kelas yang mengelola semua koneksi untuk server.
Saya hanya menggunakan daftar untuk menampung semua koneksi klien, tetapi jika Anda membutuhkan pencarian yang lebih cepat untuk daftar yang lebih besar, Anda dapat menulisnya sesuka Anda.
private List<xConnection> _sockets;
Anda juga perlu soket yang benar-benar mendengarkan untuk koneksi yang masuk.
private System.Net.Sockets.Socket _serverSocket;
Metode mulai sebenarnya memulai soket server dan mulai mendengarkan untuk setiap koneksi yang masuk.
public bool Start()
{
System.Net.IPHostEntry localhost = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
System.Net.IPEndPoint serverEndPoint;
try
{
serverEndPoint = new System.Net.IPEndPoint(localhost.AddressList[0], _port);
}
catch (System.ArgumentOutOfRangeException e)
{
throw new ArgumentOutOfRangeException("Port number entered would seem to be invalid, should be between 1024 and 65000", e);
}
try
{
_serverSocket = new System.Net.Sockets.Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
}
catch (System.Net.Sockets.SocketException e)
{
throw new ApplicationException("Could not create socket, check to make sure not duplicating port", e);
}
try
{
_serverSocket.Bind(serverEndPoint);
_serverSocket.Listen(_backlog);
}
catch (Exception e)
{
throw new ApplicationException("Error occured while binding socket, check inner exception", e);
}
try
{
//warning, only call this once, this is a bug in .net 2.0 that breaks if
// you're running multiple asynch accepts, this bug may be fixed, but
// it was a major pain in the ass previously, so make sure there is only one
//BeginAccept running
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (Exception e)
{
throw new ApplicationException("Error occured starting listeners, check inner exception", e);
}
return true;
}
Saya hanya ingin mencatat kode penanganan pengecualian terlihat buruk, tetapi alasannya adalah saya memiliki kode penekan pengecualian di sana sehingga pengecualian akan ditekan dan dikembalikan false
jika opsi konfigurasi diatur, tetapi saya ingin menghapusnya untuk demi singkatnya.
The _serverSocket.BeginAccept (AsyncCallback baru (acceptCallback)), _serverSocket) di atas pada dasarnya menetapkan soket server kami untuk memanggil metode acceptCallback setiap kali pengguna terhubung. Metode ini berjalan dari .Net threadpool, yang secara otomatis menangani pembuatan utas pekerja tambahan jika Anda memiliki banyak operasi pemblokiran. Ini harus secara optimal menangani setiap beban di server.
private void acceptCallback(IAsyncResult result)
{
xConnection conn = new xConnection();
try
{
//Finish accepting the connection
System.Net.Sockets.Socket s = (System.Net.Sockets.Socket)result.AsyncState;
conn = new xConnection();
conn.socket = s.EndAccept(result);
conn.buffer = new byte[_bufferSize];
lock (_sockets)
{
_sockets.Add(conn);
}
//Queue recieving of data from the connection
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
//Queue the accept of the next incomming connection
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (SocketException e)
{
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
{
_sockets.Remove(conn);
}
}
//Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (Exception e)
{
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
{
_sockets.Remove(conn);
}
}
//Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
}
Kode di atas pada dasarnya baru saja selesai menerima koneksi yang masuk, antrian BeginReceive
yang merupakan panggilan balik yang akan berjalan ketika klien mengirim data, dan kemudian antrian berikutnya acceptCallback
yang akan menerima koneksi klien berikutnya yang masuk.
Pemanggilan BeginReceive
metode adalah apa yang memberitahu socket apa yang harus dilakukan ketika menerima data dari klien. Untuk BeginReceive
, Anda harus memberikan array byte, di mana ia akan menyalin data ketika klien mengirim data. The ReceiveCallback
metode akan dipanggil, yang adalah bagaimana kita menangani menerima data.
private void ReceiveCallback(IAsyncResult result)
{
//get our connection from the callback
xConnection conn = (xConnection)result.AsyncState;
//catch any errors, we'd better not have any
try
{
//Grab our buffer and count the number of bytes receives
int bytesRead = conn.socket.EndReceive(result);
//make sure we've read something, if we haven't it supposadly means that the client disconnected
if (bytesRead > 0)
{
//put whatever you want to do when you receive data here
//Queue the next receive
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
}
else
{
//Callback run but no data, close the connection
//supposadly means a disconnect
//and we still have to close the socket, even though we throw the event later
conn.socket.Close();
lock (_sockets)
{
_sockets.Remove(conn);
}
}
}
catch (SocketException e)
{
//Something went terribly wrong
//which shouldn't have happened
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
{
_sockets.Remove(conn);
}
}
}
}
EDIT: Dalam pola ini saya lupa menyebutkan bahwa di area kode ini:
//put whatever you want to do when you receive data here
//Queue the next receive
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
Apa yang biasanya saya lakukan adalah dalam kode apa pun yang Anda inginkan, adalah melakukan pemasangan kembali paket menjadi pesan, dan kemudian membuatnya sebagai pekerjaan di kumpulan utas. Dengan cara ini BeginReceive dari blok berikutnya dari klien tidak ditunda sementara kode pemrosesan pesan apa pun berjalan.
Panggilan balik yang diterima selesai membaca soket data dengan menelepon dan menerima. Ini mengisi buffer yang disediakan di fungsi begin accept. Setelah Anda melakukan apa pun yang Anda inginkan di mana saya meninggalkan komentar, kami memanggil BeginReceive
metode berikutnya yang akan menjalankan panggilan balik lagi jika klien mengirim data lagi. Sekarang inilah bagian yang sangat sulit, ketika klien mengirim data, panggilan balik Anda mungkin hanya dipanggil dengan bagian dari pesan. Reassembly bisa menjadi sangat sangat rumit. Saya menggunakan metode saya sendiri dan membuat semacam protokol kepemilikan untuk melakukan ini. Saya meninggalkannya, tetapi jika Anda memintanya, saya dapat menambahkannya. Handler ini sebenarnya adalah kode yang paling rumit yang pernah saya tulis.
public bool Send(byte[] message, xConnection conn)
{
if (conn != null && conn.socket.Connected)
{
lock (conn.socket)
{
//we use a blocking mode send, no async on the outgoing
//since this is primarily a multithreaded application, shouldn't cause problems to send in blocking mode
conn.socket.Send(bytes, bytes.Length, SocketFlags.None);
}
}
else
return false;
return true;
}
Metode pengiriman di atas sebenarnya menggunakan Send
panggilan sinkron , bagi saya itu baik-baik saja karena ukuran pesan dan sifat multithreaded dari aplikasi saya. Jika Anda ingin mengirim ke setiap klien, Anda hanya perlu mengulang daftar _sockets.
Kelas xConnection yang Anda lihat dirujuk di atas pada dasarnya adalah pembungkus sederhana untuk sebuah soket untuk memasukkan buffer byte, dan dalam implementasi saya beberapa tambahan.
public class xConnection : xBase
{
public byte[] buffer;
public System.Net.Sockets.Socket socket;
}
Juga untuk referensi di sini adalah using
s saya sertakan karena saya selalu kesal ketika mereka tidak termasuk.
using System.Net.Sockets;
Saya harap itu membantu, mungkin bukan kode terbersih, tetapi berfungsi. Ada juga beberapa nuansa pada kode yang Anda harus lelah untuk mengubahnya. Untuk satu, hanya memiliki satu panggilan BeginAccept
pada satu waktu. Dulu ada bug .net yang sangat mengganggu di sekitar ini, yang bertahun-tahun lalu jadi saya tidak ingat detailnya.
Juga, dalam ReceiveCallback
kode, kami memproses apa pun yang diterima dari soket sebelum kami mengantri pada penerimaan berikutnya. Ini berarti bahwa untuk satu soket, kami hanya ReceiveCallback
sekali dalam satu waktu, dan kami tidak perlu menggunakan sinkronisasi utas. Namun, jika Anda memesan ulang untuk memanggil penerima berikutnya segera setelah menarik data, yang mungkin sedikit lebih cepat, Anda harus memastikan Anda menyinkronkan utas dengan benar.
Juga, saya meretas banyak kode saya, tetapi meninggalkan esensi dari apa yang terjadi di tempat. Ini harus menjadi awal yang baik untuk desain Anda. Tinggalkan komentar jika Anda memiliki pertanyaan lain tentang ini.