1、C#网络通信程序设计,第3章 基于TCP协议的程序设计,本章的主要内容,学习内容: (1)了解TCP协议的特点与数据包格式; (2)理解阻塞模式和非阻塞模式的特点及其应用; (3)掌握同步套接字编程和异步套接字编程方法; (4)掌握TcpListener和TcpClient的综合应用方法。学习目标: (1)掌握基于TCP协议的同步/异步套接字编程方法; (2)学会基于C/S结构的网络聊天程序设计与实现能力,1/10/2020,2,4.1 TCP协议介绍,(1) TCP协议提供服务的特点: 面向连接的传输; 端到端的通信; 高可靠性,确保传输数据的正确性,不出现丢失或乱序; 全双工方式传输; 采
2、用字节流方式,即以字节为单位传输字节序列; 紧急数据传送功能。,1/10/2020,3,(2) TCP数据包格式,注意标志位的作用 注意选项的作用, 如何使用选项部分?,1/10/2020,4,(3) TCP协议的通信特点,3次握手; 4次挥手; 回顾拥塞控制,1/10/2020,5,(4) TCP的常见端口,1/10/2020,6,4.2 阻塞模式/非阻塞模式,同步 异步 阻塞模式: 读、写、连接、接收 非阻塞模式,1/10/2020,7,4.2.1 阻塞模式的效率提升方法,(1) 超时控制方法 套接字选项设置:SetSockOption(), GetSockOption() 应用示例: S
3、ocket socketTimeout=new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);IPEndPoint myHost=new IPEndPoint(IPAddress.Any,8080);socketTimeout.SetSocket Option(SocketOptionLevel.Socket,SocketOptionName. ReceiveTimeout,3000);定时器,1/10/2020,8,(2) 套接字多路复用方法,C#使用Socket类提供的Select方法。应用示例
4、:ArrayList socketList=new ArrayList(5); socketList.Add(sock1); socketList.Add(sock2); Socket.Select(socketList,null,null,1000); Byte buffer=new type1024; for(int i=0;isocketList.Length-1;i+) socketListi.Receive(buffer);Console.WriteLine(System.Text.Encoding.ASCII.GetString(buffer); 这样,Select方法将监视soc
5、k1和sock2两个套接字上接收的数据。,1/10/2020,9,(3) 调用异步选择函数AsyncSelect(),在VC+编程环境中使用; 对于winsock 2.0,采用:WSAAsyncSelect()函数,1/10/2020,10,4.2.2 非阻塞模式的应用,(1) 非阻塞套接字 Socket sock=new Socket(AddressFamily.InterNetwork, SocketType.Stream,Protocol Type.Tcp); sock. Blocking=false; (2) 异步套接字采用异步回调AsyncCallback,委托为应用程序提供完成异步
6、操作。,1/10/2020,11,4.3 同步套接字编程技术,常规的网络聊天程序,相互发送文字信息。 服务器监听, 网络接收数据, 数据发送; 客户机发出连接请求,数据发送与接收。,1/10/2020,12,服务器“开始监听”的主要代码,this.btnStartListen.Enabled=false; IPAddress ip=IPAddress.Parse(this.textBoxIP.Text); IPEndPoint server=new IPEndPoint(ip,Int32.Parse(this.textBoxPort.Text); socket=new Socket(Addre
7、ssFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); socket.Bind(server); /监听客户端连接 socket.Listen(10); newSocket=socket.Accept(); /显示客户IP和端口号 this.lbState.Items.Add(“与客户“+newSocket.RemoteEndPoint.ToString()+“ 建立连接“); /创建一个线程接收客户信息 thread=new Thread(new ThreadStart(AcceptMessage); thread.Start()
8、;,1/10/2020,13,服务器“数据接收”的主要代码,NetworkStream netStream=new NetworkStream(newSocket); byte datasize=new byte4; netStream.Read(datasize,0,4); int size=System.BitConverter.ToInt32(datasize,0); Byte message=new bytesize; int dataleft=size; int start=0; while(dataleft0) int recv=netStream.Read(message,sta
9、rt,dataleft);start+=recv;dataleft-=recv; ,接收数据的方法有2种: 使用Socket类的Receive方法 使用NetworkStream类的Read方法。Read方法的返回值是一个整数,表明实际从TCP缓冲区中读取的字节数,可能少于远端发来的数据量。,1/10/2020,14,服务器“数据发送”的主要代码,发送数据也有2种方法: 使用Socket类的Send方法,应用示例:byte bytes=new byte2048; string message=“测试数据发送“; bytes=System.Text.Encoding.Unicode.GetByt
10、es(message); newSocket.Send(bytes, bytes.Length, SocketFlags.None); 使用NetworkStream类的Write方法 ,能够保证将用户数据全部发送到TCP缓冲区中,自动完成。不需要用户管理,简化了编程工作。 string str=this.rtbSend.Rtf; int i=str.Length; byte datasize=new byte4; datasize=System.BitConverter.GetBytes(i); /将位整数值转换为字节数组 byte sendbytes=System.Text.Encodin
11、g.Unicode.GetBytes(str); NetworkStream netStream=new NetworkStream(newSocket); netStream.Write(datasize,0,4); netStream.Write(sendbytes,0,sendbytes.Length); netStream.Flush(); this.rtbSend.Rtf=“;,1/10/2020,15,客户机“连接请求”的主要代码,IPAddress ip=IPAddress.Parse(this.tbIP.Text); IPEndPoint server=new IPEndPoi
12、nt(ip, Int32.Parse(this.tbPort.Text); socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp); try socket.Connect(server); catch MessageBox.Show(“与服务器连接失败!“);return; this.btnRequest.Enabled=false; this.lbState.Items.Add(“与服务器连接成功“); Thread thread=new Thread(new ThreadStart(
13、AcceptMessage); thread.Start();,1/10/2020,16,4.4 异步套接字编程技术,1/10/2020,17,(1) 客户机发出连接请求,客户端使用BeginConnect方法发出连接请求给服务器:Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp); IPEndPoint iep=new IPEndPoint(IPAddress.Parse(“127.0.0.1“),8000); socket.BeginConnect(iep,new
14、AsyncCallback(ConnectServer),socket);private void ConnectServer(IAsyncResult ar) clientSocket=(Socket)ar.AsyncState;clientSocket.EndConnect(ar);clientSocket.BeginReceive(data,0,dataSize,SocketFlags.None, new AsyncCallback(ReceiveData),clientSocket); ,1/10/2020,18,(2) 服务器接收连接请求,private Socket serverS
15、ocket,newSocket; IPHostEntry myHost=new IPHostEntry(); myHost=Dns.GetHostByName(“NetHost“); /主机名称NetHost IPAddress myIP=IPAddress.Parse(myHost.AddressList0.ToString();/选取第1个地址 IPEndPoint iep=new IPEndPoint(myIP,8000); serverSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,Protocol Type
16、.Tcp); serverSocket.Bind(iep); serverSocket.Listen(5); /监听队列为5 /开始异步接收连接请求 serverSocket.BeginAccept(new AsyncCallback(AcceptConnection),serverSocket);,1/10/2020,19,(3) 服务器发送和接收数据,一旦服务器接收到一个客户机连接请求,AsyncCallback委托将自动调用AcceptConnection方法。private void AcceptConnection(IAsyncResult ar) Socket myServer=(
17、Socket)ar.AsyncState;/异步接收传入的连接,并创建新的Socket来处理远程主机通信newSocket=myServer.EndAccept(ar);byte message=System.Text.Encoding.Unicode.GetBytes(“客户你好!“);newSocket.BeginSend(message,0,message.Length,SocketFlags.None, new AsyncCallback(SendData),newSocket); ,1/10/2020,20,SendData方法,当套接字准备好发送的数据时,会自动调用SendData
18、方法。EndSend方法用于完成数据发送,并返回成功发送的字节数。该段程序示例如下:private void SendData(IAsyncResult ar) Socket client=(Socket)ar.AsyncState;trynewSocket.EndSend(ar);client.BeginReceive(data,0,dataSize,SocketFlags.None, new AsyncCallback (ReceiveData),client); 异步接收数据catchclient.Close();serverSocket.BeginAccept(new AsyncCal
19、lback(AcceptConnection),server Socket); /开始异步接收新的连接请求 ,1/10/2020,21,课堂思考,如何图形化描述以上异步套接字各种状态之间的状态变化?如状态转换图?,1/10/2020,22,4.5 基于TcpClient类和TcpListener类的编程,TcpClient类和TcpListener类是构建于Socket之上,提供了更高抽象级别的TCP服务,便于程序员快速编写网络程序。 TcpClient类用于客户机,TcpListener类用于服务器。,1/10/2020,23,(1) TcpClient类的使用方法,第12 阶段: 方法1:
20、 先创建,后连接: /创建TcpClient实例 TcpClient client=new TcpClient(); /向服务器发出连接请求 client.Connect(“www.software.org“,8000);方法2 : 直接指定和连接服务器: TcpClient client=new TcpClient(“www.software.org“,8000);方法3: 指定IPEndPoint的方式: IPEndPoint localEndPoint=new IPEndPoint(IPAddress.Parse(“192.168.0.88“),8010); TcpClient clie
21、nt=new TcpClient(localEndPoint); client.Connect(“www.software.org“,8000);,4个阶段: 创建实例、连接服务器、收发数据和关闭连接。,1/10/2020,24,(1) TcpClient类的使用方法,/第三阶段: 数据接收 netStream=client.GetStream(); sr=new StreamReader(netStream,System.Text.Encoding.Unicode); str=sr.ReadLine(); Console.WriteLine(str); /第三阶段:数据发送 ws=clien
22、t.GetStream(); ws.Write(data,0,data.Length); /或者 sw=new StreamWriter(ws,System.Text.Encoding.Unicode); sw.WriteLine(str); sw.Flush(); . /第4阶段:连接关闭 client.Close();,4个阶段: 创建实例、连接服务器、收发数据和关闭。,1/10/2020,25,(2) TcpListener类的使用方法,TcpListener类用于监听和接收传入的连接请求,包括创建实例、监听连接、接收连接请求、收发数据和停止服务共5个阶段。典型用法如下: IPAddre
23、ss localAddress=Dns.Resolve(Dns.GetHostName().AddressList0; IPEndPoint localEndPoint=new IPEndPoint(localAddress,8010); TcpListener tcpListener=new TcpListener(localEndPoint); /接着,开始侦听客户端的连接请求 tcpListener.Start(); /开始接收连接请求 TcpClient newClient=tcpListener.AcceptTcpClient();程序执行到AcceptTcpClient()时,会处
24、于阻塞状态,直到有客户端的连接请求到达。接收请求后会返回一个TcpClient对象,该对象将与此建立连接的客户端进行通信。,1/10/2020,26,4.6 网络游戏程序设计,服务器界面 客户机界面,1/10/2020,27,本章小结思考,同步套接字: 采用多路复用、多线程方法、异步选择函数; 异步套接字:能够很好地客户机/服务器的场合; TcpClient和TcpListener类的使用; ,1/10/2020,28,实验项目,1完成一个并发服务的TCP程序设计,要求如下: (1)界面上能够设定连接数量,比如连接数为100; (2)当一个连接释放后,程序能够自动分配给新的客户请求; (3)从多台客户机上分别连接服务器,测试并发服务效果; (4)连接完成后,使客户之间可以直接通信,不需要再经过服务器,服务器仅作管理用。 2修改网络游戏程序,达到以下功能: (1)能够设置每局的周期,比如1分钟; (2)能够更改比赛规则,比如3个相连时未输; (3)能够记录得分。 3网络游戏设计思考:请参考有关资料,尝试设计一个网络五子棋游戏。还可以考虑网上中国象棋、网络麻将等,并扩展到Web应用环境中。,1/10/2020,29,