c# UDP/TCP协议简单实现(简单聊天工具)

时间:2023-03-08 16:04:25

长时间没有摸这两个协议,写个代码温习下

下面是界面

【服务器界面】

c# UDP/TCP协议简单实现(简单聊天工具)

【登陆界面】

c# UDP/TCP协议简单实现(简单聊天工具)

【好友列表界面(我登陆了2个)】

c# UDP/TCP协议简单实现(简单聊天工具)

【聊天界面】

c# UDP/TCP协议简单实现(简单聊天工具)

下面大致讲解下用到的内容

1、用户登陆于服务器通信用到的tcp协议,服务器接收到用户登陆信息(包括ip,端口,用户名等)后,返回已经登陆的用户列表信息(包括ip,端口,用户名等)给这个用户,同时服务器使用Udp协议向已经登陆的用户发送最新用户列表(包括ip,端口,用户名等)用于更新用户列表

2、用户登陆成功后展示好友列表,并启动udp协议的监听(叫监听似乎不太合适,暂且这么叫吧  形象),用以接收好友发来的消息和服务器返回的好友信息(1中提到的发送用户列表信息)

3、关于聊天有被动接收到消息和主动发送消息

先说主动发送消息吧:双击列表的某个好友打开聊天窗口,然后发送内容,通过udp协议向好友发送信息

被动接收消息:当2中提到的udp监听器接收到消息,则打开聊天窗口,并显示信息

4、用户退出时想服务器发送数据退出,用到的tcp协议,服务器接到到信息,更新在线用户列表并向其他用户发送用户最新列表进行更新(用到udp协议)

口才不行,写的有点乱

下面上代码解释下

先来服务器代码,服务器我使用了控制台程序

 using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.IO; namespace QQServer
{
class Program
{
public static List<string> userlist = new List<string>();
static TcpListener tl;
static NetworkStream ns;
static void Main(string[] args)
{
//声明监听对象 //声明网络流 //IPEndPoint ip=new IPEndPoint(
tl = new TcpListener();
tl.Start();
Console.WriteLine("TcpListener Star");
//开启线程
Thread th = new Thread(new ThreadStart(listen));
th.IsBackground = true;
th.Start();
while (true)
{
string index = Console.ReadLine();
if (index == "exit")
break; } }
private static void listen()
{
Console.WriteLine("TcpListenering...");
while (true)
{
//获得响应的Socket
Socket sock = tl.AcceptSocket();
//通过该Socket实例化网络流
ns = new NetworkStream(sock);
//ClientTcp是添加的类,下面会做说明
ClientTcp ct = new ClientTcp(ns);
//ct_MyEvent方法注册ClientTcp类的MyEvent事件
ct.MyEvent += new MyDelegate(ct_MyEvent);
//开启线程
Thread th = new Thread(new ThreadStart(ct.TcpThread));
th.IsBackground = true;
th.Start();
}
} static void ct_MyEvent(string temp)
{
if (temp.StartsWith("login:"))
{
temp = temp.Replace("login:", "");
Console.WriteLine("UserLogin:" + temp);
string[] index = temp.Split(';');
if (!ContainsList(index[]))
{
userlist.Add(temp);
}
SendUsersToUser(index[]);
}
else if (temp.StartsWith("out:"))
{
temp = temp.Replace("out:", "");
Console.WriteLine("UserLoginOut:" + temp);
if (ContainsList(temp))
{
RemoveList(temp);
}
SendUsersToUser(temp);
}
} static void SendUsersToUser(string outuser)
{
string message = GetUserStr();
UdpClient uc;
foreach (string s in userlist)
{
string[] _userstrindex=s.Split(';');
if (_userstrindex[] == outuser)
continue;
string _ipsindex = _userstrindex[];
string[] _ipindex = _ipsindex.Split(':');
byte[] b = System.Text.Encoding.UTF8.GetBytes("users" + message);
//向本机的8888端口发送数据
uc = new UdpClient();
uc.Send(b, b.Length, _ipindex[], int.Parse(_ipindex[]));
}
} static string GetUserStr()
{
StringBuilder sb = new StringBuilder();
foreach (string s in userlist)
{
if (sb.Length > )
sb.Append("#");
sb.Append(s);
}
return sb.ToString();
} static bool ContainsList(string str)
{
foreach (string s in userlist)
{
if (s.Split(';')[] == str)
{
return true;
}
}
return false;
} static void RemoveList(string str)
{
for (int i = userlist.Count - ; i >= ; i--)
{
string s = userlist[i];
if (s.Split(';')[] == str)
{
userlist.Remove(s);
}
}
}
} public delegate void MyDelegate(string temp);
class ClientTcp
{
//设置网络流局部对象
private NetworkStream ns;
//声明类型为MyDelegate的事件MyEvent
public event MyDelegate MyEvent;
//构造函数中接收参数以初始化
public ClientTcp(NetworkStream ns)
{
this.ns = ns;
}
//服务器端线程所调用的方法
public void TcpThread()
{
//获得相关的封装流
StreamReader sr = new StreamReader(ns);
string temp = sr.ReadLine();
//接收到客户端消息后触发事件将消息回传
if (!temp.StartsWith("getuser"))
{
MyEvent(temp);
}
StringBuilder sb = new StringBuilder();
foreach (string s in Program.userlist)
{
if (sb.Length > )
sb.Append("#");
sb.Append(s);
}
StreamWriter sw = new StreamWriter(ns);
//转换为大写后发送消息给客户端
sw.WriteLine(sb.ToString());
sw.Flush();
sw.Close();
sr.Close();
}
}
}

