代码改变世界

基于TCP的网络游戏黑白棋系列(一):建立连接

2008-09-21 13:54  BAsil  阅读(2427)  评论(4编辑  收藏  举报

利用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委托,此处也可以用匿名函数实现,代码更简洁。

以上是本文的几个需要注意的地方,本例的内容没有涉及数据传输,比较简单,但却非常基础,希望能够对大家有所帮助。

示例代码下载