1、 基于TCP协议网络通信系统设计与实现 摘 要 :网络通信, 因为其含有实时性、 跨平台性、 成本低、 效率高等优点而受到广泛使用。设计并实现一个能够处理多用户进行实时、 安全即时通信系统含有较强现实意义。即时通信底层通信是经过SOCKET套接字接口实现。目前主流UNIX系统和微软WINDOWS系统都在内核提供了对SOCKET字接口支持。使用这个统一接口, 能够编写一个可移植TCP/IP通信程序。使信息能够在INTERNET上可靠传输。 本文设计并实现了基于局域网内简单即时通信系统, 系统采取C/S模式, 底层通信经过SOCKET套接字接口实现, 服务器
2、负责用户端登录验证, 好友信息保留和心跳报文发送。用户端采取P2P方法实现消息传输, 并能实现文件传输。本文首先讨论了同时套接字, 异步套接字, 多线程并发实施任务等; 然后叙述了用户端、 服务器怎样使用XML序列化消息进行通信。 关键词: 即时通信; 文件传输; 套接字; TCP协议 Abstract :Instant messages have several advantages such as real-time, cross-platform, cheap and efficient. To design a Multi-user IM (instant message) a
3、rchitecture is very important in both theory and realism. Instant message based on TCP/IP protocol that is realized by socket interface. Almost all UNIX operation systems and Microsoft's windows operation systems provide support of socket in the kernel. Using the uniform interface, we can develop a
4、portable program of TCP/IP, which help us transfer information in Internet safely and credibly. The system uses the client/server(C/S) mode. The server takes the responsibility of the login message of client, the saving of friend message and Message heartbeat. The transmission of the basic message
5、s of the customer end will be designed on P2P architecture. This thesis explains how the client and server communicate via serializing XML message. Key words: Instant Message; File Transfer; Socket; TCP protocol 引言 1.1 课题背景 即时通信是一个终端连往一个即时通信网路服务。即时通信不一样于e-mail在于
6、它交谈是实时。大部分即时通信服务提供了presence awareness特征──显示联络人名单, 联络人是否在在线与能否与联络人交谈。 最早即时通信软件是ICQ, ICQ是英文中I seek you谐音, 意思是我找你。四名以色列青年于1996年7月成立Mirabilis企业, 并在11月份公布了最初ICQ版本, 在六个月内有85万用户注册使用。 在因特网上受欢迎即时通信服务包含了MSN Messenger、 AOL Instant Messenger、 Yahoo! Messenger、 NET Messenger Service、 Jabber、 ICQ与QQ。
7、这些服务有赖于很多想法更久(与普遍)在线聊天媒介, 如Internet Relay Chat一样著名。 1970年代早期, 一个更早即时通信形式是柏拉图系统(PLATO system)。以后在1980年代, UNIX/Linux交谈实时信息被广泛使用于工程师与学术界, 1990年代即时通信更跨越了因特网交流。1996年11月, ICQ是首个广泛被非UNIX/Linux使用者用于因特网即时通信软件。在ICQ介绍以后, 同时在很多地方有一定数量即时通信方法发展, 且各式即时通信程序有独立协议, 无法相互互通。这引导使用者同时实施两个以上即时通信软件, 或者她们能够使用支持多协议终端软件,
8、 如Gaim、 Trillian或Jabber。 1.2 中国外研究现实状况 1.2.1 国外研究现实状况 当今, 国际上对网络通信系统研究很好企业有, 思科, Sun, Ms等企业, 思科关键研究是底层传输; MS, Sun企业研究是应用层。其中ms企业凭借其在操作系统垄断地位, 为了在网络发展中取得先机, 采取了多种多样手段。不过, 其捆绑msn, 不管从功效上, 还是技术上来说, 都不算是非常优异。当然, ie, 一样也不是很受人青睐, 这让人想起了, 当年网景企业, 网景只是生不逢时。MS不择手段想打跨网景, 可见其对网络重视。现在, Sun企业在网络应用上捷足先登, 凭借
9、着Java, Sun在网络应用上领先于MS。微软, 想用一样措施搞跨对手, 所以它拿出了Visual c#, 来对抗Java。这些都是在应用层面开发工具。应用层上产品就更显种类繁多。ICQ几乎是国际上通用即时通信工具, 因为在中国它应用不是很广, 所以, 其原理也极少被介绍。msn, 是MS产品, 一样在中国没什么市场, 所以, 对其原理, 也极少被讨论过。至于ie, 是在Visual c++下开发产品, 即使有严重安全隐患, 不过, 最少能在某种程度上代表当今国际研究水平。另外, 国际上最近出先了新浏览器Firefox, 其性能听说是远高于ie, 可能在网络天下, Ms又有了更强劲对手。
10、1.2.2 中国研究现实状况 中国在应用层上网络应用软件现在发展异常火爆, 因为中国有着网络应用最大市场, 现在中国网络基础性建设发展快速, 应用软件也层出不穷, 其中, 在游戏领域中, 网络通信工作做不错, 如联众游戏平台, 还有其她部分平台, 这些平台基础上都是基于VC++, 用都是Socket通信, 不过为了效率, 这些平台没有用MFC提供CSocket类, 而是直接用Socket进行通信。所以效率上不错。另外, tencent即时通信, 也是做很好, 从某中程度上来说, 代表了中国最高水平。 1.3 本课题研究方法 本系统采取C/S(Client/Server)结
11、构进行设计,使用SQL Server 构建数据库, 并在.NET环境下使用Visual C#.net语言和SOCKET套接字开发一个基于TCP协议简单即时通信软件, 实现简单即时聊天, 文件传输等功效。 2 相关技术介绍 2.1 .NET开发平台及C#.NET开发语言 NET框架是Microsoft企业推出一个全新开发平台,提供了统一、 面向对象而且能够扩展编程类库和完善集成开发环境,大大简化了应用程序开发过程,而且含有良好移植性和安全性。 微软为了推行.NET战略, 尤其为.NET平台设计了一个语言——C#。C#是由C和C++派生而来一个“简单、 流行、 面向对象、
12、 类型安全”程序设计语言, 其综合了Visual basic高效率和C++强大功效, 然而更多人感觉C#更类似JAVA。 2.2 TCP协议 2.2.1 TCP/IP网络协议 协议是对等网络实体之间通信规则, 能够简单地了解为网络上各计算机相互交流一个“语言”。网络通信协议设计基础标准是层次化, 层和协议集合被称为网络体系结构。相邻层之间接口定义了下层向上层提供基础操作和服务, 下层向上层提供服务分两种形式: 面向连接服务和无连接服务。 计算机网络中已经形成网络体系结构关键有两个: OSI参考模型和TCP/IP参考模型。TCP/IP参考模型是因特网(Internet
13、基础。和OSI7层协议相比, TCP/IP协议只有4个层次。通常说TCP/IP是一组协议总称, TCP/IP实际上是一个协议族, 包含100多个相互关联协议, 其中IP(Internet Protocol, 网际协议)是网络层最关键协议; TCP(Transmission Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据报协议是传输层中最关键协议), 通常认为IP、 TCP、 UDP是最根本三种协议, 是其她协议基础。 2.2.2 TCP——传输控制协议 : 面向连接通信能够使用可靠通信, 在这时候, 第四层协议发送数据接收方
14、确定, 假如未收到数据或者数据被损坏, 则请求重新传输。TCP协议就使用这种可靠通信。 使用TCP协议应用层协议包含HTTP、 FTP、 SMTP和Telnet等。 现在能够发送和接收消息了。接收消息后, 总是返回ACK消息。假如在收到ACK之前发送方已经超时, 则消息将被放到重发队列中以再次发送。 因为它握手机制, 所以TCP协议比较复杂而且费时, 但此协议在处理数据时对数据包传送有保障, 从而使得在应用程序协议中不需要再包含该功效。 2.3 套接字 套接字这个术语并没有定义某个协议: 它含有两层含义, 但二者都与一个协议相关。第一个含义是套接字编程API,
15、 它最初由伯克利大学为BSD UNIX而创建。BSD套接字在经过修改后被用作Windows环境编程接口(而且被命名为WinSock)。WinSock API被包装在System.Net.sockets命名空间.NET类中。Windows Sockets 是一个独立于协议编程接口, 用于编写网络应用程序。 2.4 流 2.4.1 流基础概念 流概念已经存在很长时间了。流是一个用于传输数据对象。数据传输有两个方向: 1)假如数据从外部源传输到程序中, 这就是读取流。 2) 假如数据从程序传输到外部源, 这就是写入流。 外部源常常是一个文件, 但也不完全都是文件, 它还能
16、够是: 1) 网络, 使用一定网络协议与网络上其它计算机或终端交换数据。 2) 一个指定管道。 3) 一块内存区域。 2.5 同时、 异步、 阻塞和非阻塞 同时(synchronous): 所谓同时方法, 就是发送方发送数据包以后, 不等接收方响应, 就接着发送下一个数据包。 异步(asynchronous): 异步方法就是当发送方发送一个数据包以后, 一直等到接收方响应后, 才接着发送下一个数据包。 阻塞(Block): 指实施此套接字网络调用时, 直到调用成功才返回, 不然此套节字就一直阻塞在网络调用上, 比如调用StreamReader 类
17、Readlin ( )方法读取网络缓冲区中数据, 假如调用时候没有数据抵达, 那么此Readlin ( )方法将一直挂在调用上, 直到读到部分数据, 此函数调用才返回。 非阻塞(Unblock): 指在实施此套接字网络调用时, 不管是否实施成功, 都立刻返回。一样调用StreamReader 类Readlin ( )方法读取网络缓冲区中数据, 不管是否读到数据都立刻返回, 而不会一直挂在此函数调用上。 在Windows网络通信软件开发中, 最为常见方法就是异步非阻塞套接字。日常所说C/S(用户端/服务器)结构软件采取方法就是异步非阻塞模式。 其实在用C#进行网络编程
18、中, 我们并不需要了解什么同时、 异步、 阻塞和非阻塞原理和工作机制, 因为在.Net FrameWrok SDK中已经已经把这些机制给封装好了。 3.系统总体设计 3.1 需求分析 软件针对局域网内部用户, 实现用户间即时通信。需要分别实现服务器端和用户端软件设计。 服务器端负责监听用户连接请求, 负责连接数据库存放用户信息, 负责发送给用户好友信息, 负责发送心跳报文检验用户在线状态并即时让用户更新好友在新信息。 用户端提议主动连接, 向服务器请求登录或者注册。用户端能够修改昵称, 能够加已知用户为好友(类似于MSN好友添加功效)。用户端之间能够提议P2P模
19、式聊天, 能够传送文件。 3.2 系统基础架构 基于C/S架构即时通信软件便于对用户信息进行统一管理和保留, 面向特定用户, 对信息安全控制能力很强。为了减轻服务器负担, 用户端之间信息传输是采取P2P模式, 服务器只负责用户注册, 登录和用户在线状态检验。基础结构如图: 3.3 功效模块设计 CLIENT: 1.注册: (1)能够完成用户端注册, 用户端能够经过填写信息进行注册, 信息被发送到 服务器端。 2.登录: (1)用户能够输入账号和密码进行登录, 用户端会发送登录信息等候服务器响 应, 登录成功后会发出登录成功信息并刷新好友列表。
20、 3.修改: (1)密码修改: 应该有密码修改功效 (2)信息修改: 能够更改部分注册信息 4.通信: (1)即时聊天模块: 用户端与用户端之间建立线程进行即时聊天, 也包含有简 单对称加解密算法功效。 (2)好友列表: 能够对好友列表进行添加删除等动作。 5.文件传输: (1)文件传输: 文件传输功效 SERVER: 1. 注册回应: 对用户端传送注册信息进行判定。 (1)HASH加密: 对用户账号和密码信息进行HASH加密 (2)反复用户检验: 将加密后信息与已存在账号进行比较, 检验是否账号已存 在, 假如存在就返回错误信
21、息 (3)注册成功: 将可成功注册用户账号和密码写入数据库内, 并向用户端返回成功信息 2.修改回应: (1)对密码和信息修改请求进行判定, 实施和返回修改成功信息。 3.登录回应: (1)对登录账号和密码进行加密检验后发回正确或错误情况, 并统计上线信息 。 (2)好友列表发送: 给成功登录账号发送好友列表及好友上线信息。 (3)上线信息发送: 给成功登录账号好友发送在线信息(包含IP,端口等等信息)。 4.在线情况: (1)对登录, 在线, 离线用户情况进行统计, 统计和通知 (2)心跳测试: 每隔一段时间发送报文测试用户是否因意外原因离线 (3
22、)情况统计: 将用户登录时间, IP, 下线时间等信息统计入数据库 3.4 逻辑图: 3.5 数据库设计 3.5.1 实体关系图 服务器是作为统计和读取数据库信息载体, 与用户端关系并不复杂, 这里需要关键考虑用户端之间关系。用户与用户之间关系是较为特殊递归关系, 即描述发生在两个相同实体上关系。 4 系统实现 4.1 使用XML定义即时通信协议 4.1.1 信息结构MESSAGE.CS&UMESSAGE.CS 这两个C#类定义了包含服务器信息, 状态信息, 注册信息, 登录信息, 聊天信息或者请求文件传输信息函数, 服务器和用户端经过将它们实例化和序列化再转换
23、成流在网络上进行传输。UMESSAGE.CS关键代码以下: [Serializable] public class UMessage { public UMessage(){ } private string _nickname; private string _password; private string _accounts; private string _email; private int _info;//表示注册或者登录信息, 用户端信息0为注册
24、 1为登录; 服务器返回信息0为用户已存在, 1为注册成功,2为服务器未知错误, 3为CLIENT在线检验, 10为登录失败, 11为登录成功 private Friend[] _friend; private int _fn; private string _fg; public string Nickname { get { return _nickname; } set { _nickname = value; } }
25、 public string Password { get { return _password; } set { _password = value; } } public string Accounts { get { return _accounts; } set { _accounts = value; } } public str
26、ing Email { get { return _email; } set { _email = value; } } public int Info { get { return _info; } set { _info = value; } } public Friend[] Fri { get { return _friend; }
27、 set { _friend = value; } } public int Fn { get { return _fn; } set { _fn = value; } } public string Fg { get { return _fg; } set { _fg = value; }
28、 } } 因为MESSAGE.CS与UMESSAGE.CS类似, 在此不再详述。 服务器和用户端都能够经过相同代码对UMESSAGE赋值, 再经过XmlSerializer方法进行将UMESSAGE序列化为XML文档, 最终将XML文档转化为网络流进行传输。代码以下: #region 将登录信息转为UMessage private void Traslator() { _message.Accounts=this.TextBox1.Text; _message.Nickname=""; _message.Passwo
29、rd=this.TextBox2.Text; _message.Email=""; _message.Info=1; _message.Fri=null; } #endregion 4.1.2 数据结构FriendStruct 服务器假如保留和传输用户好友信息是难点之一。数据库设计和信息传输分辨都是比较难实现。 在数据库方面, 每个用户拥有各自好友分组信息(UserFav), 分组中间使用“, ”分隔, 在TCP_FriendInfo表中则分别保留了用户ID和好友ID, 使用一个INT字段保留分组信息。数据库以用户ID为标准
30、对好友ID和分组信息进行内连接查询, 就能够得到基础好友信息了。代码以下: select * from TCP_UserInfo join TCP_FriendInfo on TCP_FriendInfo.UserID='" + uid + "' and TCP_UserInfo.UserID=TCP_FriendInfo.FriendID 在好友信息传输方面, 首先定义一个FriendStruct数据结构(当然也能够用枚举完成)以下: using System; using System.Collections.Generic; using System.Text;
31、 namespace TCP { public class FriendStruct { public struct FileInfo { public int filere;//接收和拒绝信息,1为接收, 2为拒绝, 3为取消 public string filename; public long filelength; } } public struct Friend { public str
32、ing account; public string nickname; public string IP; public string status; public string fg;//好友分组 } } 在MESSAGE.CS或者UMESSAGE.CS中, 我们则定义了FriendStruct数组。在C#中使用DATAREADER语句能够逐句读取数据库查询结果, 再依次将结果赋值FriendStruct数组元素, 就得到了便于发送和读取存放好友信息数组。赋值代码以下: whil
33、e (getf.Read()) //getf即是以上数据库查询datareader语句 { ff[i].account=getf["UserAccount"].ToString(); ff[i].IP = getf["UserIP"].ToString(); ff[i].nickname = getf["UserNickname"].ToString(); ff[i].status = getf["UserOnline"].ToString
34、); ff[i].fg = getf["FriendGroup"].ToString(); i++; } getf.Close(); 4.2 数据库连接类 实现一个快捷简单数据库连接相关代码是非常有必需。实现路径也多个多样, 鉴于安全性和复杂性需求不一样, 实现方法有简有繁。本设计使用了一个简单类(UserData.CS)实现了简单快捷数据库连接和读取。关键代码以下: public static SqlConnection connStr = new SqlConnection("Se
35、rver=D96B85DD938A465.;uid=sa;pwd=change;database=TCPDB"); public static SqlDataReader SqlReader(string sql, SqlConnection connstr) { SqlDataReader sqldr = null; SqlCommand cmd = new SqlCommand(sql, connstr); if (cmd.Connection.State.ToString() ==
36、"Closed") cmd.Connection.Open(); try { sqldr = cmd.ExecuteReader(); } catch (Exception e) { catch (Exception e) { if (e != null) sqldr = null; }
37、 return sqldr; } //数据库操作连接 public static string SqlCmd(string sql, SqlConnection connstr) { string errorstr = null; SqlCommand sqlcmd = new SqlCommand(sql, connstr); if (sqlcmd.Connection.State.ToString() == "Open") sq
38、lcmd.Connection.Close(); sqlcmd.Connection.Open(); try { sqlcmd.ExecuteNonQuery(); } catch (Exception e) { if (e != null) errorstr = e.ToString();
39、 } sqlcmd.Connection.Close(); return errorstr; } 在UserData.CS基础上, 主程序能够更方便地实现数据库连接操作, 对数据库进行读写和更新, 在此不再详述。 4.3 服务器端 服务器端界面设计是基于便于测试目而实现。以下图: 4.3.1 同时套接字网络监听 基于同时套接字网络监听器对服务器来说并不是最好处理方案, 不过仍然可行而且实现简单。关键代码以下: 开启监听端口: public voi
40、d Serve() { int port = 8888; ServerIPEP = new IPEndPoint(IPAddress.Any, port); s = new Socket(ServerIPEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); s.Bind((EndPoint)ServerIPEP); s.Listen(10);
41、 alSock = new ArrayList(); 以下代码读取连入连接, 依次将连接加入可变长数组alsock, 而且读取传入信息, 进行反串行化: while (true) { try { uc = s.Accept(); alSock.Add(uc); this.tb_states.AppendText(System.Conver
42、t.ToString(uc)); byte[] data = new byte[2048]; int rect = uc.Receive(data); byte[] chat = new byte[rect]; Buffer.BlockCopy(data, 0, chat, 0, rect); UMessage umes
43、sage = (UMessage)_translator.Deserialize(new MemoryStream(chat)); int info = umessage.Info; 对反串行化后信息进行处理, 经过info参数识别用户端行为(注册或者登录), 对注册信息进行数据库查询, 注册信息可插入, 则将用户信息插入数据库, 不然返回用户端“注册犯错”信息: #region 处理用户注册信息 if (info==0)//分辨出用户发送是注册信息
44、 { string Accounts = umessage.Accounts; SqlDataReader usdr = FPara.SqlReader("select * from TCP_UserInfo where UserAccount='" + Accounts + "'", FPara.connStr); if (usdr != null)
45、 { if (usdr.Read()) { #region 此处写入返回注册失败代码 Socket sc = (Socket)alSock[alSock.IndexOf(uc, 0)]; sc.Send(chat);
46、 #endregion } else { #region 此处写入插入数据库用户注册信息代码 Stream ms = new MemoryStream(); So
47、cket sc = (Socket)alSock[alSock.IndexOf(uc, 0)]; if (FPara.SqlCmd("insert into TCP_UserInfo (UserAccount,UserNickname,UserEmail,JoinDate,UserIP,UserPassword) values('" + umessage.Accounts + "','" + umessage.Nickname + "','" + umessage.Email + "','" + System.DateTime
48、Now.ToString() + "','" + ((IPEndPoint)uc.RemoteEndPoint).Address.ToString() + "','" + umessage.Password + "')", FPara.connStr) == null) { umessage.Info = 1; _translator.Serialize(ms, umes
49、sage); byte[] d = new byte[ms.Length]; ms.Seek(0, SeekOrigin.Begin); ms.Read(d, 0, d.Length); sc.Send(d);
50、 } else { umessage.Info = 2; _translator.Serialize(ms, umessage); byte[]






