不想工作了,快挂了

Filed under: 未分类 | 10 Comments »
Posted on

郁闷,最近项目有点紧,不停得coding,连续一个多周都是1点多睡觉,明天还要给几个客户发个稳定的客户端,今天晚上又不知道几点睡。

偶然间翻出来在海南旅游的照片,贴几张看看

p.s 最近特别喜欢听王若琳的歌,感觉她的声音比较独特,慵懒但不失清新的曲风,陶醉ing

注意IIS 7.0的集成管道模式下HttpApplication 请求管道事件的问题

Filed under: 未分类 | 1 Comment »
Posted on

今天打算把Community Server 2.0在vista IIS 7.0下跑起来,设置完虚拟目录后浏览,发现HttpContext.Current.Request抛出了System.Web.HttpException,检查了一下IIS下设置成集成管道模式,换成经典管道模式,则不抛出异常,百思不得其解,后来发现msdn上有如下一段话

不包含自定义模块或处理程序的 Web 应用程序通常无需更改即可在 IIS 7.0 集成模式下正常工作。对于依赖于自定义模块或处理程序的 Web 应用程序,需要执行以下步骤来使其能够在集成模式下运行:

而CommunityServer 的写法如下
Global.ascx.cs

protected void Application_Start(Object sender, EventArgs e)
{
    Jobs.Instance().Start();
    EventLogs.Info("CS.Web Started", "Application", 200);
}

Jobs会调用HttpContext.Current.Request,在集成管道模式下抛出异常。
还有一个问题,在集成管道模式下涉及到CS的url rewriting全部不起作用,找不到路径,我开始试图修改处理程序映射中的“检查文件是否存在”取消打勾,不过却找不到该选项。
后来发现换成经典管道模式也正常了。这里记录一下,给有相同问题的朋友提个醒。

因博主回家抱孩子去了,本博停止更新一周!!

Filed under: 未分类 | 2 Comments »
Posted on

晚上11:45的火车,现在是21:10分,上来冒个泡!

基于TCP的网络游戏黑白棋系列(二):数据传输

Filed under: 未分类 | 4 Comments »
Posted on

上一节给大家演示了建立连接的关键代码,连接建立好后,就可以进行数据传输了。数据传输包含从服务器端到客户端和从客户端到服务器端,两者差别不大。

数据的传输,TcpClient的GetNetworkStream是关键,通过它我们可以得到NetworkStream网络流,客户端和服务器主要的工作就是对其读出和写入。关于如何构造稳定且性能好的网络应用,如何进行复杂的封包和解包,这里我们不考虑,我们使用StreamReader和StreamWriter来封装NetStream,读取和写入的代码如下

?View Code CSHARP
            TcpClient client;
            StreamReader sr;
            StreamWriter sw;
            this.userName = "";
            NetworkStream netStream = client.GetStream();
            sr = new StreamReader(netStream, System.Text.Encoding.UTF8);
            sw = new StreamWriter(netStream, System.Text.Encoding.UTF8);
            //读取数据
            string output=sr.ReadLine();
            //写入数据
            string input="";
            sw.WriteLine();
            sw.Flush();

客户端和服务器的通信一般是由客户端向服务器端发请求,服务器端接受请求并处理,再将结果重新发回客户端

发送请求和应答的协议格式可以自己定义,游戏中使用的协议格式为  命令,参数1,参数2……

先看一下客户端请求登录

?View Code CSHARP
    service.SendToServer("Login," + textBoxName.Text.Trim());
    class Service
    {
        ListBox listbox;
        StreamWriter sw;
        public Service(ListBox listbox, StreamWriter sw)
        {
            this.listbox = listbox;
            this.sw = sw;
 
        }
        public void SendToServer(string str)
        {
 
            sw.WriteLine(str);
            sw.Flush();
 
        }
 
    }

服务器接受请求并处理,一般来说是根据不同的请求创建不同的线程进行处理,见本节后半部分

