Socket聊天程序

Socket聊天服务器版

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;

namespace SocketServer
{
    /// <summary> 
    /// Form1 的摘要说明。 
    /// </summary> 
    public class mainForm : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label label4;
        private System.Windows.Forms.TextBox txtIP;
        private System.Windows.Forms.TextBox txtPort;
        private System.Windows.Forms.Button btnStart;
        private System.Windows.Forms.Button btnSend;
        private System.Windows.Forms.Button btnStop;
        private System.Windows.Forms.StatusBar statusBar;
        /// <summary> 
        /// 必需的设计器变量。 
        /// </summary> 
        private System.ComponentModel.Container components = null;

        //新添加属性 
        private IPAddress myIP;
        private IPEndPoint MyServer;
        private Socket sock;
        private Socket handler;
        private System.Windows.Forms.RichTextBox txtSendMsg;
        private System.Windows.Forms.RichTextBox txtMsg;
        private static ManualResetEvent Done = new ManualResetEvent(false);

        public mainForm()
        {
            // 
            // Windows 窗体设计器支持所必需的 
            // 
            InitializeComponent();

            // 
            // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 
            // 
        }

        /// <summary> 
        /// 清理所有正在使用的资源。 
        /// </summary> 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        #region Windows 窗体设计器生成的代码 
        /// <summary> 
        /// 设计器支持所需的方法 - 不要使用代码编辑器修改 
        /// 此方法的内容。 
        /// </summary> 
        private void InitializeComponent()
        {
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.label3 = new System.Windows.Forms.Label();
            this.label4 = new System.Windows.Forms.Label();
            this.txtIP = new System.Windows.Forms.TextBox();
            this.txtPort = new System.Windows.Forms.TextBox();
            this.btnStart = new System.Windows.Forms.Button();
            this.btnSend = new System.Windows.Forms.Button();
            this.btnStop = new System.Windows.Forms.Button();
            this.statusBar = new System.Windows.Forms.StatusBar();
            this.txtSendMsg = new System.Windows.Forms.RichTextBox();
            this.txtMsg = new System.Windows.Forms.RichTextBox();
            this.SuspendLayout();
            // 
            // label1 
            // 
            this.label1.Location = new System.Drawing.Point(24, 32);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(56, 23);
            this.label1.TabIndex = 0;
            this.label1.Text = "服务器:";
            // 
            // label2 
            // 
            this.label2.Location = new System.Drawing.Point(24, 88);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(72, 23);
            this.label2.TabIndex = 1;
            this.label2.Text = "监听端口:";
            // 
            // label3 
            // 
            this.label3.Location = new System.Drawing.Point(24, 232);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(72, 23);
            this.label3.TabIndex = 2;
            this.label3.Text = "发送信息:";
            // 
            // label4 
            // 
            this.label4.Location = new System.Drawing.Point(24, 144);
            this.label4.Name = "label4";
            this.label4.Size = new System.Drawing.Size(72, 23);
            this.label4.TabIndex = 3;
            this.label4.Text = "接收信息:";
            // 
            // txtIP 
            // 
            this.txtIP.Location = new System.Drawing.Point(96, 24);
            this.txtIP.Name = "txtIP";
            this.txtIP.Size = new System.Drawing.Size(120, 21);
            this.txtIP.TabIndex = 4;
            this.txtIP.Text = "";
            // 
            // txtPort 
            // 
            this.txtPort.Location = new System.Drawing.Point(96, 80);
            this.txtPort.Name = "txtPort";
            this.txtPort.Size = new System.Drawing.Size(120, 21);
            this.txtPort.TabIndex = 5;
            this.txtPort.Text = "";
            // 
            // btnStart 
            // 
            this.btnStart.Location = new System.Drawing.Point(280, 24);
            this.btnStart.Name = "btnStart";
            this.btnStart.TabIndex = 8;
            this.btnStart.Text = "开始监听";
            this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
            // 
            // btnSend 
            // 
            this.btnSend.Location = new System.Drawing.Point(280, 64);
            this.btnSend.Name = "btnSend";
            this.btnSend.TabIndex = 9;
            this.btnSend.Text = "发送信息";
            this.btnSend.Click += new System.EventHandler(this.btnSend_Click);
            // 
            // btnStop 
            // 
            this.btnStop.Location = new System.Drawing.Point(280, 104);
            this.btnStop.Name = "btnStop";
            this.btnStop.TabIndex = 10;
            this.btnStop.Text = "停止监听";
            this.btnStop.Click += new System.EventHandler(this.btnStop_Click);
            // 
            // statusBar 
            // 
            this.statusBar.Location = new System.Drawing.Point(0, 312);
            this.statusBar.Name = "statusBar";
            this.statusBar.Size = new System.Drawing.Size(392, 22);
            this.statusBar.TabIndex = 11;
            // 
            // txtSendMsg 
            // 
            this.txtSendMsg.Location = new System.Drawing.Point(96, 224);
            this.txtSendMsg.Name = "txtSendMsg";
            this.txtSendMsg.Size = new System.Drawing.Size(280, 72);
            this.txtSendMsg.TabIndex = 12;
            this.txtSendMsg.Text = "";
            // 
            // txtMsg 
            // 
            this.txtMsg.Location = new System.Drawing.Point(96, 144);
            this.txtMsg.Name = "txtMsg";
            this.txtMsg.Size = new System.Drawing.Size(280, 72);
            this.txtMsg.TabIndex = 13;
            this.txtMsg.Text = "";
            // 
            // mainForm 
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
            this.ClientSize = new System.Drawing.Size(392, 334);
            this.Controls.Add(this.txtMsg);
            this.Controls.Add(this.txtSendMsg);
            this.Controls.Add(this.statusBar);
            this.Controls.Add(this.btnStop);
            this.Controls.Add(this.btnSend);
            this.Controls.Add(this.btnStart);
            this.Controls.Add(this.txtPort);
            this.Controls.Add(this.txtIP);
            this.Controls.Add(this.label4);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.MaximizeBox = false;
            this.Name = "mainForm";
            this.Text = "聊天程序服务器端";
            this.Closed += new System.EventHandler(this.mainForm_Closed);
            this.ResumeLayout(false);

        }
        #endregion

