利用TCP开发网络应用程序,可以采用同步或者异步的方式,这个游戏采用的是同步的工作方式,比较简单,系列教程也主要介绍同步的工作方式。
网络通信的前提就是客户端和服务器端的通信,在服务器端,程序需要不断的监听客户端是否有连接请求,已保证多个客户端的连接,服务器通过套接字识别客户端;而客户端只需要指定哪个服务器即可。一旦双方建立连接并创建了对应的套接字,就可以互相传输数据了。客户端和服务器端发送和接受数据的方法都是一样的,区别仅是方向不同。
在同步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委托,此处也可以用匿名函数实现,代码更简洁。
以上是本文的几个需要注意的地方,本例的内容没有涉及数据传输,比较简单,但却非常基础,希望能够对大家有所帮助。
示例代码下载