需要注意的地方:

tl = new TcpListener(12345);这个地方使用了固定端口12345,所有客户端跟服务器进行通信必须使用这个端口

Thread th = new Thread(new ThreadStart(ct.TcpThread));
th.IsBackground = true;
th.Start();

这个地方为什么使用一个线程呢???

当接收到一个信息后需要进行处理,如果同时有好多信息进来的话容易堵塞,所有用线程,并且接收到一个信息马上将信息放到 ClientTcp ct = new ClientTcp(ns);这里,然后慢慢进行处理吧

服务器接收到的消息有多种,怎么区分呢???

有登陆的信息,有退出的信息,有获取列表的信息,我们可以在发送的消息内用一些字段进行标记,例如在头部加上“getuser”等等的

=======================================================

下面是客户端的

登陆

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.IO;
using System.Net; namespace QQClient
{
public partial class Login : Form
{
private TcpClient tc;
//声明网络流
private NetworkStream ns;
public Login()
{
InitializeComponent();
} private void button1_Click(object sender, EventArgs e)
{
string username = textBox1.Text;
string ipstr = textBox2.Text;
string poitstr = textBox3.Text; IPHostEntry ipe = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipa = null;
foreach (IPAddress ip in ipe.AddressList)
{
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
continue;
ipa = ip;
break;
} StringBuilder sb = new StringBuilder();
sb.Append("login:");
sb.Append(username + ";");
sb.Append(ipa.ToString() + ":");
Random r = new Random();
int port = r.Next(, );
sb.Append(port.ToString()); try
{
tc = new TcpClient(ipstr, int.Parse(poitstr));
}
catch
{
MessageBox.Show("无法连接到主机");
}
//实例化网络流对象
ns = tc.GetStream();
StreamWriter sw = new StreamWriter(ns);
StreamReader sr = new StreamReader(ns);
//将TextBox1的值传给服务器端
sw.WriteLine(sb.ToString());
sw.Flush();
//接收服务器端回传的字符串
string users = sr.ReadLine(); sr.Close();
sw.Close(); Main main=new Main();
main.Username=username;
main.Users=users;
main.Port = port;
main.ThisIP = ipa.ToString();
main.ServerIP = textBox2.Text;
main.ServerPort = textBox3.Text;
this.Hide();
main.ShowDialog();
} private void button2_Click(object sender, EventArgs e)
{
Application.Exit();
}
}
}

