自打老婆怀孕的时候,我就琢磨着给未曾谋面的宝宝建一个网站,记录他的成长,无奈乱七八糟的事情太多,网站的事情一拖再拖。7月30日宝宝出生了,亲朋好友都希望看到他的近照,结果发邮件发到手疼,这才想起建站的事情不能在拖下去了,把宝宝的照片、我们的心情都记录下来,大家访问网站就行了,也算是对宝宝有个交代。说到网站,免费的空间限制太多,我个人不太喜欢,估计宝宝也不喜欢(随我,呵呵),咬咬牙,租了个空间,申请了一个域名,以后这里就是宝宝在网上的家了。

最后这句话是对俺家宝宝说的:糖糖,人生的路还很长,爸爸妈妈祝愿你健康快乐的成长!!

posted @ 2008-08-29 09:30 BAsil 阅读(217) | 评论 (1)编辑

最近网站要上线,在部署项目环境时碰到了问题,简单记录一下

域控制器+数据库服务器  域名local
               window 2003  sp2
               sql server 2000 sp4     windows 认证

web服务器
               window 2003 sp2
               iis6   允许匿名登录  
               asp.net 1.1  系统网站  要求允许internet访问
               web.config  impersonate=false   authentication mode=”Forms”

连接采用 Intergrated Security=true(即Trusted Connection=true)
问题1:碰到no authority/network service(iis5下为 no authority/aspnet)
解决方案:网上搜了一下,要在sql server上添加network service相应的权限,可是在sql server上找了一圈也没有发现,后来找到了老外的一个帖子Installing SQL Server on a domain controller,大体意思就是不推荐把sql server安装到域控制器上,可能会导致帐户的权限等。

于是做了更改
数据库服务器
               window 2003  sp2
               sql server 2000 sp4     windows 认证

域控制器+web服务器   域名local
               window 2003 sp2
               iis6   允许匿名登录  
               asp.net 1.1  系统网站  要求允许internet访问
               web.config  impersonate=false   authentication mode=”Forms”

把域服务器安装到web服务器上,这个时候能够在sql server添加 local\Network Service 帐号,并且设置对具体数据库的权限。不过浏览页面的时候报 用户 'LOCAL\WIN03$' 登录失败,WIN03是我这里web服务器的机器名。
从网上查了一下
如果IIS启用了匿名访问
  1 如果asp.net应用程序启用模仿,则用IUSR_machinename发出请求
  2 如果asp.net应用程序未启用模仿,则用特定的ASP.NET进程帐户发出请求
所以将impersonate设为true,另外之前已经设置了IUSR_machinname(就是Network Service)在Sql Server的用户权限,页面能够正常浏览。
还要注意的是,如果启用了模仿,但IUSR_machinname在Sql Server中没有相应权限,会报用户 'LOCAL\IUSER_WIN03' 登录失败错误。

最后如果正在运行 Windows Server 2003,其中的 IIS 6.0 配置为运行在辅助进程隔离模式下(默认情况),则可通过将 ASP.NET 应用程序配置为在自定义应用程序池(在特定的域标识下运行)中运行来避免模拟。然后,可以使用指定的域标识访问资源而无需使用模拟。

 

补充:
今天从网上看到说不建议使用impersonate,可能会导致应用程序池工作不正常,于是尝试将impersonate设置为false,此时报用户 'LOCAL\WIN03$' 登录失败,可以在sql server中添加local\win03$用户权限,在虚拟机上测试能够正常访问。明天在服务器环境试一下。

 

今天在服务器环境下测试,发现外网用户不能访问(始终弹出用户名密码的登录对话框),在网上找到这样一段话

使用   Windows   集成安全性。此选择将用户的凭据传递到   SQL   Server。由于委托问题,这样做是切实可行的,条件是   SQL   Server   和   IIS   位于同一台计算机上,而且用户必须与   Web   服务器计算机位于同一域中。

看来,不使用模仿是无法解决这个问题的,但我有不想使用模仿,不知道还有没有其他办法.

posted @ 2008-12-04 01:12 BAsil 阅读(1836) | 评论 (4)编辑

今天打算把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全部不起作用,找不到路径,我开始试图修改处理程序映射中的“检查文件是否存在”取消打勾,不过却找不到该选项。
后来发现换成经典管道模式也正常了。这里记录一下,给有相同问题的朋友提个醒。

posted @ 2008-11-15 22:53 BAsil 阅读(84) | 评论 (0)编辑

中文描述:无法启动Visual Studio的Localhost Web Server

郁闷了一上午,我的vs2005没有新建网站和打开网站选项,后来尝试打开Web Application来运行,结果每次都报上述错误,而vs2008工作正常,没有这个问题。而且Framework2.0文件夹下没有WebDev.WebServer.exe,怀疑没有正确安装,运行VS2005的卸载程序的添加新功能,发现Web Developer没有安装,安装后工作正常。这才想起来安装VS2005的时候以为Web Developer是Web Developer Express版本,就没有安装,真是大意了。

ps 同时知道了WebDev.WebServer.exe实际上是基于Cassini的一个Web服务器,我们可以根据需要换成第三方的web服务器调试。

posted @ 2008-10-23 15:01 BAsil 阅读(155) | 评论 (0)编辑

