转至繁体中文版     | 网站首页 | 图文教程 | 资源下载 | 站长博客 | 图片素材 | 武汉seo | 武汉网站优化 | 
最新公告:     敏韬网|教学资源学习资料永久免费分享站!  [mintao  2008年9月2日]        
您现在的位置: 学习笔记 >> 图文教程 >> 软件开发 >> C语言系列 >> 正文
Socket 编程,一个服务器,多个客户端,互相通信         ★★★

Socket 编程,一个服务器,多个客户端,互相通信

作者:闵涛 文章来源:闵涛的学习笔记 点击数:2661 更新时间:2011/11/20 12:49:27
我只能给大家一个很简单的Socket的初级通信.

给大家做一个小的服务器,刚刚好前段时间做了一个小的聊天程序,实现了:

指定客户端发送消息,发送闪屏,支持服务器监听客户端发送消息

具体的代码如下:

首先是服务器.

以下是代码片段:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Net;//Endpoint
using System.Net.Sockets;//包含套接字
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO;

namespace Server
{
   
public partial class Form1 : Form
    {
       
public Form1()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls
= false;//关闭跨线程修改控件检查
        }


        Socket sokWatch
= null;//负责监听 客户端段 连接请求的  套接字(女生宿舍的大妈)
        Thread threadWatch = null;//负责 调用套接字, 执行 监听请求的线程

       
//开启监听 按钮
        private void btnStartListen_Click(object sender, EventArgs e)
        {
           
//实例化 套接字 (ip4寻址协议,流式传输,TCP协议)
            sokWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
           
//创建 ip对象
            IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
           
//创建网络节点对象 包含 ip和port
            IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
           
//将 监听套接字  绑定到 对应的IP和端口
            sokWatch.Bind(endpoint);
           
//设置 监听队列 长度为10(同时能够处理 10个连接请求)
            sokWatch.Listen(10);
            threadWatch
= new Thread(StartWatch);
            threadWatch.IsBackground
= true;
            threadWatch.Start();
            txtShow.AppendText(
"启动服务器成功......\r\n");
        }

       
//Dictionary<string, Socket> dictSocket = new Dictionary<string, Socket>();
        Dictionary<string, ConnectionClient> dictConn = new Dictionary<string, ConnectionClient>();
       
       
bool isWatch = true;

       
#region 1.被线程调用 监听连接端口
       
/// <summary>
       
/// 被线程调用 监听连接端口
       
/// </summary>
        void StartWatch()
        {
           
while (isWatch)
            {
               
//threadWatch.SetApartmentState(ApartmentState.STA);
               
//监听 客户端 连接请求,但是,Accept会阻断当前线程
                Socket sokMsg = sokWatch.Accept();//监听到请求,立即创建负责与该客户端套接字通信的套接字
                ConnectionClient connection = new ConnectionClient(sokMsg, ShowMsg, RemoveClientConnection);
               
//将负责与当前连接请求客户端 通信的套接字所在的连接通信类 对象 装入集合
                dictConn.Add(sokMsg.RemoteEndPoint.ToString(), connection);
               
//将 通信套接字 加入 集合,并以通信套接字的远程IpPort作为键
               
//dictSocket.Add(sokMsg.RemoteEndPoint.ToString(), sokMsg);
               
//将 通信套接字的 客户端IP端口保存在下拉框里
                cboClient.Items.Add(sokMsg.RemoteEndPoint.ToString());
                ShowMsg(
"接收连接成功......");
               
//启动一个新线程,负责监听该客户端发来的数据
               
//Thread threadConnection = new Thread(ReciveMsg);
               
//threadConnection.IsBackground = true;
               
//threadConnection.Start(sokMsg);
            }
        }
       
#endregion

       
bool isRec = true;//与客户端通信的套接字 是否 监听消息

       
#region 发送消息 到指定的客户端 -btnSend_Click
       
//发送消息 到指定的客户端
        private void btnSend_Click(object sender, EventArgs e)
        {
           
//byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(txtInput.Text.Trim());
           
//从下拉框中 获得 要哪个客户端发送数据
            string connectionSokKey = cboClient.Text;
           
if (!string.IsNullOrEmpty(connectionSokKey))
            {
               
//从字典集合中根据键获得 负责与该客户端通信的套接字,并调用send方法发送数据过去
                dictConn[connectionSokKey].Send(txtInput.Text.Trim());
               
//sokMsg.Send(arrMsg);
            }
           
else
            {
                MessageBox.Show(
"请选择要发送的客户端~~");
            }
        }
       
#endregion

       
//发送闪屏!!
        private void btnShack_Click(object sender, EventArgs e)
        {
            
string connectionSokKey = cboClient.Text;
            
if (!string.IsNullOrEmpty(connectionSokKey))
             {
                 dictConn[connectionSokKey].SendShake();
             }
            
else
             {
                 MessageBox.Show(
"请选择要发送的客户端~~");
             }
        }
       
//群闪
        private void btnShackAll_Click(object sender, EventArgs e)
        {
           
foreach (ConnectionClient conn in dictConn.Values)
            {
                conn.SendShake();
            }
        }

       
#region 2 移除与指定客户端的连接 +void RemoveClientConnection(string key)
       
/// <summary>
       
/// 移除与指定客户端的连接
       
/// </summary>
       
/// <param name="key">指定客户端的IP和端口</param>
        public void RemoveClientConnection(string key)
        {
           
if (dictConn.ContainsKey(key))
            {
                dictConn.Remove(key);
                cboClient.Items.Remove(key);
            }
        }
       
#endregion

       
//选择要发送的文件
        private void btnChooseFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd
= new OpenFileDialog();
           
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txtFilePath.Text
= ofd.FileName;
            }
        }

       
