局域网之间通讯有很多种方法,比如可以用数据库存储的方式实现,还可以用udp来实现,这里是用tcp来实现的,用tcp来通讯的话可以说是有点麻烦的,因为要保持各个用户与服务器之间的连接,连接一断便不能通讯了,所以个人觉得这个地方比较麻烦;
要实现Socket通讯的话首先可以把它分成两个项目来区别,一个是服务器项目(Server),一个是客户端项目(Client)。
服务器项目的实现可以大致的分成 部分,1:创建一个总连接点,这个连接点负责与客户端的连接,我们不能把连接和通讯都引用到一个连接上,这样服务器会崩溃的,而每个用户都是独立的,所以我们要利用线程来创建一个新的通讯实例,这个连接和通讯各个用户之间都是互不干扰的了,还有一个要注意的就是跨线程调用控件的话是不行的,所以我们又要利用委托存放要调用的功能在用this.Invoke()的方法来跨线程引用控件,接下来就是具体实现响应通讯了;
客户端相对来说要更容易些,创建一个客户端连接实例,与远程服务器连接起来,然后就是大致的通讯实现功能了。
下面是我个人的实现代码和实现的心得,附加了大致的注释,实现了群聊,私聊等功能。
通讯类(Communication)
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Server 8 { 9 [Serializable] 10 public class Communication//通讯 11 { 12 public string Name { get; set; } 13 public string Ip { get; set; }//Ip地址 14 public string Port { get; set; }//端口号 15 public string Message { get; set; }//发送的消息 16 public int Types { get; set; } 17 //1代表聊天信息 2代表客户端人物信息 18 public bool IsFistLogin { get; set; } 19 public string ToEndPoint { get; set; } 20 } 21 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Net.Sockets; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace Server 9 { 10 [Serializable] 11 public class ServerReturnInfo 12 { 13 public Communication comm { get; set; }//通讯类 14 public Socket client { get; set; } 15 } 16 }
服务器类(Server)
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.IO; 7 using System.Linq; 8 using System.Net; 9 using System.Net.Sockets; 10 using System.Runtime.Serialization.Formatters.Binary; 11 using System.Text; 12 using System.Threading; 13 using System.Threading.Tasks; 14 using System.Windows.Forms; 15 16 namespace Server 17 { 18 public partial class forServer : Form 19 { 20 public forServer() 21 { 22 InitializeComponent(); 23 } 24 25 private delegate void ListDelegate(Communication comm); 26 private ListDelegate listInfo = null;//添加成员 27 private delegate void MessageDelegate(Communication comm); 28 private MessageDelegate MessageInfo = null;//添加消息 29 private delegate void SendDelegate(ServerReturnInfo sri); 30 private SendDelegate SendInfo = null; 31 private delegate void EndDelegate(Communication commun); 32 private EndDelegate EndInfo = null; 33 private List<Socket> socketList = new List<Socket>();//成员集合 34 private Socket server = null; 35 private void 开启服务ToolStripMenuItem_Click(object sender, EventArgs e) 36 { 37 int port = 0; 38 server = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); 39 if (!int.TryParse(txtPort.Text, out port)) 40 port = 8080; 41 EndPoint endPoint = new IPEndPoint(IPAddress.Any,port); 42 server.Bind(endPoint); 43 server.Listen(1000); 44 rtbMessage.Text = "服务器已经打开!"; 45 Thread t = new Thread(GetClient); 46 t.Start(server); 47 } 48 public void GetClient(object s)//获取客户端实例 49 { 50 Socket server = s as Socket; 51 Socket client = null; 52 while(true) 53 { 54 try 55 { 56 client = server.Accept(); 57 } 58 catch (Exception) 59 { 60 return; 61 } 62 socketList.Add(client);//把客户端实例添加到集合中 63 DisMantle(client); 64 Thread t2 = new Thread(ReceiveInfo); 65 t2.Start(client); 66 } 67 } 68 //客户端人物信息绑定与上线提醒 69 public void DisMantle(Socket client) 70 { 71 string endPoint = client.RemoteEndPoint.ToString(); 72 string ip = endPoint.Split(':')[0]; 73 string port = endPoint.Split(':')[1]; 74 string name = Dns.GetHostEntry(ip).HostName; 75 Communication comm = new Communication() 76 { 77 Name = name, 78 Ip = ip, 79 Port = port, 80 Message = name + "上线了!" 81 }; 82 //通过委托来进行对控件的跨线程修改 83 //绑定客户端人物信息到ListView控件上 84 listInfo = new ListDelegate(BindListView); 85 this.Invoke(listInfo, comm); 86 //添加聊天消息 87 MessageInfo = new MessageDelegate(AddMessage); 88 this.Invoke(MessageInfo, comm); 89 ServerReturnInfo sri = new ServerReturnInfo() 90 { 91 comm = comm, 92 client = client 93 }; 94 SendInfo = new SendDelegate(SendInfos); 95 this.Invoke(SendInfo, sri); 96 } 97 98 public void SendInfos(object c) //发送列表成员信息到客户端 99 { 100 ServerReturnInfo sri = c as ServerReturnInfo; 101 byte[] b = null; 102 foreach (Socket s in socketList) 103 { 104 sri.comm.Types = 2; 105 sri.comm.IsFistLogin = true; 106 for (int i = 0; i < lvInfo.Items.Count; i++) 107 { 108 sri.comm.Name = lvInfo.Items[i].SubItems[0].Text; 109 sri.comm.Ip = lvInfo.Items[i].SubItems[1].Text; 110 sri.comm.Port = lvInfo.Items[i].SubItems[2].Text; 111 b = SerializeObject(sri.comm); 112 s.Send(b); 113 sri.comm.IsFistLogin = false; 114 } 115 } 116 } 117 public void EndClient(Communication commun) //移除成员列表里离线的成员 118 { 119 foreach (ListViewItem items in lvInfo.Items) 120 { 121 //如果成员列表上有此用户,则移除 122 if (items.SubItems[0].Text.Equals(commun.Name)) 123 { 124 items.Remove(); 125 break; 126 } 127 } 128 } 129 public void ReceiveInfo(object o) //获取客户端信息 130 { 131 Socket client = o as Socket; 132 byte[] b = new byte[1024]; 133 Communication commun = null; 134 while (true) 135 { 136 try 137 { 138 client.Receive(b); 139 commun = DeserializeObject(b) as Communication; 140 } 141 catch (Exception) 142 { 143 144 return; 145 } 146 if (commun.Types == 1)//客户端发送过来的信息 147 { 148 MessageInfo = new MessageDelegate(AddMessage); 149 this.Invoke(MessageInfo, commun); 150 byte[] bb = SerializeObject(commun); 151 foreach (Socket c in socketList) 152 { 153 c.Send(bb);//把客户端的消息发送到其它的客户端 154 } 155 } 156 else if (commun.Types == 3) //私聊,判断终结点是否一致 157 { 158 MessageInfo = new MessageDelegate(AddMessage); 159 this.Invoke(MessageInfo, commun); 160 byte[] bb = SerializeObject(commun); 161 foreach (Socket c in socketList) 162 { 163 string ClientEndPoint = c.RemoteEndPoint.ToString(); 164 if (commun.ToEndPoint.Equals(ClientEndPoint)) 165 { 166 c.Send(bb); 167 break; 168 } 169 } 170 }else if(commun.Types == 4) 171 { 172 MessageInfo = new MessageDelegate(AddMessage); 173 this.Invoke(MessageInfo, commun); 174 byte[] bb = SerializeObject(commun); 175 try 176 { 177 foreach (Socket c in socketList) 178 { 179 if (c.Connected) 180 c.Send(bb);//把客户端的离线消息发送到其它的客户端 181 } 182 } 183 catch (Exception) 184 { 185 socketList.Remove(client);//从集合中删除该用户 186 EndInfo = new EndDelegate(EndClient); 187 this.Invoke(EndInfo, commun); 188 client.Dispose();//关闭端口 189 return; 190 } 191 } 192 } 193 } 194 public void ClearList() 195 { 196 lvInfo.Items.Clear();//清除ListView控件上的数据 197 } 198 public void BindListView(Communication comm) //绑定客户端人物信息到ListView控件上 199 { 200 ListViewItem items = new ListViewItem(comm.Name); 201 items.SubItems.Add(comm.Ip); 202 items.SubItems.Add(comm.Port); 203 lvInfo.Items.Add(items); 204 } 205 public void AddMessage(Communication comm)//添加聊天消息 206 { 207 rtbMessage.Text += "\n"+comm.Name + ":" + comm.Message; 208 } 209 210 public object DeserializeObject(byte[] pBytes)//反序列化二进制为对象 211 { 212 object newOjb = null; 213 if (pBytes == null) 214 return newOjb; 215 MemoryStream memory = new MemoryStream(pBytes); 216 memory.Position = 0; 217 BinaryFormatter formatter = new BinaryFormatter(); 218 newOjb = formatter.Deserialize(memory); 219 memory.Close(); 220 return newOjb; 221 } 222 public byte[] SerializeObject(object pObj)//序列化对象为二进制的数 223 { 224 if (pObj == null) 225 return null; 226 MemoryStream memory = new MemoryStream(); 227 BinaryFormatter formatter = new BinaryFormatter(); 228 formatter.Serialize(memory, pObj); 229 memory.Position = 0; 230 byte[] read = new byte[memory.Length]; 231 memory.Read(read, 0, read.Length); 232 memory.Close(); 233 return read; 234 } 235 236 private void 退出程序ToolStripMenuItem_Click(object sender, EventArgs e) 237 { 238 Application.Exit(); 239 } 240 241 private void forServer_FormClosing(object sender, FormClosingEventArgs e) 242 { 243 //检查服务是否还在工作 244 } 245 246 private void forServer_Load(object sender, EventArgs e) 247 { 248 txtPort.Text = "8080"; 249 } 250 } 251 }
客户端类(Client)
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.IO; 7 using System.Linq; 8 using System.Net; 9 using System.Net.Sockets; 10 using System.Runtime.Serialization.Formatters.Binary; 11 using System.Text; 12 using System.Threading; 13 using System.Threading.Tasks; 14 using System.Windows.Forms; 15 using Server; 16 17 namespace Client 18 { 19 public partial class forClient : Form 20 { 21 public forClient() 22 { 23 InitializeComponent(); 24 } 25 26 private delegate void ListDelegate(Communication comm); 27 private ListDelegate listInfo = null;//用于绑定成员 28 private delegate void MessageDelegate(Communication comm); 29 private MessageDelegate MessageInfo = null;//用于添加聊天消息 30 private delegate void ClearDelegate(); 31 private ClearDelegate ClearInfo = null;//用于清除成员列表 32 private delegate void EndDelegate(Communication commun); 33 private EndDelegate EndInfo = null; 34 Socket client = null; 35 EndPoint endPoint = null; 36 private void 开启服务ToolStripMenuItem_Click(object sender, EventArgs e) 37 { 38 int port = 0; 39 client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 40 if (!int.TryParse(txtPort.Text, out port)) 41 port = 8080; 42 endPoint = new IPEndPoint(IPAddress.Parse(txtIP.Text), port); 43 client.Connect(endPoint); 44 rtbMessage.Text = "连接服务器成功!"; 45 Thread t = new Thread(ReceiveInfo); 46 t.Start(client); 47 } 48 public void ClearList() //清除成员列表 49 { 50 this.lvInfo.Clear(); 51 } 52 public void EndClient(Communication commun) //移除成员列表里离线的成员 53 { 54 foreach (ListViewItem items in lvInfo.Items) 55 { 56 //如果成员列表上有此用户,则移除 57 if (items.SubItems[0].Text.Equals(commun.Name)) 58 { 59 items.Remove(); 60 break; 61 } 62 } 63 } 64 public void ReceiveInfo(object c)//接收服务器的信息 65 { 66 byte[] b = new byte[1024]; 67 Socket client = c as Socket; 68 ClearInfo = new ClearDelegate(ClearList); 69 while(true) 70 { 71 try 72 { 73 client.Receive(b); 74 } 75 catch (Exception) 76 { 77 client.Dispose(); 78 return; 79 } 80 Communication comm = DeserializeObject(b) as Communication; 81 if (comm.Types == 2)//服务器回发过来的信息 82 { 83 listInfo = new ListDelegate(BindListView); 84 this.Invoke(listInfo, comm); 85 } 86 else if (comm.Types == 1 || comm.Types == 3) 87 { 88 MessageInfo = new MessageDelegate(AddMessage); 89 this.Invoke(MessageInfo, comm); 90 } 91 else //下线执行 92 { 93 MessageInfo = new MessageDelegate(AddMessage); 94 this.Invoke(MessageInfo, comm); 95 EndInfo = new EndDelegate(EndClient); 96 this.Invoke(EndInfo,comm); 97 } 98 } 99 } 100 public void BindListView(Communication comm) //绑定客户端人物信息到ListView控件上 101 { 102 if(comm.IsFistLogin) 103 { 104 lvInfo.Items.Clear();//第一次添加的时候(判定为true时)清除原有的旧成员 105 } 106 ListViewItem items = new ListViewItem(comm.Name); 107 items.SubItems.Add(comm.Ip); 108 items.SubItems.Add(comm.Port); 109 lvInfo.Items.Add(items); 110 } 111 public void AddMessage(Communication comm)//添加聊天消息 112 { 113 rtbMessage.Text += "\n"+comm.Name + ":" + comm.Message; 114 } 115 public object DeserializeObject(byte[] pBytes)//反序列化二进制为对象 116 { 117 object newOjb = null; 118 if (pBytes == null) 119 return newOjb; 120 MemoryStream memory = new MemoryStream(pBytes); 121 memory.Position = 0; 122 BinaryFormatter formatter = new BinaryFormatter(); 123 newOjb = formatter.Deserialize(memory); 124 memory.Close(); 125 return newOjb; 126 } 127 public byte[] SerializeObject(object pObj)//序列化对象为二进制的数 128 { 129 if (pObj == null) 130 return null; 131 MemoryStream memory = new MemoryStream(); 132 BinaryFormatter formatter = new BinaryFormatter(); 133 formatter.Serialize(memory, pObj); 134 memory.Position = 0; 135 byte[] read = new byte[memory.Length]; 136 memory.Read(read, 0, read.Length); 137 memory.Close(); 138 return read; 139 } 140 141 private void btnSend_Click(object sender, EventArgs e) 142 { 143 Communication comm = new Communication() { 144 Name = Dns.GetHostName(), 145 Message = txtMessage.Text, 146 Types = 1 147 }; 148 if (lvInfo.SelectedItems.Count > 0)//私聊 149 { 150 comm.Types = 3; 151 string ePoint = lvInfo.SelectedItems[0].SubItems[1].Text +":"+ lvInfo.SelectedItems[0].SubItems[2].Text; 152 comm.ToEndPoint = ePoint; 153 } 154 byte[] b = SerializeObject(comm); 155 client.Send(b); 156 lvInfo.SelectedItems.Clear();//清除选中的个数 157 } 158 159 private void 退出程序ToolStripMenuItem_Click(object sender, EventArgs e) 160 { 161 Communication comm = new Communication() 162 { 163 Name = Dns.GetHostName(), 164 Message = "下线了!", 165 Types = 4//用户下线类型 166 }; 167 byte[] b = SerializeObject(comm); 168 client.Send(b); 169 client.Dispose(); 170 Application.Exit(); 171 } 172 173 private void forClient_Load(object sender, EventArgs e) 174 { 175 //127.0.0.1 用于获取本机IP 176 txtIP.Text = "192.168.1.100"; 177 txtPort.Text = "8080"; 178 } 179 } 180 }