黑马程序员 Socket网络编程--聊天室

来源:互联网 发布:linux安装 编辑:程序博客网 时间:2024/05/01 17:03

------- Windows Phone 7手机开发、.Net培训、期待与您交流! -------

一、  网络中进程之间如何通信?

首先解决的问题是:如何唯一标识一个进程,否则通信无从谈起。在本地,可以用进程的PID来唯一标识一个进程,而在网路中则行不通。TCP/IP协议族已解决了这个问题:网络层的“IP地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(Ip地址,协议,端口)就可以标识网络中的进程了,网络中的进程标识就可以利用这个标识与其他进程进行交互。

二、  什么是Socket

用于描述IP地址和端口,是一个通信链的句柄。通过Socket可以接收和发送网络上的数据。

三、  Socket一般应用模式(服务端和客户端)

1、  服务端的Socket(至少需要两个)

(1)      一个负责接收客户端连接请求(但不负责通信);

(2)      每成功接收到一个客户端的连接便在服务端产生一个对应的Socket。

2、  客户端的Socket

(1)        必须指定要连接的服务端ip和端口;

(2)        通过创建一个Socket对象来初始化一个到服务端的TCP连接。

四、  Socket的通讯过程

1、  服务端

a)        申请一个socket

b)        绑定到Ip地址和端口

c)        开启监听

d)        等待客户端的连接请求。

2、  客户端

a)        申请一个socket

b)        向服务器发起连接(指明Ip地址和端口号)。

五、  面向连接的套接字调用时序

 

六、  实例:聊天程序

功能:服务端和客户端互相收发消息,服务端可以接受多个客户端的连接请求,并与之通信。

1、服务端程序:

using System;using System.Collections.Generic;using System.Text;using System.Windows.Forms;//引入命名空间using System.Net;   //IPAddress、IPEndPoint类using System.Net.Sockets;using System.Threading;  namespace Server_END{    public partial class Server_End : Form    {        public Server_End()        {            InitializeComponent();            //关闭对跨线程非安全代码调用的检查            Server_End.CheckForIllegalCrossThreadCalls =false;        }         //在文本框显示信息        void ShowMsg(string msg)        {            txtRecMsg.AppendText(msg + "\n");        }         //负责监听的Socket        Socket sockWatch = null;        //key:远程主机的ip、端口,value:负责和该主机通信的服务端Socket        Dictionary<string, Socket> dict = new Dictionary<string, Socket>();         //开启监听服务        private void btnStartService_Click(object sender, EventArgs e)        {            //获得文本框中的ip地址的对象            IPAddress ipAddr = IPAddress.Parse(txtIP.Text.Trim());            //创建包含ip和端口的网络节点对象            IPEndPoint localEP = new IPEndPoint(ipAddr, Convert.ToInt32(txtPort.Text.Trim()));            //创建 服务端 负责监听的Socket对象(使用ipv4,使用流式连接,使用TCP协议传输数据)            sockWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            //绑定IP和端口            sockWatch.Bind(localEP);            //开始监听            sockWatch.Listen(10);             ShowMsg("服务器已启动监听...");             //创建新线程,处理客户端的连接请求            Thread threadWath = new Thread(WatchAccp);            threadWath.IsBackground = true;            threadWath.Start();        }         /// <summary>        /// 监听连接请求        /// </summary>        void WatchAccp()        {            while (true)            {                //接受连接请求并创建新Socket                Socket sockCon = sockWatch.Accept();                //显示远程主机的ip和端口                lbOnline.Items.Add(sockCon.RemoteEndPoint.ToString());                //添加字典                dict.Add(sockCon.RemoteEndPoint.ToString(), sockCon);                 ShowMsg("和客户端已建立连接:" + sockCon.RemoteEndPoint.ToString());                 //为新Socket创建线程,负责和客户端通信                object obj = sockCon;                Thread threadCon = new Thread(RecMsg);                threadCon.IsBackground = true;                threadCon.Start(obj);            }        }         /// <summary>        /// 接收消息        /// </summary>        void RecMsg(object obj)        {            Socket sockCon = (Socket)obj;            //创建消息缓冲区            byte[] msgBuffer = new byte[1024 * 1024 * 2];            while (true)            {                //将接收的消息存放到缓冲区,并返回字节数                int count = sockCon.Receive(msgBuffer);                //将字节数据转换为string                string strRecMsg = Encoding.UTF8.GetString(msgBuffer);                ShowMsg(string.Format("接收自{0}:{1}", sockCon.RemoteEndPoint.ToString(), strRecMsg));            }        }         //发送消息        private void btnSend_Click(object sender, EventArgs e)        {            if (lbOnline.Text.Trim().Length > 0)            {                string clientEP = lbOnline.Text;                //从字典中查到和某客户端通信的Socket                Socket sockCon = dict[clientEP];                //将string转换为byte数据                byte[] msgBuffer = Encoding.UTF8.GetBytes(txtSndMsg.Text.Trim());                //发送消息                sockCon.Send(msgBuffer);            }        }    }}


