使用界面的效果
服务端界面
客户端登录界面
登录成功界面
登录第二个服务端
最终效果
首先了解socket客户端和socket服务端
socket客户端:
1.创建Socket对象,绑定IP,构建链接
2.发送send
3.接收数据Receive,注意,需要持续性接收,加While,异步操作Task,取消CancellationTokenSource
4.注意,有链接就应该有断开
socket服务端:
1.创建socket对象,绑定IP,启用监听
2.监听对象的获取使用Accept方法
3.推送消息的方法跟客户端相同都是Send
搭建聊天室的思路
1.如何区分昵称是否重复(登录的账号需要排重)
2.只要是消息都会被服务器接收到,如何区分消息类型?(登录,消息,退出)
3.如何在列表中选择对应的好友进行发送
直接开始敲代码
(作者太懒了,wimform界面自己看着效果图去搭建)
搭建服务端
搭建服务端之前先创建一个User类用来存储登录的用户列表,当然也可以使用别的方式(数据库等)来进行存储
internal class User
{
public string NickName { get; set; } // 昵称
public Socket Client { get; set; }// 客户端Socket
}
开始搭建服务端
public partial class Form1 : Form
{
private Socket socketServer = null; // 伺服器端Socket
private CancellationTokenSource cts = null; // 取消Token
private List<User> clients = new List<User>(); // 用来存储登录的用户列表
public Form1()
{
InitializeComponent();
}
#region 启动和关闭服务器
private void button1_Click(object sender, EventArgs e)
{
try
{
if (button1.Text == "启动服务器")
{
StartServer();
}
else if (button1.Text == "关闭服务器")
{
CloseServer();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
}
/// <summary>
/// 启动服务器
/// </summary>
private void StartServer()
{
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 客户端是链接,服务器是监听
socketServer.Bind(new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text)));
socketServer.Listen(100);
button1.Text = "关闭服务器";
textBox1.Enabled = false;
textBox2.Enabled = false;
//等待客户端连接
cts = new CancellationTokenSource();
Task.Run(async () =>
{
while (!cts.IsCancellationRequested)
{
//async与await等价于new Action
//Socket属于套字节,在服务器中相当于一个监听器(TCPListener),在客户端相当于一个连接器
Socket socketClient = await socketServer.AcceptAsync();
//处理客户端请求
ProcessClient(socketClient);
}
}, cts.Token);
}
/// <summary>
/// 关闭服务器
/// </summary>
private void CloseServer()
{
if (socketServer != null)
{
if (clients.Count > 0)
{
//发通知
BroadCastMessage("通知:服务器关闭,请退出聊天室");
}
cts.Cancel();
socketServer.Close();
socketServer = null;
button1.Text = "启动服务器";
textBox1.Enabled = true;
textBox2.Enabled = true;
}
}
#endregion
#region 处理客户端请求(a:登录 b:消息)
/// <summary>
/// 处理客户端请求(a:登录 b:消息)
/// </summary>
/// <param name="socketClient"></param>
private void ProcessClient(Socket socketClient)
{
Task.Run(() =>
{
while (socketClient.Connected)
{
byte[] buffer = new byte[socketClient.Available];
int len = socketClient.Receive(buffer);
if (len > 0)
{
//a|||内容
//a
//内容
string[] messages = Encoding.UTF8.GetString(buffer).Split(new string[] { "|||" }, StringSplitOptions.RemoveEmptyEntries);
if (messages[0] == "a")
{
//登录>>>判断用户是否存在
if (clients.FindIndex((u) => u.NickName == messages[1]) != -1)
{
//昵称重复
socketClient.Send(Encoding.UTF8.GetBytes("昵称重复"));
}
else
{
//昵称不重复.将用户添加到列表
clients.Add(new User()
{
NickName = messages[1],
Client = socketClient
});
//整理客户端需要的消息列表
UserOnLine(clients);
}
}
else if (messages[0] == "c")
{
//有用户下线了,发通知
int index = clients.FindIndex((u) => u.NickName == messages[1]);
if (index != -1)
{
clients.RemoveAt(index);
BroadCastMessage("通知:\"" + messages[1] + "\"下线了");
}
//发通知刷新列表
UserOnLine(clients);
}
else if (messages[0] == "b")
{
//需要找两个对象
//1.找到发送者
Socket socket1 = clients.Find((u) => u.NickName == messages[1]).Client;
//2.找到接收者
Socket socket2 = clients.Find((u) => u.NickName == messages[2]).Client;
//确保接收者在线
if (socket2 != null && socket2.Connected)
{
//对于客户端来说,客户端.Send就代表向自己的客户端上推一条消息
//接收者收到消息
socket2.Send(Encoding.UTF8.GetBytes($"{messages[1]}:{messages[3]}"));
//发送者收到消息
socket1.Send(Encoding.UTF8.GetBytes($"{messages[1]}:{messages[3]}"));
}
}
else if (messages[0] == "d")
{
// 群发消息
Socket socket1 = clients.Find((u) => u.NickName == messages[1]).Client;
BroadCastMessage(messages[1] + "(群发):" + messages[2]);
}
}
}
});
}
#endregion
#region 消息列表
private void UserOnLine(List<User> clients)
{
string str = string.Empty;
for (int i = 0; i < clients.Count; i++)
{
//把所有的在线用户昵称拼接成一个字符串,发送给客户端
str += clients[i].NickName + "||||";
}
BroadCastMessage(str);
}
/// <summary>
/// 广播消息的方法
/// </summary>
/// <param name="message"></param>
private void BroadCastMessage(string message)
{
//有多少个客户就发送多少条广播
for (int i = 0; i < clients.Count; i++)
{
Socket sockClirnt = clients[i].Client;
if (sockClirnt.Connected)
{
sockClirnt.Send(Encoding.UTF8.GetBytes(message));
}
}
}
#endregion
#region 点击发送按钮会给全部客户端发送通知
private void button2_Click(object sender, EventArgs e)
{
if (socketServer == null)
{
MessageBox.Show("服务器未启动", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
if (clients.Count == 0)
{
MessageBox.Show("没有在线用户", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
if (string.IsNullOrWhiteSpace(richTextBox1.Text))
{
MessageBox.Show("消息不能为空", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
BroadCastMessage("通知:" + richTextBox1.Text);
richTextBox1.Text = string.Empty;
}
#endregion
/// <summary>
/// 退出程序时关闭服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
CloseServer();
}
}
服务端搭建完成,开始搭建客户端
(客户端需要两个Form界面,作者这里的Form2界面是登录界面,Form1界面是登录成功后的聊天室界面)
在搭建好Form2和Form1界面之后,需要对应用程序的主入口点的代码进行修改,也就是Program.cs中的代码
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//主要是用于跳转界面
//当点击登录并成功登录后,会跳转到聊天界面
Form2 form2 = new Form2();
if (form2.ShowDialog() == DialogResult.OK)
{
Application.Run(new Form1());
}
}
开始操作登录界面的代码
public partial class Form2 : Form
{
//一个对象只能链接一次 但是一个程序 .可以使用exe启动多次
//使用静态是为了在Form1中更简便的使用
public static Socket socketClient = null; // 客户端Socket
public static string NickName { get; set; } // 昵称
// 用来存储登录的用户列表
public static string[] OnLineUsers { get; set; }
public Form2()
{
InitializeComponent();
// 为静态的客户端对象初始化
socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
private async void button1_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(textBox1.Text))
{
MessageBox.Show("昵称不可为空");
return;
}
//连接服务器
try
{
socketClient.Connect(new IPEndPoint(IPAddress.Parse(textBox2.Text), int.Parse(textBox3.Text)));
//发送消息,告诉服务器,有客户端进来了,方便服务器处理
//a:用来表示登录
socketClient.Send(Encoding.UTF8.GetBytes($"a|||{textBox1.Text}"));
//接收消息
string msg=await ReceiveMessage();
if(msg=="昵称重复了")
{
MessageBox.Show("昵称重复了,请重新输入","错误提示",MessageBoxButtons.OK,MessageBoxIcon.Stop);
textBox1.Clear();
textBox1.Focus();
return;
}
else
{
//跳转聊天>>>获取到在线用户有哪些
string[] users = msg.Split(new string[] { "||||" }, StringSplitOptions.RemoveEmptyEntries);
//排除自身
OnLineUsers = users.Where(u => u != textBox1.Text).ToArray();
}
}
catch (SocketException ex)
{
MessageBox.Show($"网络错误:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
catch (Exception ex)
{
MessageBox.Show($"发生错误:{ex.Message}","错误提示",MessageBoxButtons.OK,MessageBoxIcon.Stop);
return;
}
//如果连接成功,那么就关闭当前窗口,跳转到聊天窗口
NickName = textBox1.Text;
DialogResult = DialogResult.OK;
}
#region 接受消息处理昵称重复的问题
private Task<string> ReceiveMessage()
{
return Task.Run(() =>
{
while (true)
{
//只要构建链接成功,那么socketClient不关闭的情况下始终都会来链接,所以一旦服务端有消息回复,这边会收到.return出来结束掉while
byte[] buffer = new byte[socketClient.Available];
int len = socketClient.Receive(buffer);
if (len > 0)
{
return Encoding.UTF8.GetString(buffer);
}
}
});
}
#endregion
}
开始对聊天室界面的代码进行编写
public partial class Form1 : Form
{
CancellationTokenSource cts = new CancellationTokenSource(); // 取消Token
public Form1()
{
InitializeComponent();
this.Text = Form2.NickName;
if (Form2.OnLineUsers != null && Form2.OnLineUsers.Length > 0)
{
this.listBox1.DataSource = Form2.OnLineUsers;
}
else
{
this.listBox1.DataSource = new string[] { "暂无在线用户" };
}
//this.listBox1.DataSource = Form2.OnLineUsers;
//等待其他用户发送消息
Task.Run(() =>
{
while (!cts.IsCancellationRequested)
{
byte[] buffer = new byte[Form2.socketClient.Available];
int len = Form2.socketClient.Receive(buffer);
if (len > 0)
{
string msg = Encoding.UTF8.GetString(buffer);
//处理该消息是不是广播的在线用户
if (msg.Contains("||||"))
{
string selectedUser = null;
if (listBox1.SelectedItem != null)
{
//获取当前选中的用户
selectedUser = listBox1.SelectedItem.ToString();
}
//重新绑定列表
string[] users = msg.Split(new string[] { "||||" }, StringSplitOptions.RemoveEmptyEntries);
listBox1.DataSource = null;
listBox1.DataSource = users.Where(u => u != Form2.NickName).ToArray();
if (selectedUser != null)
{
listBox1.SelectedItem = selectedUser;
}
}
else
{
//展示消息
Invoke(new Action(() =>
{
if (msg.StartsWith(Form2.NickName))
{
//自己的内容在右侧
richTextBox1.SelectionAlignment = HorizontalAlignment.Right;
richTextBox1.SelectionRightIndent = 2;
richTextBox1.SelectionBackColor = Color.FromArgb(0, 153, 255);
richTextBox1.SelectionColor = Color.White;
richTextBox1.AppendText(msg + "\r\n\r\n");
}
else
{
//其他用户
richTextBox1.SelectionAlignment = HorizontalAlignment.Left;
richTextBox1.SelectionRightIndent = 2;
if (msg.StartsWith("通知"))
{
richTextBox1.SelectionColor = Color.White;
richTextBox1.SelectionColor = Color.Red;
listBox1.DataSource = null;
}
else
{
richTextBox1.SelectionBackColor = Color.White;
richTextBox1.SelectionColor = Color.Black;
}
richTextBox1.AppendText(msg + "\r\n\r\n");
}
}));
}
}
}
},cts.Token);
}
/// <summary>
/// 关闭窗口时,关闭socketClient
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (Form2.socketClient.Connected)
{
//发送消息,告诉服务器,有客户端退出了
Form2.socketClient.Send(Encoding.UTF8.GetBytes($"c|||{Form2.NickName}"));
cts.Cancel();//取消任务
Form2.socketClient.Disconnect(false);
Form2.socketClient.Close();
Form2.socketClient = null;
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
if(checkBox1.Checked)
{
//如果是广播
if (string.IsNullOrWhiteSpace(richTextBox2.Text))
{
MessageBox.Show("发送内容不能为空", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
if (Form2.socketClient.Connected)
{
//聊天:自己,内容
Form2.socketClient.Send(Encoding.UTF8.GetBytes($"d|||{Form2.NickName}|||{richTextBox2.Text}"));
}
}
else
{
//如果是单聊
if (listBox1.SelectedItem == null)
{
MessageBox.Show("请选择发送用户", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
if (string.IsNullOrWhiteSpace(richTextBox2.Text))
{
MessageBox.Show("发送内容不能为空", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
if (Form2.socketClient.Connected)
{
//聊天:自己,朋友,内容
Form2.socketClient.Send(Encoding.UTF8.GetBytes($"b|||{Form2.NickName}|||{listBox1.SelectedItem}|||{richTextBox2.Text}"));
}
}
richTextBox2.Text = string.Empty;
}
}
以上是简易聊天室的全部代码,作者只是提供一个思路,如有不足之处请各位大佬批评指正!!!
--HH牛码