上一节我们讨论的游戏大厅的实现,这一节我们来看一下客户端游戏棋盘的处理

关于棋盘的呈现采用了GDI的DrawImage方法,先准备一张400*400的棋盘图片和两个40*40的棋子图片(分别为黑棋和白棋),我们的思路是通过和客户端服务器的数据交互得到游戏大厅某桌的棋子信息,然后客户端直观的呈现该信息。

棋盘同样可以看作一个对象,只不过这个对象我们需要从Form来继承,因为我们需要用到PictureBox控件来呈现棋盘和棋子

 

 1  //此处选择从Form继承,是考虑需要在Form上利用GDI画图已达到走棋的效果
 2  public partial class FormPlaying : Form
 3 {
 4         private int tableIndex;
 5         private int side;
 6     //这里和服务器端的GameTable一样也定义了grid(8,8),目的是在客户端保存棋子的状态
 7     //每次通讯时只由服务器端通知更改的棋子的位置和颜色,客户端也作相应的修改
 8     //当然这里也可以不声明grid(8,8),不过那样每次通讯都需要传递所有的棋子位置状态,效率不高
 9         private int[,] grid = new int[88];
10     //工具类
11         private Service service;
12     //FormPlaying和FormRoom类似都需要被负责接受数据的线程操作,需要调用Object.Invoke方法,详见第二节
13     delegate void LabelDelegate(Label label, string str);
14         delegate void ButtonDelegate(Button button, bool flag);
15         LabelDelegate labelDelegate;
16         ButtonDelegate buttonDelegate;
17         //保存棋子位图信息
18     private Bitmap blackBitmap;
19         private Bitmap whiteBitmap;
20 
21     //FormPlaying将在玩家落座后呈现,在FormRoom中被初始化
22     //TableIndex 桌数
23     //Side 黑方或者白方
24     //sw 客户端发送的数据流
25         public FormPlaying(int TableIndex,int Side,StreamWriter sw)
26         {
27             InitializeComponent();
28         //构造工具类,负责打印调试信息及发送数据流到服务器端
29             service = new Service(listBox1, sw);
30             this.tableIndex = TableIndex;
31             this.side = Side;
32         blackBitmap = new Bitmap("black.gif");
33             whiteBitmap = new Bitmap("white.gif");
34         }
35 }

 

这里和服务器端的GameTable一样也定义了grid(8,8),目的是在客户端保存棋子的状态,每次通讯时只由服务器端通知更改的棋子的位置和颜色,客户端也作相应的修改。当然也可以不声明grid(8,8),不过那样每次通讯都需要传递所有的棋子位置状态,效率不高

上一节提到当玩家选中Checkbox表明坐下时,触发CheckedChange事件,这一节对该事件进行修改,初始化棋盘FormPlaying并打开

 

 1 void checkBox_CheckedChanged(object sender, EventArgs e)
 2 {
 3     CheckBox checkbox = (CheckBox)sender;
 4     if (checkbox.Checked)
 5     {
 6     int i = int.Parse(checkbox.Name.Substring(54));
 7     int j = int.Parse(checkbox.Name.Substring(94));
 8 
 9     side = j;
10 
11     service.SendToServer(string.Format("SitDown,{0},{1}", i, j));
12     formPlaying = new FormPlaying(i, j, sw);
13     formPlaying.Show();
14     }
15 
16 }

 

