最近在熟悉socket通讯,写一个简单的聊天实例,希望对以后的初学者有帮助.....
服务器端的界面
public partial class ClientSeverForm : Form
{
//private System.ComponentModel.Container components = null;
//Clients数组保存当前在线用户的Client对象
internal static Hashtable clients = new Hashtable();
//该服务器默认的监听端口号
private TcpListener listener;
// 服务器可以支持的客户端的最大连接数
static int MAX_NUM = 100;
//服务器开始服务的标志
internal static bool SocketServiceFlag = false;
//构造函数
public ClientSeverForm()
{
//windows窗口设计器支持所必须的
InitializeComponent();
//在 InitializeComponent();调用后添加任何构造函数代码
}
#region 获取地址
获取本地局域网IP地址
private string getIPAddress()
{
IPAddress[] AddressList = Dns.GetHostByName(Dns.GetHostName()).AddressList;// Dns.GetHostByName(Dns.GetHostName()).AddressList;
if (AddressList.Length < 1)
{
return "";
}
return AddressList[0].ToString();
}
#endregion
#region 获取服务器端口号,并判断端口号是否合法
//获取服务器的端口号,判读端口号是否合法
private int getValidPort(string port)
{
int lport;
//测试端口号是否有效
try
{
//是否为空
if (port == "")
{
Console.WriteLine("端口号为空,不启动服务器");
}
//将端口号转为int类型
lport = System.Convert.ToInt32(port);
}
catch (Exception e)
{
Console.WriteLine("无效的端口号:"+e.ToString());
this.rtbSocketMsg.AppendText("无效的端口号:"+e.ToString());
return -1;
}
return lport;
}
#endregion
#region 点击"启动Socket"按钮触发的事件,开始监听
//启动"Socket",开始监听
private void btnSocketStart_Click(object sender, EventArgs e)
{
//参数为端口号的值
int port = getValidPort(tbSocketPort.Text);
if (port < 0)
{
return;
}
string ip = this.getIPAddress();
try
{
IPAddress ipAdd = IPAddress.Parse(ip);
//创建服务器套接字
listener = new TcpListener(ipAdd,port);
//开始监听服务器端口
listener.Start();
this.rtbSocketMsg.AppendText("Socketr服务器已经启动,正在监听" + ip + "端口号:" + this.tbSocketPort.Text + "\n");
//启动一个新的线程,执行方法this.StratSockListen
//以便在一个独立的进程中执行确认与客户端Socket连接的操作
ClientSeverForm.SocketServiceFlag = true;
Thread thread = new Thread(new ThreadStart(this.StartSocketListen));
thread.Start();
this.btnSocketStart.Enabled = false;
this.btnStop.Enabled = true;
}
catch (Exception ex)
{
this.rtbSocketMsg.AppendText(ex.Message.ToString() + "\n");
}
}
#endregion
#region 当有用户添加进来,将其添加到用户列表,更新在线人数
public void addUser(string username)
{
//将刚链接的用户名加入到当前在线用户列表
SetControlText(rtbSocketMsg, username + "已经加入\n");
//this.rtbSocketMsg.AppendText(username + "已经加入\n");
//跟新在线人数加1
SetControlList(lbSocketClients, username);
// this.lbSocketClients.Items.Add(username);
//显示在线人数
SetControlText(tbSocketClientsNum,clients.Count.ToString());
//this.tbSocketClientsNum.Text = System.Convert.ToString(clients.Count);
}
#endregion
#region 启动新的进程来处理和该客户端的信息交互
//在新的线程中的操作,它主要用于当接收到一个客户端请求时,确认与客户端的链接
//并且立刻启动一个新的进程来处理和该客户端得信息交互
private void StartSocketListen()
{
while (ClientSeverForm.SocketServiceFlag)
{
try
{
//当接收到一个客户端请求时,确认与客户端的链接
if (listener.Pending())
{
//接收挂起的连接请求
Socket socket = listener.AcceptSocket();
if (clients.Count >= MAX_NUM)
{
this.rtbSocketMsg.AppendText("已经达到了最大连接数:" + MAX_NUM + ",拒绝接收新的链接");
socket.Close();
}
else
{
//启动一个新的线程
//执行方法this.ServiceClient,处理用户相应的请求
Client client = new Client(this, socket);
Thread clientService = new Thread(new ThreadStart(client.ServiceClient));
clientService.Start();
}
}
//是线程休息,提高系统性能
Thread.Sleep(200);
}
catch (Exception ex)
{
this.rtbSocketMsg.AppendText(ex.Message.ToString() + "\n");
}
}
}
#endregion
#region 输入端口号的文本框发生改变时
private void tbSocketPort_TextChanged(object sender, EventArgs e)
{
//当端口号为空时,启动按钮亏掉不可用
this.btnSocketStart.Enabled = (this.tbSocketPort.Text != "");
}
#endregion
#region 点击“停止”按钮,触发的事件
private void btnStop_Click(object sender, EventArgs e)
{
ClientSeverForm.SocketServiceFlag = false;
this.btnSocketStart.Enabled = true;
this.btnStop.Enabled = false;
}
#endregion
#region 当有用户离开,将其在列表中删除,在线人数跟新减少一个
public void removeUser(string username)
{
SetControlText(rtbSocketMsg, username);
// this.rtbSocketMsg.AppendText(username + "已经离开");
//将其用户名移除用户表
SetExit(lbSocketClients,username);
//this.lbSocketClients.Items.Remove(username);
//跟新在线人数
//this.tbSocketClientsNum.Text =System.Convert.ToString(clients.Count);
SetControlText(tbSocketClientsNum,clients.Count.ToString());
}
#endregion
#region 用于在获取当前全部在线用户的列表,备用户名中间用“|”分割,该函数在LIST命令时调用
public string GetUserList()
{
string Rtn = "";
for (int i = 0; i < lbSocketClients.Items.Count; i++)
{
Rtn = Rtn + lbSocketClients.Items[i].ToString() + "|";
}
return Rtn;
}
#endregion
#region 用于更新界面,添加系统或者聊天信息,参数是要显示的字符串
public void updateUI(string msg)
{
SetControlText(rtbSocketMsg,msg+"\n");
//this.rtbSocketMsg.AppendText(msg + "\n");
}
#endregion
#region 当关闭窗口,会执行窗口的closing事件
private void btn_Close_Click(object sender, System.ComponentModel.CancelEventArgs e)
{
ClientSeverForm.SocketServiceFlag = false;
}
#endregion
#region 定义一个委托
private delegate void SetControlTextDelegate(Control control, string txt);
//判断当前控件是否在此线程中,当是此线程时,调用该线程
//如果不是同一线程,让其回调
private void SetControlText(Control control, string txt)
{
if (control.InvokeRequired)
{
var d = new SetControlTextDelegate(SetControlText);
BeginInvoke(d, new object[] { control, txt });
}
else
{
control.Text = txt;
}
}
private delegate void SetControlListDelegate(ListBox listbox, string txt);
//判断当前控件是否在此线程中,当是此线程时,调用该线程
//如果不是同一线程,让其回调
private void SetControlList(ListBox listbox, string item)
{
if (listbox.InvokeRequired)
{
var d = new SetControlListDelegate(SetControlList);
BeginInvoke(d, new object[] { listbox, item });
}
else
{
listbox.Items.Add(item);
}
}
private delegate void SetExitDelegate(ListBox listbox, string item);
//判断当前控件是否在此线程中,当是此线程时,调用该线程
//如果不是同一线程,让其回调
private void SetExit(ListBox listbox, string item)
{
if (listbox.InvokeRequired)
{
var d = new SetExitDelegate(SetExit);
BeginInvoke(d, new object[] { listbox, item });
}
else
{
listbox.Items.Remove(item);
}
}
#endregion
}
-----------------------------------------------------------------------------------------------------------------------------------------------------
______________________________________________________________________________________________--
Client类——一个中间类的逻辑处理
public class Client
{
private string name;
private Socket currentSocket ;//保存与当前用户连接的Socket对象
private string ipAddress;
private ClientSeverForm server;
public ClientSeverForm Server
{
get { return server; }
set { server = value; }
}
//保存当前链接的状态:closed
private string state="closed";
public Client(ClientSeverForm server, Socket clientSocket)
{
this.server = server;
this.currentSocket = clientSocket;
ipAddress = getRemoteIPAddress();
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public Socket CurrentSocket
{
get
{
return currentSocket;
}
set
{
currentSocket = value;
}
}
public string IpAddress
{
get
{
return ipAddress;
}
}
//获取ip地址
private string getRemoteIPAddress()
{
return((IPEndPoint)currentSocket.RemoteEndPoint).Address.ToString();
}
//服务器处理聊天命令时,需要向客户端发送命令或者发送响应信息,这些命令的发送都是通过SendToClient()函数实现的
//SendToClient()方法实现了向客户端发送命令请求的功能
private void SendToClient(Client client, string msg)
{
System.Byte[] message = System.Text.Encoding.Default.GetBytes(msg.ToCharArray());
client.CurrentSocket.Send(message, message.Length, 0);
}
/// <summary>
/// Client类中提供了一个ServiceClient()函数,用于与客户端进行通信,接受客户端请求
/// 根据不同命令执行不同操作,将结果返回给客户端
/// </summary>
public void ServiceClient()
{
string[] tokens = null;
byte[] buff = new byte[1024];
bool keepConnect = true;//退出循环,关闭连接
//循环的与客户端交互,直到客户端发送exit
//讲keepConnect跟新为false ,推出循环,关闭连
while (keepConnect && ClientSeverForm.SocketServiceFlag)
{
try
{
if (this.CurrentSocket == null || this.CurrentSocket.Available < 1)
{
Thread.Sleep(300);
continue;
}
//接收数据并保存入buff数组中
int len = this.CurrentSocket.Receive(buff);
//讲字符数组转为字符串,
string clientCommand = System.Text.Encoding.Default.GetString(buff, 0, len);
//tokens[0]中保存了命令标志符(CONN,CHAT,PRIV,LIST,EXIT)
tokens = clientCommand.Split(new Char[] { '|' });
if (tokens == null)
{
Thread.Sleep(200);
continue;
}
}
catch (Exception e)
{
Server.updateUI("发生异常");
}
//以上代码是用于服务器初始化和接收客户端发来的请求,对数据解析后,把命令转为数组形式
//下面讲服务器根据不同的命令进行相应的处理
if (tokens[0] == "CONN")
{
//此时接收到的命令格式为
//命令标识符(CONN)发送者的用户名
//tokens[1]中保存发送者的用户名
this.Name = tokens[1];
if (ClientSeverForm.clients.Contains(this.name))
{
SendToClient(this,"ERR|User"+this.name+"已经存在");
}
else
{
Hashtable synclients = Hashtable.Synchronized(ClientSeverForm.clients);
synclients.Add(this.name, this);
// 更新界面
server.addUser(this.name);
//对每一个当前在线的用户发送JOIN消息命令和LIST息命令
//以此来更新客户端的当前在线用户列表
System.Collections.IEnumerator myEnumerator = ClientSeverForm.clients.Values.GetEnumerator();
while (myEnumerator.MoveNext())
{
Client client = (Client)myEnumerator.Current;
SendToClient(client, "JOIN|" + tokens[1] + "|");
Thread.Sleep(100);
}
//更新状态
state = "connected";
SendToClient(this, "ok");
//向客户端发t送list命令,以此跟新客户端的当前在线用户列表
string msgUsers = "LIST|" + server.GetUserList();
SendToClient(this, msgUsers);
}
}
else if (tokens[0] == "LIST")
{
if (state == "connected")
{
//向客户端发送list命令,以此跟新用户列表
string msgUsers = "LIST|" + server.GetUserList();
SendToClient(this, msgUsers);
}
else
{
SendToClient(this, "ERR|state error,Please login first");
}
}
else if (tokens[0] == "CHAT")
{
if (state == "connected")
{
//此时接收到的命令的格式为:
//命令标识符,发送者的用户名:发送内容|向所有当前在线的用户转发此消息
System.Collections.IEnumerator myEnumerator = ClientSeverForm.clients.Values.GetEnumerator();//ChatClientForm.clients.Values.GetEnumerator();
while (myEnumerator.MoveNext())
{
Client client = (Client)myEnumerator.Current;
//讲“发送者的用户名:发送内容“转发给用户””
SendToClient(client, tokens[1]);
}
server.updateUI(tokens[1]);
}
else
{
SendToClient(this, "ERR|state error,Please login first");
}
}
//如果是PRIV命令,就把信息转发给PRIV命令中指定的接受者和发送者
else if (tokens[0] == "PRIV")
{
if (state == "connected")
{
//此时接收到的命令格式为
//命令标识符(PRIV)发送呢者用户名(接受者用户名|发送内容|tokens[1]中保存了发送者的用户名)ro
string sender = tokens[1];
//tokens[2] 中保存了接受者 的名字
string receiver = tokens[2];
//tokens[3]保存了发送的内容
string content = tokens[3];
string message = sender + "--->" + receiver + ":" + content;
//仅将信息转发给发送者和接受者
if (ClientSeverForm.clients.Contains(sender))
{
SendToClient((Client)ClientSeverForm.clients[sender], message);
}
if (ClientSeverForm.clients.Contains(receiver))
{
SendToClient((Client)ClientSeverForm.clients[receiver], message);
}
server.updateUI(message);
}
else
{
SendToClient(this, "ERR|state error,Please login first");
}
}
else if (tokens[0] == "EXIT")
{
//此时接收到的命令的格式:命令标石符|发送者的名字|
//向所有当前用户发送已经离线的信息
if (ClientSeverForm.clients.Contains(tokens[1]))
{
Client client = (Client)ClientSeverForm.clients[tokens[1]];
//将该用户对应的Client对象从clients中删除
Hashtable syncClients = Hashtable.Synchronized(ClientSeverForm.clients);
syncClients.Remove(client.Name);
server.removeUser(client.Name);
//向客户端发送QUIT命令
string message = "QUIT|" + tokens[1];
System.Collections.IEnumerator myEnumberator = ClientSeverForm.clients.Values.GetEnumerator();
while (myEnumberator.MoveNext())
{
Client c = (Client)myEnumberator.Current;
SendToClient(c, message);
}
server.updateUI("QUIT");
}
break;
}
Thread.Sleep(200);
}
}
}
_______________________________________________________________________
——————————————————————————————————————————————————————————
客户端的界面
public partial class ChatClientForm : Form
{
public ChatClientForm()
{
InitializeComponent();
}
//与服务器的连接
TcpClient tcpClient;
//与服务器数据交互的流通道
private NetworkStream Stream;
//客户端的状态
private static string CLOSED = "closed";
private static string CONNECTED = "connected";
//定义当前客户的状态
private string state = CLOSED;
private bool stopFlag;
private Color color;
#region 点击“登陆”
private void btnLogin_Click(object sender,System.EventArgs e)
{
if(state==CONNECTED)
{
return;
}
if(this.tbUserName.Text.Length==0)
{
MessageBox.Show("请输入您的昵称","提示信息");
this.tbUserName.Focus();
return;
}
try
{
// Client clients = new Client();
//创建一个客户端套接字,他是logion的一个套接字,将被传递给ChatClient窗体
tcpClient=new TcpClient();
//向指定的IP地址服务器发送连接请求
tcpClient.Connect(IPAddress.Parse(txtHost.Text),Int32.Parse(txtport.Text));
//获得与服务器数据交互的流通道
Stream=tcpClient.GetStream();
//启动一个新的线程,执行方法this.ServerResponse(),
//以便来响应从服务器发回的信息
Thread thread=new Thread(new ThreadStart(this.ServeerResponse));
thread.Start();
//向服务器发送"CONN"请求命令,此命令的格式与服务器的定义的格式一致
//命令格式为:命令标示符CONN| 发送者的名字
string cmd ="CONN|"+this.tbUserName.Text+"|";
//将字符串转化为字符数组
Byte[] outbytes=System.Text.Encoding.Default.GetBytes(cmd.ToCharArray());
Stream.Write(outbytes,0,outbytes.Length);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
#endregion
#region 点击“发送”按钮
private void btnSend_Click(object sender, System.EventArgs e)
{
try
{
if (!this.cbPrivate.Checked)
{
//此时命令的格式是:
//命令标示符(CHAT)|发送者的名字|发送内容
string message = "CHAT|" + this.tbUserName.Text + tbSendContent.Text + "|";
tbSendContent.Text = "";
tbSendContent.Focus();
//将字符串转为字符数组
Byte[] outbytes = System.Text.Encoding.Default.GetBytes(message.ToCharArray());
Stream.Write(outbytes, 0, outbytes.Length);
}
else
{
if (lstUsers.SelectedIndex == -1)
{
MessageBox.Show("请在一个列表中选择一个用户","提示信息");
return;
}
string receiver = lstUsers.SelectedItem.ToString();
//命令标示符(PRIV|)|发送者的名字|接收者的用户名|发送内容
string message = "PRIV|" + this.tbUserName.Text + "|" + receiver + "|" + tbSendContent.Text + "|";
tbSendContent.Text="";
tbSendContent.Focus();
//将字符串转化为字符数组
byte[] outbytes = System.Text.Encoding.ASCII.GetBytes(message.ToCharArray());
Stream.Write(outbytes,0,outbytes.Length);
}
}
catch(Exception ex)
{
this.rtbMsg.AppendText("网络发生错误");
}
}
#endregion
#region 用于接收服务器发回的信息
//根据不同的命令,执行相应的操作
private void ServeerResponse()
{
//定义一个 byte数组,用于接收从服务器发回的信息
//每次所能接收的数据包的最大长度为1024个字符
byte[] buff=new byte[1024];
string msg="";
int len;
try
{
if (!Stream.CanRead)
{
return;
}
stopFlag = false;
while (!stopFlag)
{
//从流中得到数据,并存入到buff字符数组中
len = Stream.Read(buff, 0, buff.Length);
if (len < 1)
{
Thread.Sleep(200);
continue;
}
//将字符数组转化为字符串
msg = System.Text.Encoding.Default.GetString(buff, 0, len);
msg.Trim();
string[] tokens = msg.Split(new Char[]{'|'});
//tokens = msg.Split(new Char[] { '|' });//takens[0]中保存了命令标识符(LIST或JOIN或QUIT)
if (tokens[0].ToUpper() == "OK")
{
//处理响应
add("命令执行成功");
}
else if (tokens[0].ToUpper() == "ERR")
{
//命令执行错误
add("命令执行错误:" + tokens[1]);
}
else if (tokens[0] == "LIST")
{
//此时从服务器返回的消息格式:
//命令标志符(LIST|用户名|用户名2|用户名3...
//跟新在线用户列表
lstUsers.Items.Clear();
for (int i = 1; i < tokens.Length - 1; i++)
{
lstUsers.Items.Add(tokens[i].Trim());
}
}
else if (tokens[0] == "JOIN")
{
//此时从服务器返回的消息格式
add(tokens[1] + "" + "已经进入了聊天室");
SetControlList(lstUsers,tokens[1]);
// this.lstUsers.Items.Add(tokens[1]);
if (this.tbUserName.Text == tokens[1])
{
this.state = CONNECTED;
}
}
else if (tokens[0] == "QUIT")
{
if (this.lstUsers.Items.IndexOf(tokens[1]) > -1)
{
//this.lstUsers.Items.Remove(tokens[1]);
SetControlList(lstUsers,tokens[1]);
}
add("用户:" + tokens[1] + "已经离开");
}
else
{
//如果从服务器返回的其他消息格式
//则在listBox控件中直接现实
add(msg);
}
}
//关闭连接
tcpClient.Close();
}
catch (Exception ex)
{
add("网络发生错误");
}
}
#endregion
#region 点击“离开”按钮
private void btnExit_Click(object sender, System.EventArgs e)
{
if (state == CONNECTED)
{
string message = "EXIT|"+this.tbUserName.Text+"|";
//将字符串转化为字符数组
Byte[] outbytes = System.Text.Encoding.Default.GetBytes(message.ToCharArray());
Stream.Write(outbytes,0,outbytes.Length);
this.state = CLOSED;
this.stopFlag = true;
this.lstUsers.Items.Clear();
}
}
#endregion
#region 点击"关闭"
private void btnClose_Click(object sender, System.ComponentModel.CancelEventArgs e)
{
btnExit_Click(sender, e);
}
#endregion
#region 获取颜色的方法
private void btnColor_Click(object sender, EventArgs e)
{
ColorDialog colorDialog1 = new ColorDialog();
colorDialog1.Color = this.rtbMsg.SelectionColor;
if (colorDialog1.ShowDialog() == DialogResult.OK && colorDialog1.Color != this.rtbMsg.SelectionColor)
{
this.rtbMsg.SelectionColor = colorDialog1.Color;
color = colorDialog1.Color;
}
}
#endregion
#region 添加信息
private void add(string msg)
{
if(!color.IsEmpty)
{
SetControlText(rtbMsg,color.ToString());
// this.rtbMsg.SelectionColor = color;
}
SetControlText(rtbMsg,msg+"\n");
//this.rtbMsg.SelectedText = msg + "\n";
}
#endregion
#region 定义一个委托
private delegate void SetControlTextDelegate(Control control, string txt);
//判断当前控件是否在此线程中,当是此线程时,调用该线程
//如果不是同一线程,让其回调
private void SetControlText(Control control, string txt)
{
if (control.InvokeRequired)
{
var d = new SetControlTextDelegate(SetControlText);
BeginInvoke(d, new object[] { control, txt });
}
else
{
control.Text = txt;
}
}
private delegate void SetControlListDelegate(ListBox listbox, string txt);
//判断当前控件是否在此线程中,当是此线程时,调用该线程
//如果不是同一线程,让其回调
private void SetControlList(ListBox listbox, string item)
{
if (listbox.InvokeRequired)
{
var d = new SetControlListDelegate(SetControlList);
BeginInvoke(d, new object[] { listbox, item });
}
else
{
listbox.Items.Add(item);
}
}
#endregion
}
本文链接地址为:http://blog.youkuaiyun.com/evonne520/article/details/6678511