//发送文件
        private void btnSendFile_Click(object sender, EventArgs e)
        {
               
//拿到下拉框中选中的客户端IPPORT
                string key = cboClient.Text;
               
if (!string.IsNullOrEmpty(key))
                {
                    dictConn[key].SendFile(txtFilePath.Text.Trim());
                }
        }

       
#region 向文本框显示消息 -void ShowMsg(string msgStr)
       
/// <summary>
       
/// 向文本框显示消息
       
/// </summary>
       
/// <param name="msgStr">消息</param>
        public void ShowMsg(string msgStr)
        {
            txtShow.AppendText(msgStr
+ "\r\n");
        }
       
#endregion

       
private void btnSendMsgAll_Click(object sender, EventArgs e)
        {
           
foreach (ConnectionClient conn in dictConn.Values)
            {
                conn.Send(txtInput.Text.Trim());
            }
        }


    }
}
在这里,我新建了一个与客户端的通信和线程的类(ConnectionClient).

以下是代码片段:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;

namespace Server
{
   
/// <summary>
   
/// 与客户端的 连接通信类(包含了一个 与客户端 通信的 套接字,和线程)
   
/// </summary>
    public class ConnectionClient
    {
        Socket sokMsg;
        DGShowMsg dgShowMsg;
//负责 向主窗体文本框显示消息的方法委托
        DGShowMsg dgRemoveConnection;// 负责 从主窗体 中移除 当前连接
        Thread threadMsg;

       
#region 构造函数
       
/// <summary>
       
///
       
/// </summary>
       
/// <param name="sokMsg">通信套接字</param>
       
/// <param name="dgShowMsg">向主窗体文本框显示消息的方法委托</param>
        public ConnectionClient(Socket sokMsg, DGShowMsg dgShowMsg, DGShowMsg dgRemoveConnection)
        {
           
this.sokMsg = sokMsg;
           
this.dgShowMsg = dgShowMsg;
           
this.dgRemoveConnection = dgRemoveConnection;

           
this.threadMsg = new Thread(RecMsg);
           
this.threadMsg.IsBackground = true;
           
this.threadMsg.Start();
        }
       
#endregion

       
bool isRec = true;
       
#region 02负责监听客户端发送来的消息
       
void RecMsg()
        {
           
while (isRec)
            {
               
try
                {
                   
byte[] arrMsg = new byte[1024 * 1024 * 2];
                   
//接收 对应 客户端发来的消息
                    int length = sokMsg.Receive(arrMsg);
                   
//将接收到的消息数组里真实消息转成字符串
                    string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg, 0, length);
                   
//通过委托 显示消息到 窗体的文本框
                    dgShowMsg(strMsg);
                }
               
catch (Exception ex)
                {
                    isRec
= false;
                   
//从主窗体中 移除 下拉框中对应的客户端选择项,同时 移除 集合中对应的 ConnectionClient对象
                    dgRemoveConnection(sokMsg.RemoteEndPoint.ToString());
                }
            }
        }
       
#endregion

       
#region 03向客户端发送消息
       
/// <summary>
       
/// 向客户端发送消息
       
/// </summary>
       
/// <param name="strMsg"></param>
        public void Send(string strMsg)
        {
           
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
           
byte[] arrMsgFinal = new byte[arrMsg.Length+1];

            arrMsgFinal[
0] = 0;//设置 数据标识位等于0,代表 发送的是 文字
            arrMsg.CopyTo(arrMsgFinal, 1);

            sokMsg.Send(arrMsgFinal);
        }
       
#endregion

       
#region 04向客户端发送文件数据 +void SendFile(string strPath)
       
/// <summary>
       
/// 04向客户端发送文件数据
       
/// </summary>
       
/// <param name="strPath">文件路径</param>
        public void SendFile(string strPath)
        {
           
//通过文件流 读取文件内容
            using (FileStream fs = new FileStream(strPath, FileMode.OpenOrCreate))
            {
               
byte[] arrFile = new byte[1024 * 1024 * 2];
               
//读取文件内容到字节数组,并 获得 实际文件大小
                int length = fs.Read(arrFile, 0, arrFile.Length);
               
//定义一个 新数组,长度为文件实际长度 +1
                byte[] arrFileFina = new byte[length + 1];
                arrFileFina[
0] = 1;//设置 数据标识位等于1,代表 发送的是文件
               
//将 文件数据数组 复制到 新数组中,下标从1开始
               
//arrFile.CopyTo(arrFileFina, 1);
                Buffer.BlockCopy(arrFile, 0, arrFileFina, 1, length);
               
//发送文件数据
                sokMsg.Send(arrFileFina);//, 0, length + 1, SocketFlags.None);
            }
        }
       
#endregion

       
#region 05向客户端发送闪屏
       
/// <summary>
       
/// 向客户端发送闪屏
       
/// </summary>
       
/// <param name="strMsg"></param>
        public void SendShake()
        {
           
byte[] arrMsgFinal = new byte[1];
            arrMsgFinal[
0] = 2;
            sokMsg.Send(arrMsgFinal);
        }
       
#endregion

       
#region 06关闭与客户端连接
       
/// <summary>
       
/// 关闭与客户端连接
       
/// </summary>
        public void CloseConnection()
        {
            isRec
= false;
        }
       
#endregion
    }
}

