我写,我需要用它来连接跳棋游戏我在C#中做了一个客户端和服务器应用程序。 我已经得到了客户端和服务器连接和服务器可以重复发送客户端的消息更新标签,但是当客户端尝试发送邮件,它抛出的错误
“被判发送或接收数据的请求,因为在插座没有被连接和供给,地址(使用sendto调用发送数据报套接字时)”。
这是我的客户端和服务器为止。
CLIENT -
public partial class Form1 : Form //main form that establishes connection
{
Form2 form2 = new Form2();
Socket sck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket acc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint endPoint;
static byte[] Buffer { get; set; }
string text;
public Form1()
{
InitializeComponent();
}
private void button1_Click_1(object sender, EventArgs e)
{
Thread rec = new Thread(recMsg);
Thread t = new Thread(ThreadProc);
t.Start(); //starts a form that will call the sendMsg on a button click
endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8887);
try
{
sck.Connect(endPoint);
}
catch
{
Console.WriteLine("unable to connect");
}
rec.Start();
text = textBox1.Text;
sendMsg(text);
}
public void sendMsg(string s)
{
//bool x = true;
//while (true)
//{
//Thread.Sleep(500);
//if (x == true)
//{
byte[] msgBuffer = Encoding.ASCII.GetBytes(s);
sck.Send(msgBuffer); //error comes here when i try to send a message from form2 says it isn't connected and has no address
// x = false;
//}
//} the commented out part doesn't effect how the send works it sends once and can't again, I think the problem is that the thread which establishes the connection dies but don't know how to solve.
}
public void recMsg()
{
while (true)
{
Thread.Sleep(500);
byte[] Buffer = new byte[255];
int rec = sck.Receive(Buffer, 0, Buffer.Length, 0);
Array.Resize(ref Buffer, rec);
form2.SetText(Encoding.Default.GetString(Buffer));
}
}
private void button2_Click(object sender, EventArgs e)
{
sck.Close();
}
public void ThreadProc()
{
form2.ShowDialog();
}
}
形式2具有LABEL1,TextBox1中和按钮1,主操作形式,将采取输入并调用Form1中SENDMSG()
public partial class Form2 : Form
{
delegate void SetTextCallback(string text);
Form1 form1;
public Form2()
{
InitializeComponent();
}
public void SetText(string text)
{
if (InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
Invoke(d, new object[] { text });
}
else
{
label1.Text = text;
}
}
private void button1_Click(object sender, EventArgs e)
{
form1 = new Form1();
form1.sendMsg(textBox1.Text);
}
}
服务器-
class Program
{
static Form1 form1 = new Form1();
static Form2 form2 = new Form2();
static byte[] buffer { get; set; }
static Socket sck, acc;
static string name;
static void Main(string[] args)
{
if (name == null)
{
Thread t = new Thread(ThreadProc);
t.Start();
}
Thread rec = new Thread(recMsg);
sck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sck.Bind(new IPEndPoint(0, 8887));
Console.WriteLine("Your local IP address is: " + getIP());
Console.WriteLine("Awaiting Connection");
sck.Listen(0);
acc = sck.Accept();
Console.WriteLine(" >> Accept connection from client");
rec.Start();
sendMsg();
}
static string getIP()
{
string hostName = System.Net.Dns.GetHostName();
IPHostEntry ipEntry = System.Net.Dns.GetHostEntry(hostName);
IPAddress[] addr = ipEntry.AddressList;
return addr[addr.Length - 1].ToString();
}
static void recMsg()
{
while (acc.Connected)
{
Thread.Sleep(500);
byte[] Buffer = new byte[255];
int rec = acc.Receive(Buffer, 0, Buffer.Length, 0);
Array.Resize(ref Buffer, rec);
form2.SetText(Encoding.Default.GetString(Buffer));
}
}
public void btnClick(string s)
{
name = s;
Console.WriteLine("Name: " + name);
System.Threading.Thread r = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadProc2));
r.Start();
}
public void sendMSG(string s)
{
try
{
buffer = Encoding.Default.GetBytes(s);
acc.Send(buffer);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
static void sendMsg()
{
try
{
buffer = Encoding.Default.GetBytes(name);
acc.Send(buffer);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
public static void ThreadProc()
{
form1.ShowDialog();
}
public static void ThreadProc2()
{
form2.ShowDialog();
}
}
Form1的 - 起动形式,要求一个名字,这种形式可能只是scrathed用来建立在我最终将需要的跳棋游戏基本的服务器。
public partial class Form1 : Form
{
Program program;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
this.Hide();
program = new Program();
program.btnClick(textBox1.Text);
}
}
窗体2作为像我的客户,基本上都有一个标签,文本框和按钮调用SENDMSG的窗体2的接口()
public partial class Form2 : Form
{
delegate void SetTextCallback(string text);
Program program = new Program();
public Form2()
{
InitializeComponent();
}
public void SetText(string text)
{
if (InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.label2.Text = text;
}
}
private void button1_Click(object sender, EventArgs e)
{
program.sendMSG(textBox1.Text);
}
}
总括来说,这一计划将连接和服务器可以多次将数据发送到客户端,更新的标签。 客户端将数据发送到服务器的第一次,然后它抛出的错误。
为什么不使用异步套接字? 下面是一些代码:
// Server socket
class ControllerSocket : Socket, IDisposable
{
// MessageQueue queues messages to be processed by the controller
public Queue<MessageBase> messageQueue = null;
// This is a list of all attached clients
public List<Socket> clients = new List<Socket>();
#region Events
// Event definitions handled in the controller
public delegate void SocketConnectedHandler(Socket socket);
public event SocketConnectedHandler SocketConnected;
public delegate void DataRecievedHandler(Socket socket, int bytesRead);
public event DataRecievedHandler DataRecieved;
public delegate void DataSentHandler(Socket socket, int length);
public event DataSentHandler DataSent;
public delegate void SocketDisconnectedHandler();
public event SocketDisconnectedHandler SocketDisconnected;
#endregion
#region Constructor
public ControllerSocket(int port)
: base(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
{
// Create the message queue
this.messageQueue = new Queue<MessageBase>();
// Acquire the host address and port, then bind the server socket
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
this.Bind(localEndPoint);
}
#endregion
// Starts accepting client connections
public void StartListening()
{
this.Listen(100);
this.BeginAccept(AcceptCallback, this);
}
// Connects to a client
private void AcceptCallback(IAsyncResult ar)
{
Socket listener = (Socket)ar.AsyncState;
Socket socket = listener.EndAccept(ar);
try
{
// Add the connected client to the list
clients.Add(socket);
// Notify any event handlers
if (SocketConnected != null)
SocketConnected(socket);
// Create an initial state object to hold buffer and socket details
StateObject state = new StateObject();
state.workSocket = socket;
state.BufferSize = 8192;
// Begin asynchronous read
socket.BeginReceive(state.Buffer, 0, state.BufferSize, 0,
ReadCallback, state);
}
catch (SocketException)
{
HandleClientDisconnect(socket);
}
finally
{
// Listen for more client connections
StartListening();
}
}
// Read data from the client
private void ReadCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
Socket socket = state.workSocket;
try
{
if (socket.Connected)
{
// Read the socket
int bytesRead = socket.EndReceive(ar);
// Deserialize objects
foreach (MessageBase msg in MessageBase.Receive(socket, bytesRead, state))
{
// Add objects to the message queue
lock (this.messageQueue)
messageQueue.Enqueue(msg);
}
// Notify any event handlers
if (DataRecieved != null)
DataRecieved(socket, bytesRead);
// Asynchronously read more client data
socket.BeginReceive(state.Buffer, state.readOffset, state.BufferSize - state.readOffset, 0,
ReadCallback, state);
}
else
{
HandleClientDisconnect(socket);
}
}
catch (SocketException)
{
HandleClientDisconnect(socket);
}
}
// Send data to a specific client
public void Send(Socket socket, MessageBase msg)
{
try
{
// Serialize the message
byte[] bytes = msg.Serialize();
if (socket.Connected)
{
// Send the message
socket.BeginSend(bytes, 0, bytes.Length, 0, SendCallback, socket);
}
else
{
HandleClientDisconnect(socket);
}
}
catch (SocketException)
{
HandleClientDisconnect(socket);
}
}
// Broadcast data to all clients
public void Broadcast(MessageBase msg)
{
try
{
// Serialize the message
byte[] bytes = msg.Serialize();
// Process all clients
foreach (Socket client in clients)
{
try
{
// Send the message
if (client.Connected)
{
client.BeginSend(bytes, 0, bytes.Length, 0, SendCallback, client);
}
else
{
HandleClientDisconnect(client);
}
}
catch (SocketException)
{
HandleClientDisconnect(client);
}
}
}
catch (Exception e)
{
// Serialization error
Console.WriteLine(e.ToString());
}
}
// Data sent to a client socket
private void SendCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
if (socket.Connected)
{
// Complete sending the data
int bytesSent = socket.EndSend(ar);
// Notify any attached handlers
if (DataSent != null)
DataSent(socket, bytesSent);
}
else
{
HandleClientDisconnect(socket);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private void HandleClientDisconnect(Socket client)
{
// Client socket may have shutdown unexpectedly
if (client.Connected)
client.Shutdown(SocketShutdown.Both);
// Close the socket and remove it from the list
client.Close();
clients.Remove(client);
// Notify any event handlers
if (SocketDisconnected != null)
SocketDisconnected();
}
// Close all client connections
public void Dispose()
{
foreach (Socket client in clients)
{
if (client.Connected)
client.Shutdown(SocketShutdown.Receive);
client.Close();
}
}
}
// Client socket
class ReceiverSocket : Socket
{
// MessageQueue queues messages to be processed by the controller
public Queue<MessageBase> messageQueue = null;
#region Events
// Event definitions handled in the controller
public delegate void SocketConnectedHandler(Socket socket);
public event SocketConnectedHandler SocketConnected;
public delegate void DataRecievedHandler(Socket socket, MessageBase msg);
public event DataRecievedHandler DataRecieved;
public delegate void DataSentHandler(Socket socket, int length);
public event DataSentHandler DataSent;
public delegate void SocketDisconnectedHandler();
public event SocketDisconnectedHandler SocketDisconnected;
private IPEndPoint remoteEP = null;
#endregion
#region Constructor
public ReceiverSocket(int port)
: base(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
{
// Create the message queue
messageQueue = new Queue<MessageBase>();
// Acquire the host address and port
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
remoteEP = new IPEndPoint(ipAddress, port);
}
#endregion
// Connect to the server
public void Connect()
{
this.BeginConnect(remoteEP, ConnectCallback, this);
}
// Server connected
private void ConnectCallback(IAsyncResult ar)
{
// Console.WriteLine("Connect Callback");
try
{
Socket client = (Socket)ar.AsyncState;
if (client.Connected)
{
client.EndConnect(ar);
// Console.WriteLine("Connect Callback - Connected");
// Create an initial state object to hold buffer and socket details
StateObject state = new StateObject();
state.workSocket = client;
state.BufferSize = 8192;
// Notify any event handlers
if (SocketConnected != null)
SocketConnected(client);
// Begin asynchronous read
client.BeginReceive(state.Buffer, state.readOffset, state.BufferSize - state.readOffset, 0,
ReceiveCallback, state);
}
else
{
// Console.WriteLine("Connect Callback - Reconnect");
Thread.Sleep(5000);
Connect();
}
}
catch (Exception ex)
{
// Attempt server reconnect
Reconnect();
}
}
// Read data from the server
private void ReceiveCallback(IAsyncResult ar)
{
try
{
StateObject state = (StateObject)ar.AsyncState;
Socket client = state.workSocket;
// Read the socket
if (client.Connected)
{
int bytesRead = client.EndReceive(ar);
// Deserialize objects
foreach (MessageBase msg in MessageBase.Receive(client, bytesRead, state))
{
// Add objects to the message queue
lock (this.messageQueue)
this.messageQueue.Enqueue(msg);
}
// Notify any event handlers
if (DataRecieved != null)
DataRecieved(client, null);
// Asynchronously read more server data
client.BeginReceive(state.Buffer, state.readOffset, state.BufferSize - state.readOffset, 0,
ReceiveCallback, state);
}
else
{
Reconnect();
}
}
catch (SocketException)
{
// Attempt server reconnect
Reconnect();
}
}
public void Send(MessageBase msg)
{
try
{
// Serialize the message
byte[] bytes = msg.Serialize();
if (this.Connected)
{
// Send the message
this.BeginSend(bytes, 0, bytes.Length, 0, SendCallback, this);
}
else
{
Reconnect();
}
}
catch (SocketException sox)
{
// Attempt server reconnect
Reconnect();
}
catch (Exception ex)
{
int i = 0;
}
}
private void SendCallback(IAsyncResult ar)
{
try
{
Socket client = (Socket)ar.AsyncState;
if (client.Connected)
{
// Complete sending the data
int bytesSent = client.EndSend(ar);
// Notify any event handlers
if (DataSent != null)
DataSent(client, bytesSent);
}
else
{
Reconnect();
}
}
catch (SocketException)
{
Reconnect();
}
}
// Attempt to reconnect to the server
private void Reconnect()
{
try
{
// Disconnect the original socket
if (this.Connected)
this.Disconnect(true);
this.Close();
// Notify any event handlers
if (SocketDisconnected != null)
SocketDisconnected();
}
catch (Exception e)
{
// Console.WriteLine(e.ToString());
}
}
}
// Encapsulates information about the socket and data buffer
public class StateObject
{
public Socket workSocket = null;
public int readOffset = 0;
public StringBuilder sb = new StringBuilder();
private int bufferSize = 0;
public int BufferSize
{
set
{
this.bufferSize = value;
buffer = new byte[this.bufferSize];
}
get { return this.bufferSize; }
}
private byte[] buffer = null;
public byte[] Buffer
{
get { return this.buffer; }
}
}
所有你需要做的就是插上自己的消息。
请记住,套接字流可以(和大部分的时间做)包含部分的消息。 因此,较好的做法是发送消息的长度作为一个消息的第一个字节。 您还可以通过组装与读取部分信息进行相应的管理读取缓冲区。 看看下面的消息的基类。
public partial class MessageBase
{
// Virtual Execute method following the Command pattern
public virtual string Execute(Socket socket) { return string.Empty; }
protected virtual bool MustEncrypt
{
get { return false; }
}
// Binary serialization
public byte[] Serialize()
{
using (MemoryStream stream = new MemoryStream())
{
using (DeflateStream ds = new DeflateStream(stream, CompressionMode.Compress, true))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ds, this);
ds.Flush();
}
byte[] bytes = stream.GetBuffer();
byte[] bytes2 = new byte[stream.Length];
Buffer.BlockCopy(bytes, 0, bytes2, 0, (int)stream.Length);
// Array.Copy(bytes, bytes2, stream.Length);
if (this.MustEncrypt)
bytes2 = RijndaelEncrptor.Instance.Encrypt(bytes2);
// Create a buffer large enough to hold the encrypted message and size bytes
byte[] data = new byte[8 + bytes2.Length];
// Add the message size
BitConverter.GetBytes(bytes2.Length).CopyTo(data, 0);
BitConverter.GetBytes(this.MustEncrypt).CopyTo(data, 4);
// Add the message data
bytes2.CopyTo(data, 8);
return data;
}
}
static public MessageBase Deserialize(byte[] buffer)
{
int length = BitConverter.ToInt32(buffer, 0);
bool mustDecrypt = BitConverter.ToBoolean(buffer, 4);
MessageBase b = null;
try
{
b = MessageBase.Deserialize(buffer, 8, length, mustDecrypt);
}
catch { }
return b;
}
static public MessageBase Deserialize(byte[] buffer, int offset, int length, bool mustDecrypt)
{
// Create a buffer and initialize it with data from
// the input buffer offset by the specified offset amount
// and length determined by the specified length
byte[] data = new byte[length];
Buffer.BlockCopy(buffer, offset, data, 0, length);
// Array.Copy(buffer, offset, data, 0, length);
// Decrypt message
if (mustDecrypt)
data = RijndaelEncrptor.Instance.Decrypt(data);
// Deserialize the binary data into a new object of type MessageBase
using (MemoryStream stream = new MemoryStream(data))
{
using (DeflateStream ds = new DeflateStream(stream, CompressionMode.Decompress, false))
{
BinaryFormatter formatter = new BinaryFormatter();
try
{
return formatter.Deserialize(ds) as MessageBase;
}
catch
{
return null;
}
}
}
}
static public IEnumerable<MessageBase> Receive(Socket client, int bytesReceived, StateObject state)
{
// Total buffered count is the bytes received this read
// plus any unprocessed bytes from the last receive
int bufferLen = bytesReceived + state.readOffset;
// Reset the next read offset in the case
// this recieve lands on a message boundary
state.readOffset = 0;
// Make sure there are bytes to read
if (bufferLen >= 0)
{
// Initialize the current read position
int readOffset = 0;
// Process the receive buffer
while (readOffset < bufferLen)
{
// Get the message size
int length = BitConverter.ToInt32(state.Buffer, readOffset);
bool mustDecrypt = BitConverter.ToBoolean(state.Buffer, readOffset + 4);
// Increment the current read position by the length header
readOffset += 8;
// Change the buffer size if necessary
if (length + readOffset > state.Buffer.Length)
{
byte[] oldBuffer = new byte[state.BufferSize];
Buffer.BlockCopy(state.Buffer, 0, oldBuffer, 0, state.BufferSize);
// Array.Copy(state.Buffer, oldBuffer, state.BufferSize);
state.BufferSize = length + readOffset;
Buffer.BlockCopy(oldBuffer, 0, state.Buffer, 0, oldBuffer.Length);
// Array.Copy(oldBuffer, state.Buffer, oldBuffer.Length);
}
// Ensure there are enough bytes to process the message
if (readOffset + length <= bufferLen)
yield return MessageBase.Deserialize(state.Buffer, readOffset, length, mustDecrypt);
else
{
// Add back the message length
readOffset -= 8;
// Reorder the receive buffer so unprocessed
// bytes are moved to the start of the array
Buffer.BlockCopy(state.Buffer, 0, state.Buffer, 0, bufferLen - readOffset);
// Array.Copy(state.Buffer, state.Buffer, bufferLen - readOffset);
// Set the receive position to the current read position
// This is the offset where the next socket read will start
state.readOffset = bufferLen - readOffset;
break;
}
// Update the read position by the message length
readOffset += length;
}
}
}
}
上面的代码应该让你去。
你一定要明白你的代码将永远不会退出从这里:
public void sendMsg(string s)
{
bool x = true;
while (true)
{
Thread.Sleep(500);
if (x == true)
{
byte[] msgBuffer = Encoding.ASCII.GetBytes(s);
sck.Send(msgBuffer);
x = false;
}
}
}
这是没有休息或回报,没有失控的方式无限循环。 如何只:
public void sendMsg(string s)
{
while (true)
{
Thread.Sleep(500);
byte[] msgBuffer = Encoding.ASCII.GetBytes(s);
sck.Send(msgBuffer);
}
}
想出了一个解决我的问题可能不是做标准的方式,但它的工作原理。 这是一个可以用来连接玩家vs玩家游戏在线发送来回移动一个简单的聊天应用。
服务器-
using System;
using System.Net.Sockets;
using System.Text;
using System.Net;
using System.Threading;
using System.Windows.Forms;
namespace testServer
{
class Program
{
static Form1 form1 = new Form1();
static Form2 form2 = new Form2();
static byte[] buffer { get; set; }
static Socket sck, acc;
static string name;
public void setName(string s)
{
name = s;
string[] asdf = new string[2];
}
static string getIP()
{
string hostName = System.Net.Dns.GetHostName();
IPHostEntry ipEntry = System.Net.Dns.GetHostEntry(hostName);
IPAddress[] addr = ipEntry.AddressList;
return addr[addr.Length - 1].ToString();
}
static void Main(string[] args)
{
if (name == null)
{
Thread t = new Thread(ThreadProc);
t.Start();
}
sck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sck.Bind(new IPEndPoint(0, 8888));
Console.WriteLine("Your local IP address is: " + getIP());
Console.WriteLine("Awaiting Connection");
sck.Listen(100);
acc = sck.Accept();
Console.WriteLine(" >> Accept connection from client");
sendMsg();
while (acc.Connected)
{
Thread.Sleep(500);
byte[] Buffer = new byte[255];
int receive = acc.Receive(Buffer, 0, Buffer.Length, 0);
Array.Resize(ref Buffer, receive);
form2.SetText(Encoding.Default.GetString(Buffer));
}
}
public void btnClick(string s)
{
name = s;
Console.WriteLine("Name: " + name);
System.Threading.Thread r = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadProc2));
r.Start();
}
public void sendMSG(string s)
{
try
{
buffer = Encoding.Default.GetBytes(s);
acc.Send(buffer);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
static void sendMsg()
{
try
{
buffer = Encoding.Default.GetBytes(name);
acc.Send(buffer);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
public static void ThreadProc()
{
form1.ShowDialog();
}
public static void ThreadProc2()
{
form2.ShowDialog();
}
}
}
这是程序类,其他形式是一样的我原来的职位。
客户-
using System;
using System.Windows.Forms;
using System.Text;
using System.Net.Sockets;
using System.Threading;
namespace testClient100
{
public partial class Form1 : Form
{
System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
NetworkStream serverStream = default(NetworkStream);
string readData = null;
string ipaddress;
public Form1()
{
InitializeComponent();
}
private void getMessage()
{
while (true)
{
serverStream = clientSocket.GetStream();
int buffSize = 0;
byte[] inStream = new byte[10025];
buffSize = clientSocket.ReceiveBufferSize;
serverStream.Read(inStream, 0, buffSize);
string returndata = System.Text.Encoding.ASCII.GetString(inStream);
readData = "" + returndata;
msg();
}
}
private void msg()
{
if (this.InvokeRequired)
this.Invoke(new MethodInvoker(msg));
else
textBox1.Text = textBox1.Text + Environment.NewLine + " >> " + readData;
}
private void button1_Click_1(object sender, EventArgs e)
{
byte[] outStream = System.Text.Encoding.ASCII.GetBytes(textBox2.Text);
serverStream.Write(outStream, 0, outStream.Length);
serverStream.Flush();
}
private void button2_Click_1(object sender, EventArgs e)
{
ipaddress = textBox4.Text;
readData = "Conected to Chat Server ...";
msg();
clientSocket.Connect(ipaddress, 8888);
serverStream = clientSocket.GetStream();
byte[] outStream = System.Text.Encoding.ASCII.GetBytes(textBox3.Text);
serverStream.Write(outStream, 0, outStream.Length);
serverStream.Flush();
Thread ctThread = new Thread(getMessage);
ctThread.Start();
}
}
}
文章来源: Socket single client/server connection, server can send multiple times, client can only once