        /// <summary> 
        /// 应用程序的主入口点。 
        /// </summary> 
        [STAThread]
        static void Main()
        {
            Application.Run(new mainForm());
        }

        /// <summary> 
        /// 开始监听 
        /// </summary> 
        /// <param name="sender"></param> 
        /// <param name="e"></param> 
        private void btnStart_Click(object sender, System.EventArgs e)
        {
            if (txtIP.Text == "")
            {
                MessageBox.Show("对不起,IP地址不能为空@!", "警告");
                txtIP.Focus();
                return;
            }
            if (txtPort.Text == "")
            {
                MessageBox.Show("对不起,端口不能为空!", "警告");
                txtPort.Focus();
                return;
            }
            try
            {
                myIP = IPAddress.Parse(txtIP.Text.Trim());
            }
            catch
            {
                MessageBox.Show("你输入的IP地址格式不正确,请重新输入!", "警告");
            }
            try
            {
                MyServer = new IPEndPoint(myIP, Int32.Parse(txtPort.Text.Trim()));
                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                sock.Bind(MyServer); //绑定IP和端口 
                sock.Listen(50);
                statusBar.Text = "主机: " + txtIP.Text + " 端口: " + txtPort.Text + "开始监听......";

                //线程开始监听 
                Thread thread = new Thread(new ThreadStart(StartServer));
                thread.Start();
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }
        private void StartServer()
        {
            try
            {
                while (true)
                {
                    Done.Reset();
                    //异步开始接收 
                    sock.BeginAccept(new AsyncCallback(AcceptCallBack), sock);
                    //阻塞线程,直到收到信号 
                    Done.WaitOne();
                }
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }
        private void AcceptCallBack(IAsyncResult ar)
        {
            Done.Set(); //设为终止 

            //获取状态 
            Socket listener = (Socket)ar.AsyncState;
            //结束异步接收,并获取结果 
            handler = listener.EndAccept(ar);

            //创建自定义类对象实例 
            StateObject state = new StateObject();
            state.workSocket = handler;
            statusBar.Text = "与客户端建立连接...";
            try
            {
                byte[] byteData = System.Text.Encoding.BigEndianUnicode.GetBytes("准备完毕,可以通话!" + "\n\r");
                //开始异步发送数据 
                handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
            //定义线程 
            Thread thread = new Thread(new ThreadStart(rec));
            thread.Start();
        }
        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                //获取状态 
                handler = (Socket)ar.AsyncState;
                //结束发送 
                int bytesSent = handler.EndSend(ar);
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }

        private void rec()
        {
            StateObject state = new StateObject();
            state.workSocket = handler;
            //开始异步接收数据 
            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
        }

        private void ReadCallback(IAsyncResult ar)
        {
            //获取状态 
            StateObject state = (StateObject)ar.AsyncState;
            Socket tt = state.workSocket;
            //结束异步读取数据,并获取结果 
            int bytesRead = handler.EndReceive(ar);
            //储存数据 
            state.sb.Append(System.Text.Encoding.BigEndianUnicode.GetString(state.buffer, 0, bytesRead));
            //转化为字符串 
            string content = state.sb.ToString();
            //清除state.sb的内容 
            state.sb.Remove(0, content.Length);
            //向listBox写入内容 
            txtMsg.AppendText(content + "\r\n");
            tt.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
        }

        private void btnStop_Click(object sender, System.EventArgs e)
        {
            try
            {
                sock.Close();
                statusBar.Text = "与客户端断开连接...";
            }
            catch
            {
                MessageBox.Show("连接尚未建立,断开无效!", "警告");
            }
        }

        private void btnSend_Click(object sender, System.EventArgs e)
        {
            try
            {
                byte[] byteData = System.Text.Encoding.BigEndianUnicode.GetBytes(txtSendMsg.Text + "\n\r");
                //开始异步发送数据 
                handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }

        private void mainForm_Closed(object sender, System.EventArgs e)
        {
            try
            {
                sock.Close();
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
            finally
            {
                Application.Exit();
            }
        }
    }
    public class StateObject
    {
        public Socket workSocket = null;//定义套接字 
        public const int BufferSize = 1024; //缓冲区大小 
        public byte[] buffer = new byte[BufferSize]; //缓冲区 
        public StringBuilder sb = new StringBuilder();//接收数据的字符串 
        public StateObject()
        {

        }
    }
}

Socket聊天程序客户端

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
namespace SocketClient
{
    /// <summary> 
    /// Form1 的摘要说明。 
    /// </summary> 
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label label4;
        private System.Windows.Forms.TextBox txtIP;
        private System.Windows.Forms.TextBox txtPort;
        private System.Windows.Forms.ListBox listReceiveMsg;
        private System.Windows.Forms.RichTextBox txtSendMsg;
        private System.Windows.Forms.Button btnConnect;
        private System.Windows.Forms.Button btnSend;
        private System.Windows.Forms.Button btnExit;
        /// <summary> 
        /// 必需的设计器变量。 
        /// </summary> 
        private System.ComponentModel.Container components = null;

        //新添加私有成员 
        private IPEndPoint MyServer;
        private Socket sock;
        private static ManualResetEvent ConnectDone = new ManualResetEvent(false);
        private static ManualResetEvent SendDone = new ManualResetEvent(false);
        private System.Windows.Forms.StatusBar statusBar1;
        private IPAddress myIP;


        public Form1()
        {
            // 
            // Windows 窗体设计器支持所必需的 
            // 
            InitializeComponent();

            // 
            // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 
            // 
        }

        /// <summary> 
        /// 清理所有正在使用的资源。 
        /// </summary> 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        #region Windows 窗体设计器生成的代码 
        /// <summary> 
        /// 设计器支持所需的方法 - 不要使用代码编辑器修改 
        /// 此方法的内容。 
        /// </summary> 
        private void InitializeComponent()
        {
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.label3 = new System.Windows.Forms.Label();
            this.label4 = new System.Windows.Forms.Label();
            this.txtIP = new System.Windows.Forms.TextBox();
            this.txtPort = new System.Windows.Forms.TextBox();
            this.listReceiveMsg = new System.Windows.Forms.ListBox();
            this.txtSendMsg = new System.Windows.Forms.RichTextBox();
            this.btnConnect = new System.Windows.Forms.Button();
            this.btnSend = new System.Windows.Forms.Button();
            this.btnExit = new System.Windows.Forms.Button();
            this.statusBar1 = new System.Windows.Forms.StatusBar();
            this.SuspendLayout();
            // 
            // label1 
            // 
            this.label1.Location = new System.Drawing.Point(24, 40);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(64, 23);
            this.label1.TabIndex = 0;
            this.label1.Text = "服务器:";
            // 
            // label2 
            // 
            this.label2.Location = new System.Drawing.Point(24, 96);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(72, 23);
            this.label2.TabIndex = 1;
            this.label2.Text = "请求端口:";
            // 
            // label3 
            // 
            this.label3.Location = new System.Drawing.Point(24, 136);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(72, 23);
            this.label3.TabIndex = 2;
            this.label3.Text = "接收信息:";
            // 
            // label4 
            // 
            this.label4.Location = new System.Drawing.Point(24, 208);
            this.label4.Name = "label4";
            this.label4.Size = new System.Drawing.Size(80, 23);
            this.label4.TabIndex = 3;
            this.label4.Text = "发送信息:";
            // 
            // txtIP 
            // 
            this.txtIP.Location = new System.Drawing.Point(112, 32);
            this.txtIP.Name = "txtIP";
            this.txtIP.Size = new System.Drawing.Size(128, 21);
            this.txtIP.TabIndex = 4;
            this.txtIP.Text = "";
            // 
            // txtPort 
            // 
            this.txtPort.Location = new System.Drawing.Point(112, 88);
            this.txtPort.Name = "txtPort";
            this.txtPort.Size = new System.Drawing.Size(128, 21);
            this.txtPort.TabIndex = 5;
            this.txtPort.Text = "";
            // 
            // listReceiveMsg 
            // 
            this.listReceiveMsg.ItemHeight = 12;
            this.listReceiveMsg.Location = new System.Drawing.Point(112, 128);
            this.listReceiveMsg.Name = "listReceiveMsg";
            this.listReceiveMsg.Size = new System.Drawing.Size(312, 76);
            this.listReceiveMsg.TabIndex = 6;
            // 
            // txtSendMsg 
            // 
            this.txtSendMsg.Location = new System.Drawing.Point(112, 208);
            this.txtSendMsg.Name = "txtSendMsg";
            this.txtSendMsg.Size = new System.Drawing.Size(312, 96);
            this.txtSendMsg.TabIndex = 7;
            this.txtSendMsg.Text = "";
            // 
            // btnConnect 
            // 
            this.btnConnect.Location = new System.Drawing.Point(312, 24);
            this.btnConnect.Name = "btnConnect";
            this.btnConnect.TabIndex = 8;
            this.btnConnect.Text = "请求连接";
            this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click);
            // 
            // btnSend 
            // 
            this.btnSend.Location = new System.Drawing.Point(312, 64);
            this.btnSend.Name = "btnSend";
            this.btnSend.TabIndex = 9;
            this.btnSend.Text = "发送消息";
            this.btnSend.Click += new System.EventHandler(this.btnSend_Click);
            // 
            // btnExit 
            // 
            this.btnExit.Location = new System.Drawing.Point(312, 96);
            this.btnExit.Name = "btnExit";
            this.btnExit.TabIndex = 10;
            this.btnExit.Text = "关闭连接";
            this.btnExit.Click += new System.EventHandler(this.btnExit_Click);
            // 
            // statusBar1 
            // 
            this.statusBar1.Location = new System.Drawing.Point(0, 312);
            this.statusBar1.Name = "statusBar1";
            this.statusBar1.Size = new System.Drawing.Size(440, 22);
            this.statusBar1.TabIndex = 11;
            // 
            // Form1 
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
            this.ClientSize = new System.Drawing.Size(440, 334);
            this.Controls.Add(this.statusBar1);
            this.Controls.Add(this.btnExit);
            this.Controls.Add(this.btnSend);
            this.Controls.Add(this.btnConnect);
            this.Controls.Add(this.txtSendMsg);
            this.Controls.Add(this.listReceiveMsg);
            this.Controls.Add(this.txtPort);
            this.Controls.Add(this.txtIP);
            this.Controls.Add(this.label4);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.MaximizeBox = false;
            this.Name = "Form1";
            this.Text = "客户端";
            this.Closed += new System.EventHandler(this.Form1_Closed);
            this.ResumeLayout(false);

        }
        #endregion

        /// <summary> 
        /// 应用程序的主入口点。 
        /// </summary> 
        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

        private void btnConnect_Click(object sender, System.EventArgs e)
        {
            if (txtIP.Text == "")
            {
                MessageBox.Show("请输入IP地址", "警告");
                txtIP.Focus();
                return;
            }
            if (txtPort.Text == "")
            {
                MessageBox.Show("请输入端口号", "警告");
                txtPort.Focus();
                return;
            }
            try
            {
                myIP = IPAddress.Parse(txtIP.Text.Trim());
            }
            catch
            {
                MessageBox.Show("你输入的IP地址格式不正确,请重新输入", "警告");
            }
            try
            {
                MyServer = new IPEndPoint(myIP, Int32.Parse(txtPort.Text.Trim()));
                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                sock.BeginConnect(MyServer, new AsyncCallback(ConnectCallBack), sock);
                ConnectDone.WaitOne();
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }
        private void ConnectCallBack(IAsyncResult ar)
        {
            try
            {
                //获取状态 
                Socket client = (Socket)ar.AsyncState;
                client.EndConnect(ar);
                //自动发送数据 
                try
                {
                    byte[] byteData = System.Text.Encoding.BigEndianUnicode.GetBytes("准备完毕,可以通话!" + "\n\r");
                    //异步开始发送 
                    sock.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallBack), sock);
                }
                catch (Exception ee)
                {
                    MessageBox.Show(ee.Message);
                }
                statusBar1.Text = "与主机 : " + txtIP.Text + "端口: " + txtPort.Text + "建立连接!";

                //定义线程 
                Thread thread = new Thread(new ThreadStart(targett));
                //开始接收数据线程 
                thread.Start();
                //设为终止 
                ConnectDone.Set();
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }
        private void SendCallBack(IAsyncResult ar)
        {
            try
            {
                //获取状态 
                Socket client = (Socket)ar.AsyncState;
                //设为终止 
                SendDone.Set();
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }

        }
        private void targett()
        {
            try
            {
                StateObject state = new StateObject();
                state.workSocket = sock;
                //开始异步接收数据 
                sock.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }
        private void ReceiveCallBack(IAsyncResult ar)
        {
            try
            {
                //获取状态 
                StateObject state = (StateObject)ar.AsyncState;
                Socket client = state.workSocket;
                //结束异步读数据,并获取结果 
                int byteData = client.EndReceive(ar);
                //储存数据 
                state.sb.Append(System.Text.Encoding.BigEndianUnicode.GetString(state.buffer, 0, byteData));
                string msg = state.sb.ToString();
                //清除state.sb 
                state.sb.Remove(0, msg.Length);
                //显示接收的数据 
                listReceiveMsg.Items.Add(msg);
                //读取未读数据 
                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }

        private void btnExit_Click(object sender, System.EventArgs e)
        {
            try
            {
                sock.Close();
                statusBar1.Text = "与服务器断开连接";
            }
            catch
            {
                MessageBox.Show("连接尚未建立,断开无效!", "警告");
            }
        }

        private void btnSend_Click(object sender, System.EventArgs e)
        {
            try
            {
                byte[] byteData = System.Text.Encoding.BigEndianUnicode.GetBytes(txtSendMsg.Text);
                //开始异步发送数据 
                sock.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallBack), sock);
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
            }
        }

        private void Form1_Closed(object sender, System.EventArgs e)
        {
            sock.Close();
        }
    }
    public class StateObject
    {
        public Socket workSocket = null;//定义套接字 
        public const int BufferSize = 1024; //缓冲区大小 
        public byte[] buffer = new byte[BufferSize]; //缓冲区 
        public StringBuilder sb = new StringBuilder();//接收数据的字符串 
        public StateObject()
        {

        }
    }
}


Contributors: FHL