万事俱备只欠客户端.
以下是代码片段:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Windows.Forms;
using System.IO;

namespace Client
{
   
public partial class Form1 : Form
    {
       
public Form1()
        {
            InitializeComponent();
            TextBox.CheckForIllegalCrossThreadCalls
= false;
        }

        Socket sokClient
= null;//负责与服务端通信的套接字
        Thread threadClient = null;//负责 监听 服务端发送来的消息的线程
        bool isRec = true;//是否循环接收服务端数据

       
private void btnConnect_Click(object sender, EventArgs e)
        {
           
//实例化 套接字
            sokClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
           
//创建 ip对象
            IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
           
//创建网络节点对象 包含 ip和port
            IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
           
//连接 服务端监听套接字
            sokClient.Connect(endpoint);
           
           
//创建负责接收 服务端发送来数据的 线程
            threadClient = new Thread(ReceiveMsg);
            threadClient.IsBackground
= true;
           
//如果在win7下要通过 某个线程 来调用 文件选择框的代码,就需要设置如下
            threadClient.SetApartmentState(ApartmentState.STA);
            threadClient.Start();
        }

       
/// <summary>
       
/// 接收服务端发送来的消息数据
       
/// </summary>
        void ReceiveMsg()
        {
           
while (isRec)
            {
               
byte[] msgArr = new byte[1024 * 1024 * 1];//接收到的消息的缓冲区
                int length=0;
               
//接收服务端发送来的消息数据
                length =sokClient.Receive(msgArr);//Receive会阻断线程
                if (msgArr[0] == 0)//发送来的是文字
                {
                   
string strMsg = System.Text.Encoding.UTF8.GetString(msgArr, 1, length - 1);
                    txtShow.AppendText(strMsg
+ "\r\n");
                }
               
else if (msgArr[0] == 1) { //发送来的是文件
                    SaveFileDialog sfd = new SaveFileDialog();
                   
//弹出文件保存选择框
                    if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                       
//创建文件流
                        using (FileStream fs = new FileStream(sfd.FileName, FileMode.OpenOrCreate))
                        {
                            fs.Write(msgArr,
1, length - 1);
                            MessageBox.Show(
"文件保存成功!");
                        }
                    }
                }
               
else if (msgArr[0] == 2) {
                    ShakeWindow();
                }
            }
        }

       
/// <summary>
       
/// 闪屏
       
/// </summary>
        private void ShakeWindow()
        {
            Random ran
= new Random();
           
//保存 窗体原坐标
            System.Drawing.Point point = this.Location;
           
for (int i = 0; i < 30; i++)
            {
               
//随机 坐标
                this.Location = new System.Drawing.Point(point.X + ran.Next(8), point.Y + ran.Next(8));
                System.Threading.Thread.Sleep(
15);//休息15毫秒
                this.Location = point;//还原 原坐标(窗体回到原坐标)
                System.Threading.Thread.Sleep(15);//休息15毫秒
            }
        }

       
//发送消息
        private void btnSend_Click(object sender, EventArgs e)
        {
           
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(txtInput.Text.Trim());
            sokClient.Send(arrMsg);
        }
    }
}

这上面的代码,就能实现Socket通信,可以实现聊天.

希望能对大家有点帮助!

由于代码有点长.分段发的...

怎么解决客户端或服务器端Socket.Close()的时候,另一段出现的异常?谢谢!
专门去定义一个处理异常的方法

以下是代码片段:
//移除与指定客户端的连接
//key 指定客户端的IP和端口
public void RemoveClient(string key)
{
   
if(conn.ContainsKey(key))
    {
      
//删除这个客户端
       conn.Remove(key);
      
//看看是否有这个客户端.
       cboClient.Items.Remove(key);
    }
}

就是需要去判断一下这个客户端是否包含这个key

如果包含,那么则移除掉这个key,然后把combox控件上面的这个客户端移除!
[网络技术]PPTP服务器的端口  [聊天工具]Office 2000 服务器扩展
[聊天工具]Firefox小插件gspace把Gmail变FTP服务器  [系统软件]突破Windows 2003 PHP服务器的新思路
[常用软件]微软最新VoIP服务器及客户端软件下周开测  [常用软件]uTorrent:史上最省资源BT客户端试用
[常用软件]越看越流畅三款主流网络电视客户端导购  [常用软件]Allpeers:让Firefox摇身一变为P2P客户端
[常用软件]编译给自己专用的FTP客户端  [常用软件][网络]FTPRush FTP客户端 软件评测
教程录入:mintao    责任编辑:mintao 
  • 上一篇教程:

  • 下一篇教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      注:本站部分文章源于互联网,版权归原作者所有!如有侵权,请原作者与本站联系,本站将立即删除! 本站文章除特别注明外均可转载,但需注明出处! [MinTao学以致用网]
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    同类栏目
    · C语言系列  · VB.NET程序
    · JAVA开发  · Delphi程序
    · 脚本语言
    更多内容
    热门推荐 更多内容
  • 没有教程
  • 赞助链接
    更多内容
    闵涛博文 更多关于武汉SEO的内容
    500 - 内部服务器错误。

    500 - 内部服务器错误。

    您查找的资源存在问题,因而无法显示。

    | 设为首页 |加入收藏 | 联系站长 | 友情链接 | 版权申明 | 广告服务
    MinTao学以致用网

    Copyright @ 2007-2012 敏韬网(敏而好学,文韬武略--MinTao.Net)(学习笔记) Inc All Rights Reserved.
    闵涛 投放广告、内容合作请Q我! E_mail:admin@mintao.net(欢迎提供学习资源)

    站长:MinTao ICP备案号:鄂ICP备11006601号-18

    闵涛站盟:医药大全-武穴网A打造BCD……
    咸宁网络警察报警平台