网络通信的前提就是客户端和服务器端的通信,在服务器端,程序需要不断的监听客户端是否有连接请求,已保证多个客户端的连接,服务器通过套接字识别客户端;而客户端只需要指定哪个服务器即可。一旦双方建立连接并创建了对应的套接字,就可以互相传输数据了。客户端和服务器端发送和接受数据的方法都是一样的,区别仅是方向不同。
在同步TCP网络应用程序中,发送、接受和监听语句均采用阻塞方式工作,一般有如下步骤:
(1)创建一个包含所采用的网络类型、数据传输类型和协议类型的本地套接字对象,并将其余服务器的IP地址和端口号绑定。可通过Socket类或者TcpListener类完成。
(2)在指定的端口进行监听,以便接受客户端的连接请求。
(3)一旦接受了客户端的连接请求,就根据客户端发送的连接信息创建与该客户端对应的Socket对象或者TcpClient对象。
(4)根据创建的Socket对象或者TcpClient对象,分别与每个连接的客户进行数据传输。
(5)根据传送信息的情况确定是否关闭与对方的连接。
本文的目的是完成前三步,即创建服务器和客户端的连接,服务器将根据对应客户端建立的TcpClient对象得到客户端的信息
服务器端部分代码
IPAddress localAddress;
int port = 51888;
TcpListener myListener;
Service service = new Service(listbox);
IPAddress[] addrIP = Dns.GetHostAddresses(Dns.GetHostName());
localAddress = addrIP[0];
myListener = new TcpListener(localAddress, port);
myListener.Start();
service.SetListBox(string.Format("开始在{0}:{1}监听客户连接", localAddress, port));
ThreadStart ts = new ThreadStart(ListenClientConnect);
Thread myThread = new Thread(ts);
myThread.Start();
我们可以看到建立了一个TcpListener,并且调用了TcpListener.Start();接着启动了一个线程,循环的接受客户端的请求并建立对应的TcpClient对象,看一下ListenClientConnect方法
while (true)
{
TcpClient newClient = null;
try
{
newClient = myListener.AcceptTcpClient();
}
catch
{
break;
}
User user = new User(newClient);
userList.Add(user);
service.SetListBox(string.Format("{0}进入", newClient.Client.RemoteEndPoint));
service.SetListBox(string.Format("当前连接用户数:{0}",userList.Count));
}
其中的while(true)用法看起来比较奇怪,但没什么问题,保证应答多个客户端的请求。
客户端的代码
TcpClient client = null;
try
{
client = new TcpClient(Dns.GetHostName(), 51888);
}
catch
{
MessageBox.Show("与服务器连接失败", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
客户端代码很简单,因为不需要传输数据,这样当服务器端监听到请求后,利用建立的TcpClient对象的到该客户端的信息,通过调用service.SetListBox打印到ListBox中(在游戏中每个窗口都有一个ListBox,使大家看到服务器和客户端交互的一些信息,方便调试)
Service代码
class Service
{
private ListBox listbox;
private delegate void SetListBoxCallback(string str);
private SetListBoxCallback setListBoxCallback;
public Service(ListBox listbox)
{
this.listbox = listbox;
setListBoxCallback = new SetListBoxCallback(SetListBox);
}
public void SetListBox(string str)
{
if (listbox.InvokeRequired)
{
listbox.Invoke(setListBoxCallback, str);
}
else
{
listbox.Items.Add(str);
listbox.SelectedIndex = listbox.Items.Count - 1;
listbox.ClearSelected();
}
}
}
大家一定会对SetListBox的写法比较奇怪,这里实际上是多线程中调用winform 的方法,来看网上的一段话
每一个从Control类中派生出来的WinForm类(包括Control类)都是依靠底层Windows消息和一个消息泵循环(message pump loop)来执行的。消息循环都必须有一个相对应的线程。由于最初消息循环的缘故,只有创建该form的线程才能调用其事件处理方法。
换句话说,如果你在你自己的线程中调用这些方法,则它们会在该线程中处理事件,而不是在创建该form的主线程中进行处理,这时就需要通过Control.Invoke方法返回窗体主线程执行相关操作。
同时,由于程序中大量使用了SetListBox方法,因此将其修改为自动判断是否需要Invoke,而使用该方法时不需要关心此细节。Invoke的第一个参数是一个SetListBoxCallback委托,此处也可以用匿名函数实现,代码更简洁。