调用GDI画棋盘及棋子信息(初始化及任一方落子都调用该事件)

 

 1  private void pictureBox1_Paint(object sender, PaintEventArgs e)
 2 {
 3     Graphics g = e.Graphics;
 4     for (int i = 0; i <= grid.GetUpperBound(0); i++)
 5     {
 6     for (int j = 0; j <= grid.GetUpperBound(1); j++)
 7     {
 8         if (grid[i, j] != DotColor.None)
 9         {
10         if (grid[i, j] == DotColor.Black)
11         {
12             //i代表行j代表列 y轴应为列 x轴应为行 
13             g.DrawImage(blackBitmap, (i + 1* 40,(j + 1* 40);
14         }
15         else
16         {
17             g.DrawImage(whiteBitmap, (i + 1* 40, (j + 1* 40);
18         }
19         }
20     }
21     }
22 }

 

当玩家落子的时候实际上是触发了PictureBox的MouseDown事件,程序判断是否可在该处下子,如果可以则调用GDI的Graphics.DrawImage将棋子“画”到棋盘上。

 

 1 private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
 2 {
 3     //i代表行j代表列 y轴应为列 x轴应为行 
 4     //棋子的图片大小为40*40,所以这里根据坐标信息得到棋子在对应的grid数组中的位置
 5     int x = e.X / 40;
 6     int y = e.Y / 40;
 7     //黑白棋逻辑
 8     //保证鼠标点击位置在棋盘内部
 9     if (!(x < 1 || x > 8 || y < 1 || y > 8))
10     {
11     if (grid[x - 1,y - 1== DotColor.None)
12     {
13         //int color = grid[x - 1, y - 1];
14         //格式 SetDot,参数1,参数2,参数3,参数4
15         //功能:由服务器端判断此处是否允许落子,
16         // 不允许落子的情况
17         // 1 在不合法的位置落子
18         // 2 轮到黑方落子,但白方在点击棋盘,则对白方不允许落子
19         //如果允许同时计算落子后双方棋子的变化情况,
20         //参数1 桌号
21         //参数2 棋盘行
22         //参数3 棋盘列
23         //参数4 黑方或白方
24         service.SendToServer(string.Format("SetDot,{0},{1},{2},{3}", tableIndex, x - 1,y-1, side));
25     }
26     }
27 }

 

此处的实现效率上有些问题,把判断能否落子的任务交给服务器端,实际上我们已经在客户端利用grid数组保存了棋子的状态,完全可以放到客户端去判断,放到服务器端会造成很多无用的通讯数据(写文章的时候代码已经完成,不打算改了)

列一下服务器端处理SetDot命令的逻辑

1 如果没有轮到该方落子,则丢弃此次命令

2 将落子的位置向八个方向进行比较(上、下、左、右、左上、左下、右上、右下)

   考虑落子的同一列的上下两个方向的比较情况
   (1)单列中小于目标行的情况,目标行最小为第1行(下标为0)
      假设目标行为第5行(下标为4)则比较行k1依次为3,2,1,0
      如果目标行为第1行(下标为0)则不需要比较小于目标行的情况
   (2)单列中大于目标行的情况,目标行最大为第8行(下标为7)
      假设目标行为第5行(下标为4)则比较行k2依次为5,6,7
      如果目标行为第8行(下标为7)则不需要比较大于目标行的情况

3 准备八个List<string>,将八个方向需要反转的棋子分别存入代表各自方向的List<string>中,以向上方向为例

   (1)从最靠近目标行的行数开始,逐个检验同列中棋子颜色,遇到相同颜色或者无子即退出,连续不同存入;如果检测位置无子,则晴空该方向List<string>,即无法改变该方向棋子颜色
   (2)如果有同颜色的子,则退出,List<string>中的值即为该方向需要更改的值
   (3)如果有不同颜色的子且不为第1行,则记录,否则清空列表。假设在第1列第1-4行为黑子,白方在第1列第5行打算落下白子,从向上的方向比较,当检测到第1行时,该方向列表中已有3个子,但第一行为黑子,无法反转1-4行黑子,所以清空已有列表。

4 如果八个方向List<string>均为空,则表明此处无法下子,丢弃此命令

5 通知客户端修改 落子的信息及需要反转的棋子的所有信息

此处代码较多,不列了,请看源码

本节完成了棋盘的呈现及简单的落子算法,但未考虑游戏中一方无法落子而由另一方继续落子的情况,下一节将对代码进行简单的重构以添加该功能,最终完成一个完整的网络黑白棋对弈。

源代码下载

posted @ 2008-10-14 23:14 BAsil 阅读(1574) | 评论 (7)编辑

上一节我们讲到了客户端发送Login命令后,服务器返回欢迎信息,完成了一个简单的数据传输。这一节我们来完成游戏大厅的基本功能,我们首先思考一下游戏大厅的基本功能:

1 提供可供对弈的游戏桌,游戏大厅可供多桌玩家同时游戏,为了考虑游戏大厅服务器的负载能力,应该设置一个人数的上限和桌数的上限。实际上前面提到的功能抽象出来就是一些数据的状态集合。

2 当玩家登入大厅,应该直观的显示当前大厅的就座情况,方便玩家选择。此处应该考虑大厅的直观显示。

3 当玩家选择某一位置就坐,游戏大厅的相应状态数据应发生更改,任何玩家都能看到大厅的就座情况的变化,方便做出选择。比如a选择坐在第一桌的黑方位置,则b应该看到该位置不可落坐,只能选择其他位置就坐。

尽量从面向对象的角度考虑,我们应当把游戏大厅,游戏桌,玩家看做对象。建议大家使用面向对象的方法去思考,个人感觉服务器客户端通信的网络程序主要涉及通信协议(就是我们前面提到的命令,参数1,参数2等等)的分析,设计不好的话到最后你会发现逻辑复杂到难以控制的程度。

下面我们分别看一下这几个对象(有删减,具体请看源代码)

 

 1 //游戏大厅
 2 public FormServer
 3 {
 4     //basilwang 2008-09-06 
 5     //new to this version myGame2
 6     //游戏桌集合
 7     private GameTable[] gameTable;
 8     //玩家集合
 9     List<User> userList = new List<User>();
10     //游戏桌上限
11     private int maxTables;
12     //玩家上限
13     private int maxUsers;
14     //上一节列出的帮助类
15     private Service service;
16 }
17 //游戏桌
18 class GameTable
19 {
20         private const int None = -1//无棋子
21         private const int Black = 0//黑白棋子
22         private const int White = 1//白色棋子
23         private int[,] grid = new int[88];   //8*8的方格
24         public Player[] gamePlayer;
25         public GameTable()
26         {
27             gamePlayer = new Player[2];
28             gamePlayer[0= new Player();
29             gamePlayer[1= new Player();
30         }
31         
32  }
33 class Player
34 {
35         //是否开始
36     public bool started;
37     //己方棋子个数
38     public int grade;
39     //是否落座
40     public bool someone;
41     public Player()
42     {
43         someone = false;
44         started = false;
45         grade = 0;
46     }
47 }

 

当服务器程序启动的时候,需要初始化游戏大厅的数据;而当客户端登录到游戏大厅后,按照前面列出的逻辑,我们需要把游戏大厅的状态在客户端直观的显示出来。那怎么才能得到游戏大厅的状态数据呢?没错,也是利用的客户端服务器之间的通讯。

当客户端向服务器发送Login命令后,服务器处理完毕后,返回Tables命令返回状态数据

 

 1 private void ReceiveData(object obj)
 2 {
 3     //省略接受客户端协议代码
 4     
 5     //拆分接受到的协议      格式:  命令,参数1,参数2 .
 6     string[] splitString = receiveString.Split(',');
 7     string sendString = "";
 8     switch (splitString[0])
 9     {
10     case "Login":
11         user.userName = string.Format("[{0}--{1}]", splitString[1], client.Client.RemoteEndPoint);
12         //向客户端发送协议   
13         //格式 : Tables,参数1
14         //参数1为游戏大厅的游戏桌就座情况
15         sendString = "Tables," + this.GetOnlineString();        
16         service.SendToOne(user, sendString);        
17         break;
18     default:
19         break;
20     }
21  }
22  //返回如0100010101的字符串  奇数位表示游戏桌黑方的就座情况,偶数位相反,游戏桌按序号排列连接
23  private string GetOnlineString()
24 {
25     string str = "";
26     for (int i = 0; i < gameTable.Length; i++)
27     {
28     for (int j = 0; j < 2; j++)
29     {
30         str += gameTable[i].gamePlayer[j].someone == true ? "1" : "0";
31     }
32     }
33     return str;
34 }

 

客户端接受到Tables命令协议进行分析,直观显示游戏大厅的就座情况,这里为了简单,采用了动态生成若干组checkbox控件添加到Panel的方法,比较简单但能够说明问题。checkbox选中表明已有玩家就座,如果未选中表明可以在此处落座。

这部分代码就不列出来了,可以看一下原程序。

如果玩家选择在某一位置落座,将出发CheckBox的CheckedChanged事件,并向服务器发送SitDown命令

 

 1 void checkBox_CheckedChanged(object sender, EventArgs e)
 2 {
 3             CheckBox checkbox = (CheckBox)sender;
 4             if (checkbox.Checked)
 5             {
 6             //动态生成的CheckBox命名规则为checkXXXXYYYY, 第5-8位为桌号,不足0补齐;第9-12位为黑方或白方,不足0补齐
 7                 int i = int.Parse(checkbox.Name.Substring(54));
 8                 int j = int.Parse(checkbox.Name.Substring(94));
 9                 side = j;
10         //格式 SitDown,参数1,参数2
11         //参数1 桌号
12         //参数2 黑方或白方
13                 service.SendToServer(string.Format("SitDown,{0},{1}", i, j));
14             }
15  }

 

服务器分析SitDown命令

 1 private void ReceiveData(object obj)
 2 {
 3         //省略接受客户端协议代码
 4     
 5         //拆分接受到的协议      格式:  命令,参数1,参数2 .
 6         string[] splitString = receiveString.Split(',');
 7     string sendString = "";
 8     int tableIndex = -1;  //桌号
 9     int side = -1;        //座位号
10     int anotherSide = -1//对方座位号
11 
12     switch (splitString[0])
13     {
14         case "Login":
15         //省略部分
16         break;
17         case "SitDown":
18         tableIndex = int.Parse(splitString[1]);
19         side = int.Parse(splitString[2]);
20         gameTable[tableIndex].gamePlayer[side].user = user;
21         gameTable[tableIndex].gamePlayer[side].someone = true;
22         service.SetListBox(string.Format(
23             "{0}在第{1}桌第{2}座入座", user.userName, tableIndex + 1, side + 1));
24         //得到对家座位号
25         anotherSide = (side + 1% 2;
26         //判断对方是否有人
27         if (gameTable[tableIndex].gamePlayer[anotherSide].someone)
28         {
29             //先告诉该用户对家已经入座
30             //发送格式:SitDown,座位号,用户名
31             sendString = string.Format("SitDown,{0},{1}", anotherSide,
32             gameTable[tableIndex].gamePlayer[anotherSide].user.userName);
33             service.SendToOne(user, sendString);
34         }
35         //同时告诉两个用户该用户入座(也可能对方无人)
36         //发送格式:SitDown,座位号,用户名
37         sendString = string.Format("SitDown,{0},{1}", side, user.userName);
38         service.SendToBoth(gameTable[tableIndex], sendString);
39         //重新将游戏室各桌情况发送给所有用户
40         service.SendToAll(userList, "Tables," + this.GetOnlineString());
41         break;
42         default:
43         break;
44     }
45  }
46 

 

到这里,我们把游戏大厅的简单逻辑都处理了,下一节将介绍客户端棋盘的呈现。

源代码下载
posted @ 2008-10-11 15:29 BAsil 阅读(1269) | 评论 (8)编辑

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

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

 1 TcpClient client;
 2 StreamReader sr;
 3 StreamWriter sw;
 4 this.userName = "";
 5 NetworkStream netStream = client.GetStream();
 6 sr = new StreamReader(netStream, System.Text.Encoding.UTF8);
 7 sw = new StreamWriter(netStream, System.Text.Encoding.UTF8);
 8 //读取数据
 9 string output=sr.ReadLine();
10 //写入数据
11 string input="";
12 sw.WriteLine();
13 sw.Flush();

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

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

先看一下客户端请求登录

 1 service.SendToServer("Login," + textBoxName.Text.Trim());
 2 class Service
 3 {
 4 ListBox listbox;
 5 StreamWriter sw;
 6 public Service(ListBox listbox, StreamWriter sw)
 7 {
 8     this.listbox = listbox;
 9     this.sw = sw;
10 }
11 public void SendToServer(string str)
12 {
13     sw.WriteLine(str);
14     sw.Flush();
15 }
16 }


服务器接受请求并处理,一般来说是根据不同的请求创建不同的线程进行处理,见本节后半部分
 1 private void ReceiveData(object obj)
 2 {
 3     //User类封装了TcpClient,StreamReader和StreamWriter
 4     User user = (User)obj;
 5     TcpClient client = user.client;
 6     string receiveString = null;
 7     receiveString = sr.ReadLine();
 8     service.SetListBox(string.Format("来自{0}:{1}", user.userName, receiveString));
 9     string[] splitString = receiveString.Split(',');
10     switch (splitString[0])
11     {
12         case "Login":
13             user.userName = string.Format("[{0}--{1}]", splitString[1], client.Client.RemoteEndPoint);
14             service.SendToOne(user, "Welcome");
15             break;
16         default:
17             break;
18     }
19 }
20 


 1 class Service
 2 {
 3         private ListBox listbox;
 4         private delegate void SetListBoxCallback(string str);
 5         private SetListBoxCallback setListBoxCallback;
 6         public Service(ListBox listbox)
 7         {
 8             this.listbox = listbox;
 9             setListBoxCallback = new SetListBoxCallback(SetListBox);
10         }
11         public void SetListBox(string str)
12         {
13             if (listbox.InvokeRequired)
14             {
15                 listbox.Invoke(setListBoxCallback, str);
16             }
17             else
18             {
19                 listbox.Items.Add(str);
20                 listbox.SelectedIndex = listbox.Items.Count - 1;
21                 listbox.ClearSelected();
22             }
23         }
24         public void SendToOne(User user, string str)
25         {
26             try
27             {
28                 user.sw.WriteLine(str);
29                 user.sw.Flush();
30                 SetListBox(string.Format("向{0}发送{1}", user.userName, str));
31             }
32             catch
33             {
34                 SetListBox(string.Format("向{0}发送信息失败", user.userName));
35             }
36         }
37  }
服务器负责监听并根据不同的用户启动不同的线程
 1 IPAddress localAddress;
 2 int port = 51888;
 3 TcpListener myListener;
 4 Service service = new Service(listbox);
 5 IPAddress[] addrIP = Dns.GetHostAddresses(Dns.GetHostName());
 6 localAddress = addrIP[0];
 7 myListener = new TcpListener(localAddress, port);
 8 myListener.Start();
 9 service.SetListBox(string.Format("开始在{0}:{1}监听客户连接", localAddress, port));
10 ThreadStart ts = new ThreadStart(ListenClientConnect);
11 Thread myThread = new Thread(ts);
12 myThread.Start();


ListenClientConnect方法
 1 while (true)
 2 {
 3     TcpClient newClient = null;
 4     try
 5     {
 6        newClient = myListener.AcceptTcpClient();
 7     }
 8     catch
 9     {
10        break;
11     }
12     User user = new User(newClient);
13     userList.Add(user);
14     service.SetListBox(string.Format("{0}进入", newClient.Client.RemoteEndPoint));
15     service.SetListBox(string.Format("当前连接用户数:{0}",userList.Count));
16     //basilwang 2008-09-06 to receive data from client
17     ParameterizedThreadStart pts = new ParameterizedThreadStart(ReceiveData);
18     Thread threadReceive = new Thread(pts);
19     threadReceive.Start(user);
20 }

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

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

 文件下载

posted @ 2008-09-24 14:48 BAsil 阅读(2053) | 评论 (7)编辑

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

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

示例代码下载

posted @ 2008-09-21 13:54 BAsil 阅读(1561) | 评论 (4)编辑

看过网上很多的类似系列教程(博客园包包版网络大厅的+桥牌系统),写的很深入,感觉比较复杂,初学者不宜上手。我是在学习WCF的时候,发现自己对底层的传输原理都没有搞明白,于是又回头学习网络传输的一些知识,自己写了一个简单的网络游戏黑白棋,因此也想把学习的一个过程记录下来和初学者们一块交流。我的只是小儿科,还请网友多多包涵,高手们也不要吝惜你们的砖头。

第一次写系列教程,心里没底,本来自己水平就一般,却要完成这个命题作文,难煞我了。好歹程序写的差不多了(不过还没有最终完成),这里先把完成的部分分章介绍一下,程序我在慢慢补。

本系列源代码TCP网络传输参考《C#网络应用高级编程》人民邮电出版社,马骏编,黑白棋游戏部分为本人(http://www.basilwang.net/)编写。

写这个游戏只是为了我个人理解基于TCP网络游戏编程的基本思路,算法部分写的比较乱,没有优化,不过我都做了注释,方便大家阅读。

先说一下黑白棋,又叫反棋(Reversi)、奥赛罗棋(Othello),苹果棋,翻转棋。黑白棋在西方和日本很流行。但是这个游戏在中国目前还不够推广,下棋的水平还不高。黑白棋规则很简单,只要肯花点脑筋,新手也能玩得很好。因为棋盘小,下一局棋所花的时间也不多。对于黑白棋,有一种说法是:只需要几分钟学会它,却需要一生的时间去精通它(a minute to learn, a lifetime to master)。

早些年文曲星里面带的游戏就喜欢它了,我这个人脑子不开窍,很多游戏连电脑AI都打不过,好容易整了个能打败AI的游戏,还不往死里玩,嘿嘿。不过我写的这个黑白棋只是供网络对战使用,不涉及AI算法部分(我还不知道怎么做呢),斗胆发到网上,权作抛砖引玉,废话少说,进入正题。

系列的介绍打算以我学习TCP网络编程的过程为顺序,每一篇教程都能够完成功能,附上的源代码能够独立运行,我会把代码中碰到的相关知识做相应的介绍,使初学者能有直观的认识。

游戏完成的部分

1 网络大厅,可自定义桌数,人数 (完全参照《C#网络应用高级编程》,马老师应该不介意吧)

2 黑白棋游戏客户端

2.1 吃子

2.2  奇偶数统计

2.3 轮流下子

2.4 终局胜负提示

未完成部分

1 黑白棋游戏客户端

1.1 下子时间限制

1.2 甲方无子可下时,程序判定由乙方下子,甲方丢掉一次机会

1.3 判定任一方无子可下的程序(思路:需要计算盘中空子的列表,然后调用已完成的吃子程序,看能否下子,但并不真正下子)

源程序写的很简单,界面比较简陋,只是为了帮助大家更好的理解基于TCP的网络游戏传输的基本原理,还请大家多包涵。

posted @ 2008-09-21 13:47 BAsil 阅读(552) | 评论 (0)编辑

windows xp以后的系统自带了还原功能,可以创建系统的还原点,当系统出问题时可以利用该功能恢复到以前创建的还原点,同时由于还原点的创建采用了增量备份,每次还原点的创建的比较快,占用的空间也不是太大。

偏偏我不太喜欢使用这个还原功能,从来也记不得创建还原点,结果今天让我吃尽了苦头。今早电脑进了木马,任务管理器出现了explore.exe进程(不是explorer.exe),很多exe文件都无法执行,查了一下确认是中了灰鸽子。因为手头上有工作,我偷了个懒,想直接用自动还原恢复到以前的状态。检查还原点,手工还原点没有(我这个人最懒了),只有一个9月3日系统创建的还原点,就用这个了呵呵,我暗自窃喜:真是懒人自有天相。可是回复完了以后傻眼了,启动后报System32\Config\System文件损坏,无法启动,我的神阿!*%?!~~%^@@$(&@

看到这里,可能有童鞋会说,这个问题好说阿,用启动盘进入修复功能提示符下,把System32\repair下的相应文件拷贝过去就可以。这个方式可以恢复到系统安装的初始状态,而且大多数软件都可以运行,包括office。可是我很不幸,我是一个MS程序员,作为一个MS程序员的基本一条就是要和诡异的VS开发工具打交道,我的vs2005,vs2008,sql server 2005在这种恢复模式下统统不起作用,卸载再安装的话我的双休就拜拜了(vs2005 sp1的补丁安装就达到恐怖的4小时)。

好在我还有一个ghost备份,是2008年1月1日自己做的,7G,里面除了没有安装vs2008及其补丁剩下的基本都全了,接下来,恢复ghost,安装vs2008,打sp1补丁,2个小时ok。

在这里需要提醒大家的是

1.最好在完全重装系统后来一个完全的ghost,不过要有心理准备这个备份比较大;

2.另外要定时的做一些手工的还原点。

3.系统创建的还原点可能有问题,我就是吃了这个亏。

4.经常备份以下windows\system32\config下面的文件,绝对没有坏处

posted @ 2008-09-05 22:07 BAsil 阅读(52) | 评论 (0)编辑

网站已经上线,当然需要定时备份了。可是当我打开phpMyAdmin连接的mysql数据表准备备份的时候,却发现全是乱码,咨询了一下空间提供商,被告知数据库的编码就是这样设置的,没法改。可是我用phpMyAdmin备份到本地打开也一样是乱码,在网上找到了一个使用phpMyAdmin备份的解决方案,可是不起作用。后来试了一下WordPress DataBase Backup,发现问题解决。而且DB Backup支持周期Email数据库备份,这个功能太方便了。

另外需要提醒大家的是,备份的文件需要用UTF8的编码形式打开,本机的数据库导入用phpMyAdmin没问题,可是我用第三方的Mysql-Front却报错,至此问题解决。

后记

mysql的编码问题确实比较头疼,以上是我在wordpress 2.6版本的解决方案,建议大家碰到乱码的时候,各种方法多试几次,应该能解决。

posted @ 2008-09-01 22:09 BAsil 阅读(126) | 评论 (0)编辑

上次说到给俺们家糖糖建了一个网站,这次主要说一下建站的技术细节吧,希望对感兴趣的朋友有所帮助,大牛们请直接跳过。

建站的几个步骤如下

1 域名申请

2 空间选择

3 上传自己的程序代码并且建立数据库

4 广告申请

5 Alexa排名申请

6 备案

1 域名申请

因为我是第一次申请域名,我把碰到的问题和需要注意的事项一并记录一下,希望能对朋友们有所帮助。刚开始我想申请cn的顶级域名,网上给出的价格很便宜,一般不超过10块钱一年。可是当我打算下单购买的时候才发现cn域名的申请对个人来说几乎是不可操作,因为要求域名申请的单位以及一系列的手续,当然这也比较符合中国国情,只是不知奥运后限制会不会放宽。最后我选择了比较贵的国际域名,edong网给出的价格是68元/年,国际域名的注册没有太多的限制,很快就注册下来了。打款的时候也碰上了一点麻烦,我首先在开通了工行的网上银行,不过支付的时候总是报超出客户最大支付限额的错误,后来才知道需要到银行取办理口令卡开通才能完成,只好放弃(太不人性了,我半夜办理业务让我怎么去银行阿)。相比较招商银行就做的很不错,简单的注册就可以使用了。

2 空间选择

网络上建站有两种方式,租用虚拟主机或者服务器托管。对于个人站长,租用虚拟主机是相对经济的解决方案。我的网站http://www.basilwang.net就是租用了了edong网的虚拟主机,采用了它的k300的linux/unix解决方案,支持php+mysql,空间费用300元/年,当然也有支持.net的解决方案,而且比较便宜200元/年。这里为什么没有选用.net的解决方案,我做一下解释:就.net和php而言,我认为个人网站还是选用php更加灵活一点,而且网上php开源的东西一大把,有的已经非常稳定了。另外建议大家选择口碑比较不错的空间商,不过这方面我也没有什么经验,就不误导大家了。

3上传自己的程序代码并且建立数据库

代码方面我选用了wordpress,我之前没有任何php的编程经验,不过为了建站还是花了3天看了一部分原代码,了解了wordpress关于theme、插件还有widget等方面运行机制。不过这里提醒大家的是,wordpress已经完全能够做到一键式的安装,非常方便,没有程序开发经验的朋友也可以很快上手。当然我在阅读php代码的时候,还是有种回到asp编程的年代。

程序代码可以用上传软件如cuteftp等用提供商给的帐号上传。关于mysql,你可以建库并设置访问密码,通过phpMyAdmin进行访问,不支持类似mysql-front等第三方工具的访问。这里还要注意的是,网络提供商可能提供的是英文的编码方案,因此通过phpMyAdmin看到的中文都是乱码,不过显示都是没有问题的,只是备份还原的时候需要注意,网上有解决方案,也可以参考我接下来的文章。

4 广告申请

在写这篇文章的时候,申请的Google Adsense又一次被拒,原因是“确保您的网站已经注册6个月以上时间;”,只能等半年后再说了。相反alimama的申请到是很方便,不过希望马云所说的“电子商务的冬天”会不会影响到造福放大个人站长的alimama。最后提醒大家,单靠广告点击的收入保证网站的自负盈亏比较困难,请打算上贼船的朋友三思。我做网站的时候也考虑了这点,全当是我对儿子的投资了,呵呵,儿子不要有压力阿。

5 alexa排名

这个东西能给使你对你的网站有一个比较直观的认识,建议把网站提交到alexa以进行统计

6 备案

现在的网站都需要备案,当然这个备案目前是免费的,需要到工业和信息化部网站进行注册(原来的信息化部网站),也可以由你的空间提供商代为你注册。我个人感觉还是自己操作吧,熟悉一下流程,没有坏处。

更多内容,请阅读发表在[http://www.basilwang.net]的文章

posted @ 2008-08-29 11:46 BAsil 阅读(414) | 评论 (2)编辑

自打老婆怀孕的时候,我就琢磨着给未曾谋面的宝宝建一个网站,记录他的成长,无奈乱七八糟的事情太多,网站的事情一拖再拖。7月30日宝宝出生了,亲朋好友都希望看到他的近照,结果发邮件发到手疼,这才想起建站的事情不能在拖下去了,把宝宝的照片、我们的心情都记录下来,大家访问网站就行了,也算是对宝宝有个交代。说到网站,免费的空间限制太多,我个人不太喜欢,估计宝宝也不喜欢(随我,呵呵),咬咬牙,租了个空间,申请了一个域名,以后这里就是宝宝在网上的家了。

最后这句话是对俺家宝宝说的:糖糖,人生的路还很长,爸爸妈妈祝愿你健康快乐的成长!!

posted @ 2008-08-29 09:30 BAsil 阅读(217) | 评论 (1)编辑

tt

更多图片请访问糖糖的个人空间

http://www.basilwang.net

posted @ 2008-08-28 12:04 BAsil 阅读(54) | 评论 (6)编辑

我的儿子糖糖于2008年7月30日(农历6月28)生于山东大学齐鲁医院,体重7斤7两,记录一下。

posted @ 2008-08-28 12:02 BAsil 阅读(53) | 评论 (4)编辑
     摘要:

前几天,在园子里看了一个图片幻灯效果,很不错,不过今天想找却找不到了。这两天想把网站的图片幻灯效果给换了,原来的虽然做成了web控件,不过控件里硬编码了javascript和css,甚至还有document.write这样的输出,感觉很是不爽。偶然发现了一篇翻译文章如何使用 JavaScript 创建可维护的幻灯片效果,感觉原作者在构建可分离的javascript和css上颇有心得,不过这里和我想要的效果还有些差异,参照大部分国内网站的实现方式,应该能够实现定时的播放,同时在右下角还应该有一个序列号的指示,就像新浪网的首页那样,既然作者帮我们作了大部分的分离工作,剩下的这部分自己实现,也十分简单。

  阅读全文
posted @ 2008-07-04 13:16 BAsil 阅读(2601) | 评论 (13)编辑
我他妈的要是再看中国队的球我就是王八羔子!
posted @ 2008-06-07 21:50 BAsil 阅读(270) | 评论 (5)编辑
     摘要: 画了两张UML图,帮助理解asp.net 2.0的页面即时编译,建议结合Reflector查看
  阅读全文
posted @ 2008-06-04 21:46 BAsil 阅读(3010) | 评论 (14)编辑
最近在看《ASP.NET 2.0服务器控件与组件开发高级编程》,其中有一个以前没有注意的细节,思索了好久,还是没有答案,还请各位帮忙指点一下。
服务器控件,注意CardholderNameText是存到ViewState当中的
    public class CreditCardForm : Control
    
{
        
public CreditCardForm()
        
{
        }

        
public string CardholderNameText
        
{
            
get return ViewState["CardholderNameText"!= null ? (string)ViewState["CardholderNameText"] : "CardholderName"; }
            
set { ViewState["CardholderNameText"= value; }
        }

        
protected override void Render(HtmlTextWriter writer)
        
{
            writer.Write(
"<strong>" + CardholderNameText + "</strong>");
        }

    }
调用的页面,我放置了一个button,以便观察Postback的值是否发生改变,另外设置了CardholderNameText属性
   <form id="form1" runat="server">
    
<div>
       
<custom:CreditCardForm CardholderNameText="Full Name(Initialize)" runat="server" ID="creditcardform" />
        
<asp:Button ID="Button1" runat="server" Text="Button" /></div>
    
</form>
调用页面的后台代码,很简单
void Page_Load(object sender, EventArgs e)
  
{
    
if (!IsPostBack)
    
{
      creditcardform.CardholderNameText 
= "Full Name(After Postback)";
    }

  }
在页面第一次请求的时候,执行aspx编译生成的Page子类的AddParseSubObject,将控件加入到Controls集合中,并设置了CardholderNameText的值为"Full Name(Initialize)",由于是第一次加载在Page_Load中修改了值为"Full Name(After PostBack)",到这里没有问题。
当我点击button提交,第二次请求页面时,我在调试器里发现再一次设置了CardholderNameText的值为"Full Name(Initialize)",但是页面却显示Name(After PostBack)",不知道怎么回事。
我怀疑是和Page的AddParseSubObject有关,因为它是在整个生命周期的最开始执行,会不会是此时的ViewState还没有生效?希望能给与解答!!谢谢
源代码附上CreditCardForm
原因:
结果和AddParseSubObject无关,原因实际很简单,怪自己看得不仔细,乱扣帽子,引用wit的回复
回传时,第一步任然解析html,并且设置CardholderNameText为:Full Name(Initialize),此时 视图里的值也为这个,因为该属性保存在视图里。

下面就是重载视图了,会根据客户端form请求的值,重新加载视图状态,
这时就把第一次访问后的视图值加载过来(将Full Name(After Postback))

所以就是你的结果了···
posted @ 2008-05-20 11:34 BAsil 阅读(848) | 评论 (6)编辑
     摘要: Linq to sql给我们的orm影射带来了极大的便利,实体类写很少的代码就可以完成数据库表的增删查改,也使我们能够更加专注于业务逻辑;而Xml的应用也是相当广泛,如今的SOA很大程度上是利用了Xml格式的SOAP消息来进行交互。有的时候我们会碰到需要读取xml保存到数据库的情况,在没有Linq的时候,可能我们会通过XmlSerializer的Deserialize方法来反序列化Xml生成相对应的net class,然后操作net class插入数据库,如果不采用orm的话,代码量和利用XmlDocument直接操作Xml更新数据库没有什么差别,那么现在有了Linq to sql,反序列化的net class和Linq to sql的Entity可以优雅的结合到一起,看看减轻了我们多少的工作量?  阅读全文
posted @ 2008-04-25 10:43 BAsil 阅读(2190) | 评论 (10)编辑
     摘要: 折腾了近两天,这个500的问题终于解决了,我都快崩溃了,我的笔记本是xp sp2,vs2005 vsts,sql2005 dev,biztalk en r2 eval版,作了Adapter Useages的HTTPRequestResponse个例子,在http://localhost/RequestResponse/default.aspx中点击提交按钮,正常的应该是调用http adapter返回response 可是却报500的错误,网上关于这个问题的帖子很多,但是解决方案就是那么几种,转过来转过去的,没有什么帮助,并且大都是window 2003 iis6的解决,我在window 2003 iis6下测试没有问题。下面我把我的解决思路整理一下,问题到最后解决很简单,就那么一下子,但是我却走了不少弯路,希望以后碰到问题的时候能够尽快地定位到错误点。
  阅读全文
posted @ 2008-04-16 16:25 BAsil 阅读(1361) | 评论 (7)编辑