列表界面

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Net; namespace QQClient
{
public partial class Main : Form
{
public string Username { get; set; }
public string Users { get; set; }
public int Port { get; set; }
public string ServerIP;
public string ServerPort;
public string ThisIP { get; set; }
public static List<Talking> TalkList = new List<Talking>();
public List<User> userList = new List<User>();
public Main()
{
InitializeComponent();
} private void Main_Load(object sender, EventArgs e)
{
//Control.CheckForIllegalCrossThreadCalls = false;
this.Text = Username;
LoadUser();
StartListen();
} private void LoadUser()
{
if (string.IsNullOrEmpty(Users))
return;
this.listView1.Items.Clear();
userList.Clear();
string[] _userindex = Users.Split('#');
foreach (string s in _userindex)
{
string[] _index = s.Split(';');
string _username = _index[];
//string[] _ipinex = _index[1].Split(':');
//string ip = _ipinex[0];
//string port = _ipinex[1];
if (_username != Username)
{
//TreeNode tn = new TreeNode();
//tn.Text = _username;
//tn.Tag = _index[1];
//this.treeView1.Nodes.Add(tn); ListViewItem lvitem = new ListViewItem(); lvitem.ImageIndex = ;
lvitem.Text = _username;
lvitem.Tag = _index[];
this.listView1.Items.Add(lvitem);
userList.Add(new User() { UserName = _username, Ips = _index[] });
}
}
} private void button2_Click(object sender, EventArgs e)
{
Application.Exit();
} private void button1_Click(object sender, EventArgs e)
{
try
{
TcpClient tc = new TcpClient(ServerIP, int.Parse(ServerPort));
//实例化网络流对象
NetworkStream ns = tc.GetStream();
StreamWriter sw = new StreamWriter(ns);
StreamReader sr = new StreamReader(ns);
//将TextBox1的值传给服务器端
sw.WriteLine("getuser");
sw.Flush();
//接收服务器端回传的字符串
Users = sr.ReadLine();
sr.Close();
sw.Close();
LoadUser();
}
catch
{ }
} private void Main_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
TcpClient tc = new TcpClient(ServerIP, int.Parse(ServerPort));
//实例化网络流对象
NetworkStream ns = tc.GetStream();
StreamWriter sw = new StreamWriter(ns);
//将TextBox1的值传给服务器端
sw.WriteLine("out:" + Username);
sw.Flush();
sw.Close();
iswork = false;
}
catch
{ }
Application.Exit();
} private void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (this.listView1.SelectedItems.Count > )
{
ListViewItem lvitem = this.listView1.SelectedItems[];
string toname = lvitem.Text;
string toips = lvitem.Tag.ToString();
Talking t = isHaveTalk(toname);
if (t != null)
{
t.Focus();
}
else
{
Talking talk = new Talking();
talk.UserName = Username;
talk.ToName = toname;
talk.ToIP = toips;
TalkList.Add(talk);
talk.Show();
}
}
} private Talking isHaveTalk(string toname)
{
foreach (Talking tk in TalkList)
{
if (tk.ToName == toname)
return tk;
}
return null;
} public static void RemoveTalking(Talking _talk)
{
foreach (Talking tk in TalkList)
{
if (tk.ToName == _talk.ToName)
{
TalkList.Remove(_talk);
return;
}
}
} bool iswork = false;
UdpClient uc = null;
private void StartListen()
{ iswork = true;
Thread th = new Thread(new ThreadStart(listen));
//设置为后台
th.IsBackground = true;
th.Start();
}
private void listen()
{
uc = new UdpClient(Port);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, );
while (iswork)
{
//获得Form1发送过来的数据包
string text = System.Text.Encoding.UTF8.GetString(uc.Receive(ref iep));
if (text.StartsWith("message"))
{
text = text.Substring();
int indexof = text.IndexOf("#");
string fromuser = text.Substring(, indexof);
string message = text.Substring(indexof + );
Talking _tk = isHaveTalk(fromuser);
if (_tk != null)
{
this.BeginInvoke(new MethodInvoker(delegate()
{
_tk.Focus();
_tk.AddMessage(message, true);
}));
}
else
{
//Talking talk = new Talking(message);
//talk.UserName = Username;
//talk.ToName = fromuser;
//talk.ToIP = GetIP(fromuser);
//TalkList.Add(talk);
//talk.Show();
this.BeginInvoke(new MethodInvoker(delegate()
{
this.CreatTalking(text);
}));
//Thread th = new Thread(new ParameterizedThreadStart(CreatTalking));
//th.IsBackground = true;
//th.Start(text);
}
//加入ListBox
//this.listBox1.Items.Add(text);
}
else if (text.StartsWith("users"))
{
text = text.Substring();
Users = text;
LoadUser();
}
}
} public void CreatTalking(object _text)
{
string text = _text.ToString();
int indexof = text.IndexOf("#");
string fromuser = text.Substring(, indexof);
string message = text.Substring(indexof + );
Talking talk = new Talking(message);
talk.UserName = Username;
talk.ToName = fromuser;
talk.ToIP = GetIP(fromuser);
TalkList.Add(talk);
talk.Show();
} private string GetIP(string toname)
{
foreach (User user in userList)
{
if (user.UserName == toname)
return user.Ips;
}
return "";
}
}
public class User
{
private string userName; public string UserName
{
get { return userName; }
set { userName = value; }
}
private string ips; public string Ips
{
get { return ips; }
set { ips = value; }
}
}
}

