我面临着套接字和线程的奇怪问题。它是一个简单的多线程服务器客户端,只是发送和接收消息。
这是服务器代码:
public class Server
{
Thread t;
Thread listen;
byte[] data1;
byte[] data2;
NetworkStream ns;
TcpClient client;
TcpListener newsock;
public bool Running;
List<ClStr> ListOfStreams = new List<ClStr>();
List<ClientList> ListOfClients = new List<ClientList>();
public Server()
{
Listen = new Thread(new ThreadStart(ExecuteThread));
Listen.Start();
}
private void ExecuteThread()
{
newsock = new TcpListener(Globals.ServerPort);
newsock.Start();
Running = true;
LoopClients();
}
public void LoopClients()
{
try
{
while (Running)
{
client = newsock.AcceptTcpClient();
t = new Thread(new ParameterizedThreadStart(NewClient));
t.Start(client);
ClientList LCli = new ClientList(client);
ListOfClientes.Add(LCli);
}
}
catch (SocketException e)
{
MessageBox.Show(e.ToString());
}
finally
{
newsock.Stop();
}
}
private void NewClient(object obj)
{
TcpClient client = (TcpClient)obj;
ns = client.GetStream();
ClStr Cli = new ClStr(ns);
ListOfStreams.Add(Cli);
string TicketDelivery = "TKT:" + Cli.Ticket.ToString();
byte[] TKTbuffer = Encoding.ASCII.GetBytes(TicketDelivery);
ns.Write(TKTbuffer, 0, TKTbuffer.Length);
while (true)
{
int recv;
data2 = new byte[200 * 1024];
recv = ns.Read(data2, 0, data2.Length);
if (recv == 0)
break;
MessageBox.Show(Encoding.ASCII.GetString(data2, 0, recv));
}
}
请注意,每个新客户端都是新线程。没问题,直到这里。
查看客户端代码。
public class Client
{
Thread Starter;
Socket server;
byte[] data;
string stringData;
int recv;
public int Ticket;
public void Connect()
{
data = new byte[200 * 1024];
IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), Port);
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
server.Connect(ipep);
Starter = new Thread(new ThreadStart(execute));
Starter.Start();
}
catch (SocketException e)
{
MessageBox.Show("Unable to connect to server.");
Starter.Abort();
return;
}
}
public void SendMessageToServer(string msg)
{
server.Send(Encoding.ASCII.GetBytes(msg));
}
private void execute()
{
while (true)
{
data = new byte[200 * 1024];
recv = server.Receive(data);
stringData = Encoding.ASCII.GetString(data, 0, recv);
byte[] byteData = data;
MessageBox.Show(stringData);
}
}
现在问题在于:
我只能从连接到服务器的最后一个客户端向服务器发送消息(使用SendMessageToServer方法)。如果我添加3个客户端,并尝试发送第一个添加的消息,则没有任何反应!似乎客户端没有连接到套接字,但事实并非如此,因为我可以毫无问题地从服务器发送消息。
因此,考虑到服务器为每个客户端创建一个线程,如何从任何连接的客户端向服务器发送消息?
答案 0 :(得分:1)
如果您打算创建异步服务器,我认为您应该使用Async方法。
这是一个非常基本的异步服务器,可以将收到的内容发送给所有连接的客户端。
您可以像这样运行:
Server s = new Server(1338);
s.startListening();
尝试将客户端连接到它:
class Server
{
private int port;
private Socket serverSocket;
private List<StateObject> clientList;
private const int DEFAULT_PORT = 1338;
public Server()
{
this.port = 1338; //default port
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientList = new List<StateObject>();
}
public Server(int port)
{
this.port = port;
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientList = new List<StateObject>();
}
public void startListening(int port = DEFAULT_PORT)
{
this.port = port;
//Bind to our port
serverSocket.Bind(new IPEndPoint(IPAddress.Any, this.port));
//Listen on the port
serverSocket.Listen(1);
//Async Method to wait for a client to connect
serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
}
private void AcceptCallback(IAsyncResult AR)
{
try
{
//A client has connected so create a stateobject for them
//This will hold their buffer and socket
StateObject state = new StateObject();
//This is where the socket is passed to our state
state.workSocket = serverSocket.EndAccept(AR);
//Add this client to our client list
clientList.Add(state);
//Async method to receive data from them (Non Blocking)
state.workSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
//Immediately go back to waiting for a new client (Non Blocking)
serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
}
catch { }
}
private void ReceiveCallback(IAsyncResult AR)
{
//A client has sent us data
//We pass ourselves the state in our previous method
//Now we can retrieve that StateObject from the callback
StateObject state = (StateObject)AR.AsyncState;
//Use this to get the socket so we can retrieve the data from it
Socket s = state.workSocket;
//Variable to hang on to the amount of data we received
int received = 0;
//Call the socket EndReceive Method
received = s.EndReceive(AR);
//If we received no data from the client, they have disconnected
if (received == 0)
{
//Remove them from our clientlist
clientList.Remove(state);
//Tell the other clients that they have left
foreach (StateObject others in clientList)
others.workSocket.Send(Encoding.ASCII.GetBytes("Client disconnected " + s.LocalEndPoint));
//We return here - this callback thread is now dead.
return;
}
//If we received data
if (received > 0) {
//Append the data to our StringBuilder
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, received));
}
//Build the string and let's see if it contains a new line character
string content = state.sb.ToString();
//If the data has a new line character
if(content.Contains("\n") && content.Length > 1)
{
//Send this data to every other client
foreach (StateObject others in clientList)
if (others != state) //Make sure we don't send it back to the sender
others.workSocket.Send(Encoding.ASCII.GetBytes(content.ToCharArray()));
state.sb.Clear(); //Clear their StringBuilder so we can retrieve the next message
}
//Clear the temporary buffer
Array.Clear(state.buffer, 0, StateObject.BufferSize);
//And prepare to receive more data
s.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
}
class StateObject
{
public Socket workSocket = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
}
一些解释:
完全没问题。我也不是很了解他们,所以我很长时间没有理解他们。基本上,回调方法被注册为事件。当某些东西触发它们(比如接收数据)时,服务器执行该功能。调用BeginAccept
可以被认为是在执行链接方法之前创建一个等待连接的新线程。
当收到一个时,它会执行我们作为参数输入的AcceptCallback
方法。从那里,AcceptCallback
再次设置BeginAccept
等待新连接以及等待从客户端发送数据的BeginReceive
。
这些方法都没有阻止执行(将其视为创建新线程并启动它),并且一旦触发它们就会调用它们各自的函数。
为每个客户端分配一个包含其套接字和缓冲区的StateObject,并将它们放在我们可以循环访问的列表中。
StateObject作为带有异步调用的参数传递,这样我们就可以告诉哪个客户端发送了数据并将其存储在一个单独的缓冲区中。
第二次编辑:
您的客户端收到代码看起来没问题。服务器不会响应发送数据的客户端的数据。如果我发送服务器“你好!”它会将该消息转发给所有其他连接的客户端。如果您想尝试接收某些内容,请连接2个客户端。或者,您只需删除代码行if (others != state) //Make sure we don't send it back to the sender
我写了一个客户端/服务器用于聊天,代码可以在这里找到: