黑马程序员--Socket套接字聊天室

时间:2023-02-21 14:34:38

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

 

 Socket的英文原意是"孔"或"插座".作为进程通信机制,取后一种意思,通常也成为"套接字".用于描述IP地址和端口,是一个通信链的句柄(其实就是两个程序通信用的).

有两种类型

①.流式Socket(STREAM)

  是一种面向连接的Socket,针对与面向连接的TCP服务应用,安全,但效率低.

②.数据报式Socket(DATAGRAM)

  是一种无连接的Socket,对应于无连接的UDP服务应用,不安全,但效率高

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

①.服务器端的Socket(至少需要两个)

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

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

②.客户端Socket

  1.必须指定要连接的服务端地址和端口.

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

Socket的通讯过程

①.服务器端

  1.申请一个Socket

  2.绑定到一个IP地址和一个端口上

  3.开启侦听,等待接受连接

②.客户端

  1.申请一个Socket

  2.连接服务器(指明IP地址和端口号)

服务器端接到连接请求后,产生一个新的Socket(端口大于1024)与客户端建立连接并进行通讯,原监听Socket继续监听

 

Socket方法

  相关类

    IPAddress类:包含了一个IP地址

    IPEndPoint类:包含了一对IP地址和端口号

  方法

    Socket():创建一个Socket

    Bind():绑定一个本地的IP和端口号(IPEndPoint)

    Listen():让Socket侦听传入的连接尝试,并指定侦听队列容量

    Connect():初始化与另一个Socket的连接

    Accpet():接收连接并返回一个新的Socket

    Send():输出数据到Socket

    Receive():从Socket中读取数据

    Close():关闭Socket(销毁连接)

一.首先,进行聊天室服务端和客户端的WINFORM框架设计

黑马程序员--Socket套接字聊天室

 

黑马程序员--Socket套接字聊天室

二.服务器端的代码

黑马程序员--Socket套接字聊天室黑马程序员--Socket套接字聊天室MyChatRoomServer
  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 using System.Net.Sockets;
 10 using System.Net;
 11 using System.Threading;
 12 using System.IO;
 13 
 14 namespace MyChatRoomServer
 15 {
 16     public partial class FChatServer : Form
 17     {
 18         public FChatServer()
 19         {
 20             InitializeComponent();
 21             TextBox.CheckForIllegalCrossThreadCalls = false;//关闭对 文本框的跨线程操作检查
 22         }        
 23         Thread threadwatch = null;//负责监听 客户端 连接请求的线程
 24         Socket socketwatch = null;//创建 服务端 负责监听的套接字
 25 
 26         private void btnbeginlisten_Click(object sender, EventArgs e)
 27         {
 28             //创建 服务端 负责监听的套接字,参数(使用IP4寻址协议,使用流式连接,使用TCP协议传输数据)
 29             socketwatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 30             //获得文本框中的IP地址
 31             IPAddress address = IPAddress.Parse(txtip.Text.Trim());
 32             //创建包含IP和PORT的网络节点对象
 33             IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtport.Text.Trim()));
 34             //将负责监听的套接字绑定到唯一的IP和PORT上
 35             socketwatch.Bind(endpoint);
 36             //设置监听队列的长度
 37             socketwatch.Listen(10);
 38             threadwatch = new Thread(WatchConnecting);
 39             threadwatch.IsBackground = true;//设置为后台线程
 40             threadwatch.Start();//开启线程
 41             ShowMsg("服务器启动监听成功");
 42             
 43         }
 44         //保存了服务器端所有负责和客户端通信的套接字
 45         Dictionary<string, Socket> dict = new Dictionary<string, Socket>();
 46         //保存了服务器端所有负责调用 通信套接字.recive方法的线程
 47         Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>();
 48         /// <summary>
 49         /// 监听客户端请求的方法
 50         /// </summary>
 51         //Socket sokConnection = null;//创建监听套接字
 52         void WatchConnecting()
 53         {
 54             while (true)//持续不断的监听新的客户端请求
 55             {
 56                 //开始监听 客户端 连接请求,注意: Accept方法会阻断当前线程!
 57                 Socket sokConnection = socketwatch.Accept();//一旦监听到客户端的请求,就返回一个负责和该客户端通信的套接字 sokconnection
 58                 //向列表控件中添加一个客户端的IP端口字符串,作为客户端的唯一标识
 59                 lbOnline.Items.Add(sokConnection.RemoteEndPoint.ToString());
 60                 //将与客户端通信的套接字对象sokConnection添加到键值对集合中,并以客户端ip端口作为键
 61                 dict.Add(sokConnection.RemoteEndPoint.ToString(),sokConnection);
 62                 //创建通信线程                
 63                 ParameterizedThreadStart pts = new ParameterizedThreadStart(RecMsg);
 64                 Thread thr = new Thread(pts);
 65                 thr.SetApartmentState(ApartmentState.STA);
 66                 thr.IsBackground = true;
 67                 thr.Start(sokConnection);
 68                 dictThread.Add(sokConnection.RemoteEndPoint.ToString(), thr);
 69 
 70                 ShowMsg("客户端连接成功!"+sokConnection.RemoteEndPoint.ToString());
 71             }
 72         }
 73         /// <summary>
 74         /// 服务器端负责监听客户端发送来的数据的方法
 75         /// </summary>
 76         void RecMsg(object socketclientpara)
 77         {
 78             Socket socketclient = socketclientpara as Socket;
 79             while (true)
 80             {
 81                 //定义一个接收用的缓存区(2M字节数组)
 82                 byte[] arrmsgrec = new byte[1024 * 1024 * 2];
 83                 //将接收到的数据存入arrmsgrec数组,并返回真正接收到的数据的长度
 84                 int length = -1;
 85                 try
 86                 {
 87                     length = socketclient.Receive(arrmsgrec);
 88                 }
 89                 catch (SocketException ex)
 90                 {
 91                     ShowMsg("异常:" + ex.Message+",RemoteEndPoint="+socketclient.RemoteEndPoint.ToString());
 92                     //从通信套接字集合中删除被中断连接的通信 套接字对象
 93                     dict.Remove(socketclient.RemoteEndPoint.ToString());
 94                     //从 通信线程 集合中 删除 被  中断连接的 通信线程 对象
 95                     dictThread.Remove(socketclient.RemoteEndPoint.ToString());
 96                     //从 列表 中 移除 被中断连接的对象的 IP POINT
 97                     lbOnline.Items.Remove(socketclient.RemoteEndPoint.ToString());
 98                     break;
 99                 }
100                 catch(Exception ex)
101                 {
102                     ShowMsg("异常:" + ex.Message);
103                 }
104                 if (arrmsgrec[0] == 0)//判断发送过来的数据第一个元素是0,则发送过来的是文字数据
105                 {
106                     
107                     string strmsgrec = System.Text.Encoding.UTF8.GetString(arrmsgrec, 1, length-1);
108                     ShowMsg(strmsgrec);
109                 }
110                 else if (arrmsgrec[0] == 1)//如果是1,则代表发送过来的是文件数据(图片/视频/文件...)
111                 {
112                     SaveFileDialog sfd = new SaveFileDialog();//保存文件选择框对象
113                     if(sfd.ShowDialog()==System.Windows.Forms.DialogResult.OK)//用户选择文件路径后
114                     {
115                         string fileSavePath = sfd.FileName;//获得要保存的文件路径
116                         //创建文件流,然后让文件流来 根据路径 创建一个文件
117                         using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))
118                         {
119                             fs.Write(arrmsgrec,1, length-1);
120                             ShowMsg("文件保存成功:" + fileSavePath);
121                         }
122                     }
123                 }
124             }            
125         }
126         
127         //发送消息到客户端
128         private void btnsend_Click(object sender, EventArgs e)
129         {
130             if (string.IsNullOrEmpty(lbOnline.Text))
131             {
132                 MessageBox.Show("请选择好友");
133             }
134             else
135             {
136                 string strmsg = txtmsgsend.Text.Trim();
137                 //将要发送的字符串 转成 UTF8对应的字节数组
138                 byte[] arrmsg = System.Text.Encoding.UTF8.GetBytes(strmsg);
139                 //获得列表中选定的KEY
140                 string strClientKey = lbOnline.Text;
141                 //通过KEY,使用字典集合中对应的与某个客户端通信的套接字的send方法,发送数据给对方
142                 dict[strClientKey].Send(arrmsg);
143                 //sokConnection.Send(arrmsg);          
144                 ShowMsg("发送了数据出去" + strmsg);
145             }
146             
147             
148         }
149         void ShowMsg(string msg)
150         {
151             txtmsg.AppendText(msg + "\r\n");
152         }
153 
154         private void txtmsg_TextChanged(object sender, EventArgs e)
155         {
156 
157         }
158         //服务端群发消息
159         private void btnSendToAll_Click(object sender, EventArgs e)
160         {
161             string strMsg = txtmsgsend.Text.Trim();
162             byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
163             foreach (Socket s in dict.Values)
164             {
165                 s.Send(arrMsg);
166             }
167             ShowMsg("群发完毕:)");
168         }
169     }
170 }

三.客户端的代码