聊天界面

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Threading; namespace QQClient
{
public partial class Talking : Form
{
public string UserName { get; set; }
public string ToName { get; set; }
public string ToIP { get; set; } UdpClient uc;
public Talking()
{
InitializeComponent();
} string getmessage = string.Empty;
public Talking(string message)
{
getmessage = message;
InitializeComponent();
} private void Talking_Load(object sender, EventArgs e)
{
uc = new UdpClient();
this.Text = "和" + ToName + "聊天中";
if (!string.IsNullOrEmpty(getmessage))
{
ShowTalking();
AddMessage(getmessage, true);
}
} private void button1_Click(object sender, EventArgs e)
{
string temp = this.textBox1.Text; //保存TextBox文本
//将该文本转化为字节数组
byte[] b = System.Text.Encoding.UTF8.GetBytes("message" + UserName + "#" + temp);
//向本机的8888端口发送数据
string[] _ip = ToIP.Split(':');
uc.Send(b, b.Length, _ip[], int.Parse(_ip[]));
AddMessage(temp, false);
this.textBox1.Clear();
}
public void AddMessage(string str, bool isuser)
{
int startindex = this.richTextBox1.Text.Length; string message = string.Empty; if (isuser)
message = "【" + ToName + "】 " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\n" + str + "\n";
else
message = "【" + UserName + "】 " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\n" + str + "\n";
this.richTextBox1.AppendText(message);
this.richTextBox1.Select(startindex, message.Length);
if (isuser)
{
this.richTextBox1.SelectionAlignment = HorizontalAlignment.Left;
}
else
{
this.richTextBox1.SelectionAlignment = HorizontalAlignment.Right;
}
this.richTextBox1.Select(this.richTextBox1.Text.Length, );
} [System.Runtime.InteropServices.DllImport("user32")]
private static extern long FlashWindow(IntPtr hwnd, bool bInvert); private static void FlashWindow(object _handle)
{
IntPtr handle = (IntPtr)_handle;
int flashindex = ;
while (true)
{
if (flashindex > )
break;
FlashWindow(handle, true);
flashindex++;
Thread.Sleep();
}
} public void ShowTalking()
{
Thread _thread = new Thread(FlashWindow);
_thread.IsBackground = true;
_thread.Start(this.Handle);
} private void Talking_FormClosed(object sender, FormClosedEventArgs e)
{
Main.RemoveTalking(this);
} private void button2_Click(object sender, EventArgs e)
{
this.Close();
}
}
}

大致总结下:

tcp必须建立连接才可以进行通信

udp不需要建立通信

但是两者都需要一个监听来接收消息