2014-9-4 复习Socket
1、套接字(也就是Socket)
用于描述IP地址和端口,是一个通信链的句柄。
(其实就是两个程序通信用的)
2、有两种类型
流式Socket(Stream):
是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低。
数据报式Socket(DataGram):
是一种无连接的Socket,对应于无连接的UDP服务应用,不安全(丢失,顺序混乱,在接收端要分析重排及要求重发),但效率高。
3、Socket一般应用模式(服务器端和客户端)
4、聊天程序
1.服务端
namespace 聊天程序_服务端
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//定义一个更新UI的委托
private delegate void DGUpdateUI(string msg);
//实例一个更新UI的委托
private DGUpdateUI DgUpdateUI;
private DGUpdateUI DgShowMsg;
//定义一个字典用来储存用户连接时创建的连接套接字
private Dictionary<string, Socket> dictClient = new Dictionary<string, Socket>();
//定义一个字典用来储存用户连接时创建监听线程
private Dictionary<string, Thread> dictClientThread = new Dictionary<string, Thread>();
/// <summary>
/// 开启服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStartServer_Click(object sender, RoutedEventArgs e)
{
//把更新UI的方法赋值给更新UI委托
DgUpdateUI = UpadteUI;
DgShowMsg = ShowMsg;
//创建一个监听套接字
Socket socketWatch = SocketHelper.CreateWatchSocket(txtIP.Text.Trim(), int.Parse(txtPort.Text.Trim()),10);
//创建一个监听线程
Thread threadWatch = new Thread(delegate()
{
Watch(socketWatch);
});
//设置为后台线程
threadWatch.IsBackground = true;
//开启监听线程
threadWatch.Start();
//开启成功
ShowMsg("开启服务成功!");
txtIP.IsEnabled = false;
txtPort.IsEnabled = false;
btnStartServer.IsEnabled = false;
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSendMsg_Click(object sender, RoutedEventArgs e)
{
//拿到key值
string key = lbOnlineClient.SelectedValue.ToString();
//拿到发送的信息
string sendMsg = txtSendMsg.Text.Trim();
//转成字节数组
byte[] arrMsg = Encoding.UTF8.GetBytes(sendMsg);
byte[] arrSendMsg = new byte[arrMsg.Length + 1];
arrSendMsg[0] = 0;
Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length);
//群发
if (cbGroupSend.IsChecked == true)
{
foreach (Socket s in dictClient.Values)
{
s.Send(arrSendMsg);
}
ShowMsg("你 对 所有人 说:" + sendMsg);
}
//单个发
else
{
dictClient[key].Send(arrSendMsg);
ShowMsg("你 对 " + key + " 说:" + sendMsg);
}
}
/// <summary>
/// 选择文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnChooseFile_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog() == true)
{
txtFile.Text = ofd.FileName;
}
}
/// <summary>
/// 发送文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSendFile_Click(object sender, RoutedEventArgs e)
{
//拿到key值
string key = lbOnlineClient.SelectedValue.ToString();
//拿到发送的文悠扬
string sendFilePath = txtFile.Text;
using (FileStream fs = new FileStream(sendFilePath, FileMode.Open))
{
//创建一个缓冲区
byte[] arrMsg = new byte[1024*1024*2];
//把流写入缓冲区
int arrLen=fs.Read(arrMsg, 0, arrMsg.Length);
//再创建一个发送文件(标识)缓冲区
byte[] arrSendMsg = new byte[arrLen + 1];
arrSendMsg[0] = 1;//代表发送的是文件
Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrLen);
//群发
if (cbGroupSend.IsChecked == true)
{
foreach (Socket s in dictClient.Values)
{
s.Send(arrSendMsg);
}
ShowMsg("你 对 所有人 发送了:" + sendFilePath);
}
//单个发
else
{
dictClient[key].Send(arrSendMsg);
ShowMsg("你 对 " + key + " 发送了:" + sendFilePath);
}
}
}
//方法
//1.监听方法
void Watch(Socket socketWatch)
{
while (true)
{
Socket socketClient = socketWatch.Accept();
//把用户连接时创建的socket放到dictClient中
dictClient.Add(socketClient.RemoteEndPoint.ToString(), socketClient);
//为每个用户新建一个接收线程,用来监听用户发来的消息
Thread threadClient = new Thread(delegate()
{
Receive(socketClient);
});
//把通信线程以socketClient.RemoteEndPoint 为key,保存起来
dictClientThread.Add(socketClient.RemoteEndPoint.ToString(), threadClient);
threadClient.IsBackground = true;
threadClient.Start();
this.Dispatcher.Invoke(DgUpdateUI, socketClient.RemoteEndPoint.ToString());
}
}
//2.更新UI方法
void UpadteUI(string msg)
{
ShowMsg(msg +"连接成功!");
lbOnlineClient.Items.Add(msg);
}
//3.显示消息
void ShowMsg(string msg)
{
txtMsg.AppendText(msg + "\r\n");
}
//4.接收消息
void Receive(Socket socketClient)
{
while (true)
{
//创建一个2M的字节数组,用来存放接收到数据
byte[] arrRecMsg = new byte[1024*1024*2];
//开启接收 并把实际接收到的数据长度存在arrLen中
int arrLen = -1;
try
{
arrLen = socketClient.Receive(arrRecMsg);
}
catch (SocketException ex)
{
this.Dispatcher.Invoke(DgShowMsg, "异常:" + ex.Message);
//删了断开的通信套接字
dictClient.Remove(socketClient.RemoteEndPoint.ToString());
dictClientThread.Remove(socketClient.RemoteEndPoint.ToString());
this.Dispatcher.Invoke(delegate()
{
RemoveListBox(socketClient.RemoteEndPoint.ToString());
});
break;
}
catch (Exception ex)
{
this.Dispatcher.Invoke(DgShowMsg, "异常:" + ex.Message);
break;
}
//0 代表发的是消息 1 代表是文件
if (arrRecMsg[0] == 0)
{
//把字节数组转成字符串 byte[]--string
string strRecMsg = Encoding.UTF8.GetString(arrRecMsg, 1, arrLen-1);
//显示消息
this.Dispatcher.Invoke(DgShowMsg, socketClient.RemoteEndPoint.ToString() + " 对 你 说:" + strRecMsg);
}
// 1 代表是文件
else if (arrRecMsg[0] == 1)
{
SaveFileDialog sfd = new SaveFileDialog();
if (sfd.ShowDialog() == true)
{
string saveFilePath = sfd.FileName;
using (FileStream fs = new FileStream(saveFilePath, FileMode.Create))
{
fs.Write(arrRecMsg, 1, arrLen - 1);
if (fs.Length == arrLen - 1)
{
this.Dispatcher.Invoke(DgShowMsg, "文件保存成功!" + saveFilePath);
}
else
{
this.Dispatcher.Invoke(DgShowMsg, "文件保存失败!");
}
}
}
}
}
}
//5.删除ListBox
void RemoveListBox(string key)
{
lbOnlineClient.Items.Remove(key);
}
}
class SocketHelper
{
/// <summary>
/// 创建一个连接套接字
/// </summary>
/// <param name="ip">IP地址</param>
/// <param name="port">端口</param>
/// <param name="listenLen">监听队列长度</param>
/// <returns></returns>
public static Socket CreateWatchSocket(string ip, int port,int listenLen)
{
//创建一个监听套接字
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//创建一个IP地址
IPAddress address = IPAddress.Parse(ip);
//创建一个包含IP和端口的网络节点
IPEndPoint endPoint = new IPEndPoint(address, port);
//绑定网络节点
socketWatch.Bind(endPoint);
//设置监听队列长度
socketWatch.Listen(listenLen);
//返回监听套接字
return socketWatch;
}
}
}
2.客户端
namespace Client
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//定义一个更新UI的委托
private delegate void DGUpdateUI(string msg);
//实例一个更新UI的委托
private DGUpdateUI DgUpdateUI;
//连接套接字
private Socket socketConn=null;
/// <summary>
/// 连接服务
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStartServer_Click(object sender, RoutedEventArgs e)
{
DgUpdateUI = UpadteUI;
//创建一个连接套接字
socketConn=SocketHelper.CreateConnSocket(txtIP.Text.Trim(), int.Parse(txtPort.Text.Trim()));
//创建一个监听Receive线程
Thread threadReceive = new Thread(Receive);
//设置为后台
threadReceive.IsBackground = true;
//开启线程
threadReceive.Start();
}
/// <summary>
/// 发送信息给服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSendMsg_Click(object sender, RoutedEventArgs e)
{
string strSendMsg = txtSendMsg.Text.Trim();
byte[] arrMsg = Encoding.UTF8.GetBytes(strSendMsg);
byte[] arrSendMsg = new byte[arrMsg.Length + 1];
arrSendMsg[0] = 0;
Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length);
socketConn.Send(arrSendMsg);
ShowMsg("你对 服务器 说" + strSendMsg);
}
/// <summary>
/// 选择文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnChooseFile_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog() == true)
{
txtFilePath.Text = ofd.FileName;
}
}
/// <summary>
/// 发送文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSendFile_Click(object sender, RoutedEventArgs e)
{
//拿到要保存的文件路径
string filePath = txtFilePath.Text;
//创建文件流
using (FileStream fs = new FileStream(filePath,FileMode.Open))
{
//创建一个字节数组 2M
byte[] arrFile = new byte[1024*1024*2];
//从流中写入byte的缓冲区中 返回实际写入的长度
int arrLen=fs.Read(arrFile, 0, arrFile.Length);
//创建一个标识数组
byte[] arrSendFile = new byte[arrLen + 1];
arrSendFile[0] = 1;//1代表发的是文件 0代表发的是信息
//复制数据
Buffer.BlockCopy(arrFile, 0, arrSendFile, 1, arrLen);
int arrSendLen=socketConn.Send(arrSendFile);
if (arrSendLen == arrLen+1)
{
ShowMsg("发送文件成功");
}
else
{
ShowMsg("发送文件出错!");
}
}
}
//方法
//1.接收消息(文件)方法
void Receive()
{
while (true)
{
//创建一个2M的字节数组,用来存放接收到数据
byte[] arrRecMsg = new byte[1024 * 1024 * 2];
//开启接收 并把实际接收到的数据长度存在arrLen中
int arrLen = socketConn.Receive(arrRecMsg);
//0 是消息 1是文件
if (arrRecMsg[0] == 0)
{
//把字节数组转成字符串 byte[]--string
string strRecMsg = Encoding.UTF8.GetString(arrRecMsg, 1, arrLen-1);
//显示消息
this.Dispatcher.Invoke(DgUpdateUI, strRecMsg);
}
else if (arrRecMsg[0] == 1)
{
SaveFileDialog sfd = new SaveFileDialog();
if (sfd.ShowDialog() == true)
{
string saveFileName = sfd.FileName;
using (FileStream fs = new FileStream(saveFileName, FileMode.Create))
{
fs.Write(arrRecMsg, 1, arrLen - 1);
if (fs.Length == arrLen - 1)
{
//显示消息
this.Dispatcher.Invoke(DgUpdateUI, "文件保存成功!");
}
else
{
this.Dispatcher.Invoke(DgUpdateUI, "文件保存失败!");
}
}
}
}
}
}
//2.更新UI方法
void UpadteUI(string msg)
{
ShowMsg(msg);
}
//3.显示消息
void ShowMsg(string msg)
{
txtMsg.AppendText(msg + "\r\n");
}
}
/// <summary>
/// 创建一个socketHelper类
/// </summary>
internal class SocketHelper
{
public static Socket CreateConnSocket(string ip, int port)
{
//创建一个连接套接字
Socket socketConn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//创建一个IP地址
IPAddress address = IPAddress.Parse(ip);
//创建一个包含IP和端口的网络节点
IPEndPoint endPoint = new IPEndPoint(address, port);
//连接网络节点
socketConn.Connect(endPoint);
//返回监听套接字
return socketConn;
}
}
}