?View Code CSHARP
        private void ReceiveData(object obj)
        {
         //User类封装了TcpClient,StreamReader和StreamWriter
         User user = (User)obj;
         TcpClient client = user.client;
         string receiveString = null;
         receiveString = sr.ReadLine();
         service.SetListBox(string.Format("来自{0}:{1}", user.userName, receiveString));
 
         string[] splitString = receiveString.Split(',');
         switch (splitString[0])
         {
             case "Login":
                user.userName = string.Format("[{0}--{1}]", splitString[1], client.Client.RemoteEndPoint);
                service.SendToOne(user, "Welcome");
                break;
             default:
                break;
         }
        }
?View Code CSHARP
       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();
            }
        }
        public void SendToOne(User user, string str)
        {
            try
            {
                user.sw.WriteLine(str);
                user.sw.Flush();
                SetListBox(string.Format("向{0}发送{1}", user.userName, str));
            }
            catch
            {
                SetListBox(string.Format("向{0}发送信息失败", user.userName));
            }
        }
      }

服务器负责监听并根据不同的用户启动不同的线程

?View Code CSHARP
            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();

ListenClientConnect方法

?View Code CSHARP
             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));
                //basilwang 2008-09-06 to receive data from client
                ParameterizedThreadStart pts = new ParameterizedThreadStart(ReceiveData);
                Thread threadReceive = new Thread(pts);
                threadReceive.Start(user);
            }

此处的启用了ParameterizedThreadStart 线程处理带参数的ReceiveData方法,主要是防止同客户端传输数据的过程出现的异常不能正确处理导致线程崩溃,每一个客户端分配一个线程可以保证客户端各自的独立性;同时ParameterizedThreadStart允许传递参数。

至此我们完成了一个简单的服务器客户端应答,希望能对初学者有所帮助。

文件下载

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

Filed under: 未分类 | No Comments »
Posted on

利用TCP开发网络应用程序,可以采用同步或者异步的方式,这个游戏采用的是同步的工作方式,比较简单,系列教程也主要介绍同步的工作方式。

网络通信的前提就是客户端和服务器端的通信,在服务器端,程序需要不断的监听客户端是否有连接请求,已保证多个客户端的连接,服务器通过套接字识别客户端;而客户端只需要指定哪个服务器即可。一旦双方建立连接并创建了对应的套接字,就可以互相传输数据了。客户端和服务器端发送和接受数据的方法都是一样的,区别仅是方向不同。

在同步TCP网络应用程序中,发送、接受和监听语句均采用阻塞方式工作,一般有如下步骤:

(1)创建一个包含所采用的网络类型、数据传输类型和协议类型的本地套接字对象,并将其余服务器的IP地址和端口号绑定。可通过Socket类或者TcpListener类完成。

(2)在指定的端口进行监听,以便接受客户端的连接请求。

(3)一旦接受了客户端的连接请求,就根据客户端发送的连接信息创建与该客户端对应的Socket对象或者TcpClient对象。

(4)根据创建的Socket对象或者TcpClient对象,分别与每个连接的客户进行数据传输。

(5)根据传送信息的情况确定是否关闭与对方的连接。

本文的目的是完成前三步,即创建服务器和客户端的连接,服务器将根据对应客户端建立的TcpClient对象得到客户端的信息

服务器端部分代码

?View Code CSHARP
            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方法

?View Code CSHARP
             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)用法看起来比较奇怪,但没什么问题,保证应答多个客户端的请求。

客户端的代码

?View Code CSHARP
             TcpClient client = null;
             try
            {
                client = new TcpClient(Dns.GetHostName(), 51888);
 
            }
            catch
            {
                MessageBox.Show("与服务器连接失败", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

客户端代码很简单,因为不需要传输数据,这样当服务器端监听到请求后,利用建立的TcpClient对象的到该客户端的信息,通过调用service.SetListBox打印到ListBox中(在游戏中每个窗口都有一个ListBox,使大家看到服务器和客户端交互的一些信息,方便调试)

Service代码

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

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

示例代码下载