服务端代码分析

a)  Socket.Accept()成员方法,会阻断当前线程,所以需要创建一个新线程执行该操作。

b)  每当有一个客户端连接服务器时,就创建一个Socket负责和客户端通信。这里用Dictionary<string,Socket>泛型,存储两者的对应关系。服务端向客户端发消息时,从Dictionary字典中取出与客户端对应的服务端Socket,然后用该Socket发送消息;

c)  Receive方法会阻断当前线程,因此服务端在监听到客户端的连接请求后,就创建一个新线程来执行新连接的通信,接收客户端的消息;

d)  CheckForIllegalCrossThreadCalls=false:关闭跨线程访问控件的检查,这里threadWath线程访问了UI线程创建的TextBox控件txtRecMsg,默认是不允许的,所以这里设置该属性为false,就可以跨线程访问了。

2、 客户端代码

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Net;using System.Net.Sockets;using System.Threading; namespace Client_END{    public partial class Client_End : Form    {        public Client_End()        {            InitializeComponent();            Client_End.CheckForIllegalCrossThreadCalls = false;        }         //在文本框显示信息        void ShowMsg(string msg)        {            txtRecMsg.AppendText(msg + "\r\n");        }         Socket sockClient = null;        private void btnConnect_Click(object sender, EventArgs e)        {            IPAddress ipAddr = IPAddress.Parse(txtIP.Text.Trim());            IPEndPoint remoteEP = new IPEndPoint(ipAddr, Convert.ToInt32(txtPort.Text.Trim()));            sockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            //            sockClient.Connect(remoteEP);        }         private void btnSend_Click(object sender, EventArgs e)        {            byte[] strSndMsg = Encoding.UTF8.GetBytes(txtSndMsg.Text.Trim());            sockClient.Send(strSndMsg);            ShowMsg("发送了:" + txtSndMsg.Text.Trim());        }    }}

客户端代码分析:

a)        首先申请一个Socket,指定服务端的ip和端口,请求连接(Connect);

b)        客户端需要不断的接收服务端的消息,所以创建一个后台线程,不断的循环接收(Receive)消息,如果收到消息,显示出来;

c)        如果服务器关闭了连接,客户端报SocketException异常。捕获该异常,提示“服务器已断开连接”,然后不再继续接收服务端的消息。

 

六、  实例:异常处理

Server和Client建立连接后,先关闭Client窗口,服务器代码报异常:

“未处理:SocketException 远程主机强迫关闭了一个现有的连接。”

解决办法:try-catch捕获SocketException,然后关闭当前服务端Socket,并中断接收客户端消息。

服务端接收消息的部分代码如下:       

 void RecMsg(object obj)        {            Socket sockCon = (Socket)obj;            //创建消息缓冲区            byte[] msgBuffer = new byte[1024 * 1024 * 2];            while (true)            {                try                {                    //将接收的消息存放到缓冲区,并返回字节数                    int count = sockCon.Receive(msgBuffer);                    //将字节数据转换为string                    string strRecMsg = Encoding.UTF8.GetString(msgBuffer, 0, count);                    ShowMsg("接收自" + sockCon.RemoteEndPoint.ToString() + ":" + strRecMsg);                }                catch (SocketException e)                {                    //客户端断开了连接,关闭与之通信的服务端Socket                    sockCon.Close();                    //不再接收已关闭的客户端消息                    break;                }            }        }
------- Windows Phone 7手机开发、.Net培训、期待与您交流! -------