黑马程序员--Socket套接字聊天室黑马程序员--Socket套接字聊天室MyChatRoomClient
  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 using System.Net.Sockets;
 10 using System.Net;
 11 using System.Threading;
 12 using System.IO;
 13 
 14 namespace MyChatRoomClient
 15 {
 16     public partial class Form1 : Form
 17     {
 18         public Form1()
 19         {
 20             InitializeComponent();
 21             TextBox.CheckForIllegalCrossThreadCalls = false;
 22         }
 23         Thread ThreadClient = null;//客户端用来接收服务端发来的数据消息的线程
 24         Socket socketclient = null;
 25         private void btnbeginlisten_Click(object sender, EventArgs e)
 26         {
 27             IPAddress address = IPAddress.Parse(txtip.Text.Trim());
 28             IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtport.Text.Trim()));
 29             socketclient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 30             socketclient.Connect(endpoint);
 31             //开启线程,监听服务端发来的消息
 32             ThreadClient = new Thread(RecMsg);
 33             ThreadClient.IsBackground = true;
 34             ThreadClient.Start();
 35         }
 36         void RecMsg()
 37         {
 38             while(true)
 39             {
 40             //定义一个接收用的缓存区(2M字节数组)
 41             byte[] arrmsgrec = new byte[1024 * 1024 * 2];
 42             //将接收到的数据存入arrmsgrec数组,并返回真正接收到的数据的长度
 43             int length=socketclient.Receive(arrmsgrec);
 44             //此时是将数组所有的元素都转成字符串,而真正接收到的只有服务端发来的几个字符
 45             string strmsgrec = System.Text.Encoding.UTF8.GetString(arrmsgrec,0,length);
 46             ShowMsg(strmsgrec);
 47             }
 48         }
 49         void ShowMsg(string msg)
 50         {
 51             txtmsg.AppendText(msg + "\r\n");
 52         }
 53 
 54         private void Form1_Load(object sender, EventArgs e)
 55         {
 56 
 57         }
 58         //向服务器发送文本消息
 59         private void btnSendMsg_Click(object sender, EventArgs e)
 60         {
 61             string strMsg = textMsgSend.Text.Trim();
 62             //将字符串 转成 方便网络传送的 二进制数据
 63             byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
 64             byte[] arrMsgSend = new byte[arrMsg.Length + 1];
 65             arrMsgSend[0] = 0;//设置标识位,0代表的是文字
 66             Buffer.BlockCopy(arrMsg, 0, arrMsgSend, 1, arrMsg.Length);
 67             socketclient.Send(arrMsgSend);
 68             ShowMsg("我说" + strMsg);
 69 
 70         }
 71         
 72         private void textMsgSend_TextChanged(object sender, EventArgs e)
 73         {
 74 
 75         }
 76         //选择要发送的文件
 77         private void btnChooseFile_Click(object sender, EventArgs e)
 78         {
 79             OpenFileDialog ofd = new OpenFileDialog();
 80             if(ofd.ShowDialog()==System.Windows.Forms.DialogResult.OK)
 81             {
 82                 txtFilePath.Text = ofd.FileName;
 83             }
 84         }
 85         //向服务端发送文件
 86         private void btnSendFile_Click(object sender, EventArgs e)
 87         {
 88             ////用文件流打开 用户选择的 文件
 89             //using(FileStream fs=new FileStream(txtFilePath.Text,FileMode.Open))
 90             //{
 91             //    byte[] arrFile=new byte[1024*1024*2];//定义一个2M的数组(缓存区)
 92             //    //将文件数据读到数组arrFlie中,并获得读取的真实数据长度length
 93             //    int length=fs.Read(arrFile, 0, arrFile.Length);
 94             //    byte[] arrFileSend = new byte[length + 1];
 95             //    arrFileSend[0] = 1;//1代表发送的是文件数据
 96             //    //for (int i = 0; i < length; i++)
 97             //    //{
 98             //    //    arrFileSend[i + 1] = arrFile[i];
 99             //    //}
100             //    //将arrFile数组里的元素 从第0个拷贝,拷贝到arrFileSend数组里,从第1个位置开始存放,一共存放
101             //    //length个数据
102             //    Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length);
103             //    //发送包含了标识位的新数据数组到服务端                
104             //    socketclient.Send(arrFileSend);            
105             //}
106             ThreadClient = new Thread(SendFile);
107             ThreadClient.IsBackground = true;
108             ThreadClient.Start();
109         }
110         void SendFile()
111         {
112             //用文件流打开 用户选择的 文件
113             using (FileStream fs = new FileStream(txtFilePath.Text, FileMode.Open))
114             {
115                 byte[] arrFile = new byte[1024 * 1024 * 2];//定义一个2M的数组(缓存区)
116                 //将文件数据读到数组arrFlie中,并获得读取的真实数据长度length
117                 int length = fs.Read(arrFile, 0, arrFile.Length);
118                 byte[] arrFileSend = new byte[length + 1];
119                 arrFileSend[0] = 1;//1代表发送的是文件数据
120                 //for (int i = 0; i < length; i++)
121                 //{
122                 //    arrFileSend[i + 1] = arrFile[i];
123                 //}
124                 //将arrFile数组里的元素 从第0个拷贝,拷贝到arrFileSend数组里,从第1个位置开始存放,一共存放
125                 //length个数据
126                 Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length);
127                 //发送包含了标识位的新数据数组到服务端                
128                 socketclient.Send(arrFileSend);
129             }
130         }
131 
132         private void txtFilePath_TextChanged(object sender, EventArgs e)
133         {
134 
135         }
136     }
137 }

 

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