最近工作不怎麼忙,學習了一下複習了一下Socket相關的知識,並寫了兩個簡單的Demo,一個客戶端,一個服務端,代碼如下:
服務端:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SocketChatServer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//定義一個鍵值對集合,將客戶端的IP地址和Socket存入集合中
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
private void BtnStartListing_Click(object sender, EventArgs e)
{
//創建負責監聽的Socket
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//獲取要監聽的IP地址,把IP字符串轉爲IP
IPAddress ip = IPAddress.Parse(txtIP.Text);
//獲取要監聽的IP和端口,合併爲一個變量(創建要監聽的IP和端口)
IPEndPoint point = new IPEndPoint(ip,Convert.ToInt32(txtPort.Text));
//綁定要監聽的ip和端口號
socketWatch.Bind(point);
//給出監聽提示
ShowMsg("監聽成功");
//開始監聽。設置服務器在一個時間點內最多能夠監聽的隊列數量。
socketWatch.Listen(10);
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start(socketWatch);
}
void ShowMsg(string str) {
//如果不是創建這個控件的線程來調用它
if (txtLog.InvokeRequired)
{
if (txtLog.InvokeRequired) {
//跨線程訪問
txtLog.Invoke(new Action<string>(s =>
{
txtLog.AppendText(s + "\r\n");
}), str);
}
}
else {
txtLog.AppendText(str+"\r\n");
}
}
/// <summary>
/// 等待客戶端的連接,同時創建一個與之通信的socket
/// </summary>
Socket socketCommunication;
void Listen(object obj) {
//下面開始等待客戶端來連接,同時會創建一個負責通訊的socket
Socket socketWatch = obj as Socket;
while (true)
{
//創建一個通訊連接
socketCommunication = socketWatch.Accept();
//把上面創建好的負責通訊的socket存入集合中
dicSocket.Add(socketCommunication.RemoteEndPoint.ToString(),socketCommunication);
//這裏cboUssers屬於跨線程訪問
if (cboUsers.InvokeRequired)
{
//跨線程訪問
cboUsers.Invoke(new Action(() =>
{
cboUsers.Items.Add(socketCommunication.RemoteEndPoint.ToString());
}), null);
}
else {
//將遠程連接的客戶端的IP地址和端口號存儲到下拉列表框中
cboUsers.Items.Add(socketCommunication.RemoteEndPoint.ToString());
}
//給出連接成功提示信息。
ShowMsg(socketCommunication.RemoteEndPoint.ToString() + ":連接成功");
//開啓一個新的線程不停的接收客戶端發來的消息
Thread thread = new Thread(Receive);
thread.IsBackground = true;//最好設置爲後臺線程,爲什麼?
thread.Start(socketCommunication);
}
}
/// <summary>
/// 服務端接收客戶端發來的信息
/// </summary>
/// <param name="obj"></param>
void Receive(object obj) {
Socket socketCommunication = obj as Socket;
//定義一個2M大小的字節數組
byte[] buffer = new byte[1024*1024*2];
while (true)
{
try
{
//Receive方法的參數表示接收到的數據存儲的位置,需要的是一個字節數組,返回值爲int
int size = socketCommunication.Receive(buffer);
if (size == 0)
{
//關閉並退出Socket
socketCommunication.Shutdown(SocketShutdown.Both);
socketCommunication.Close();
return;
}
//string str1 = Encoding.Default.GetString(buffer, 0, r);
//通過Encodeing從buffer字節數組中的0個位置開始,把r個字節轉成字符串;使用Default編寫也可以
string str = Encoding.UTF8.GetString(buffer, 0, size);
//把接收到的數據放到文本框中
ShowMsg(socketCommunication.RemoteEndPoint.ToString() + ":" + str);
}
catch (Exception)
{
}
}
}
/// <summary>
/// 服務端向客戶端發送信息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e)
{
//獲取要發送的文本
string str = txtMsg.Text;
//把輸入的字符串消息的轉爲字節數組
byte[] buffer = Encoding.UTF8.GetBytes(str);
byte[] newBuffer = new byte[buffer.Length + 1];
newBuffer[0] = 0;
//BlockCopy的用法是:將buffer字節從第0個下標往後複製buffer.Length個長度追加到newBuffer字節第1個下標2後面
Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
//send方法需要的就是字節數組
//socketCommunication.Send(buffer);
string ip = cboUsers.SelectedItem.ToString();
dicSocket[ip].Send(newBuffer);
}
private void btnGroupSend_Click(object sender, EventArgs e)
{
//獲取要發送的文本
string str = txtMsg.Text;
//把輸入的字符串消息的轉爲字節數組
byte[] buffer = Encoding.UTF8.GetBytes(str);
byte[] newBuffer = new byte[buffer.Length + 1];
newBuffer[0] = 0;
//BlockCopy的用法是:將buffer字節從第0個下標往後複製buffer.Length個長度追加到newBuffer字節第1個下標2後面
Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
for (int i = 0; i < cboUsers.Items.Count; i++)
{
dicSocket[cboUsers.Items[i].ToString()].Send(newBuffer);
}
}
/// <summary>
/// 選擇要發送的文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSelect_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.InitialDirectory = @"D:\";
ofd.Title = "請選擇要發送的文件";
ofd.Filter = "所有文件|*.*";
ofd.ShowDialog();
txtPath.Text = ofd.FileName;
}
private void btnSendFile_Click(object sender, EventArgs e)
{
//#region 第一種實現方式,使用文件流讀寫
////獲取要發送文件的路徑
//string path = txtPath.Text;
//using (FileStream fsRead=new FileStream(path,FileMode.Open,FileAccess.Read)) {
// byte[] buffer = new byte[1024 * 1024 * 5];
// int size = fsRead.Read(buffer,0,buffer.Length);
// List<byte> list = new List<byte>();
// list.Add(1);
// list.AddRange(buffer);
// byte[] newBuffer = list.ToArray();
// //第四個參數一般設置爲none,除非外網與內網通訊有特殊的要求
// dicSocket[cboUsers.SelectedItem.ToString()].Send(newBuffer,0,size+1,SocketFlags.None);
//}
//#endregion
#region 第二種實現方式,使用File.ReadAllBytes讀取
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() != DialogResult.OK)
{
//如果沒有選擇文件
return;
}
else
{
byte[] buffer = File.ReadAllBytes(ofd.FileName);
byte[] newBuffer = new byte[buffer.Length + 1];
newBuffer[0] = 1;
Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
//這裏第二個參數SocketFlags.None可以不需要,因爲這裏不存在多發送內容,讀取的是文件中的所有字節數
dicSocket[cboUsers.SelectedItem.ToString()].Send(newBuffer, SocketFlags.None);
}
}
#endregion
}
private void btnZD_Click(object sender, EventArgs e)
{
byte[] buffer = new byte[1];
buffer[0] = 2;
dicSocket[cboUsers.SelectedItem.ToString()].Send(buffer);
}
}
}
運行效果:
客戶端:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SocketChatClient
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//取消跨線程之間的檢查
Control.CheckForIllegalCrossThreadCalls = false;
}
Socket socketCommunication;
private void btnStart_Click(object sender, EventArgs e)
{
//創建一個通訊的Socket
socketCommunication = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//獲得要連接的遠程服務器的IP和端口號
IPAddress ip = IPAddress.Parse(txtServer.Text);
IPEndPoint point = new IPEndPoint(ip,Convert.ToInt32(txtPort.Text));
//調用Connection方法去連接遠程服務器
socketCommunication.Connect(point);
//提示連接成功
ShowMsg("連接成功");
//開啓一個新的線程不斷的去接收服務端發來的消息
Thread thread = new Thread(Receive);
//設置爲後臺線程
thread.IsBackground = true;
thread.Start();
}
void ShowMsg(string str)
{
//如果不是創建這個控件的線程來調用它
if (txtLog.InvokeRequired)
{
if (txtLog.InvokeRequired)
{
//跨線程訪問
txtLog.Invoke(new Action<string>(s =>
{
txtLog.AppendText(s + "\r\n");
}), str);
}
}
else
{
txtLog.AppendText(str + "\r\n");
}
}
/// <summary>
/// 客戶端向服務端發送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e)
{
//獲取輸入的信息
string str = txtMsg.Text.Trim();
//把輸入的消息轉爲字節數組,所用的編碼要和服務端保持一致
byte[] buffer = Encoding.UTF8.GetBytes(str);
//向服務端發送消息
socketCommunication.Send(buffer);
}
void Receive() {
try
{
//因爲要不停的去接收,所以寫一個while循環裏
byte[] buffer = new byte[1024 * 1024 * 3];
while (true)
{
//不管是發送還是接收消息,都要拿到負責通訊的socket
//size表示實際接收到的有效字節數
int size = socketCommunication.Receive(buffer);
//爲防止服務器端關掉,客戶端還在不斷的接收消息
int type = buffer[0];
if (size == 0)
{
break;
}
if (type == 0)
{
//接收文本
string str = Encoding.UTF8.GetString(buffer, 1, size - 1);
//在文本框中顯示接收到的消息
ShowMsg(socketCommunication.RemoteEndPoint + ":" + str);
}
else if (type == 1)
{
//接收文件
//#region 第一種實現思路,使用文件流讀寫
//SaveFileDialog sfd = new SaveFileDialog();
//sfd.InitialDirectory = @"F:\123";
//sfd.Title = "請選擇要保存的文件位置";
//sfd.Filter = "所有文件|*.*|";
//sfd.ShowDialog(this);
//string path = sfd.FileName;
//using (FileStream fsWrite=new FileStream(path,FileMode.OpenOrCreate,FileAccess.Write)) {
// //把接收到的數據從第一個字節位置,size-1個字節的數據寫入到流指向的文件中
// fsWrite.Write(buffer,1,size-1);
//}
//MessageBox.Show("保存成功");
//#endregion
#region 第二種實現思路,不用使用文件流寫入使用File.WriteAllBytes
using (SaveFileDialog sfd = new SaveFileDialog())
{
sfd.DefaultExt = "doc";
sfd.Filter = "所有文件|*.*|文本文件(*.txt)|*.txt|word文檔(*.doc)|*.doc";
if (sfd.ShowDialog(this) != DialogResult.OK)
{
return;
}
else
{
byte[] newBuffer = new byte[size - 1];
Buffer.BlockCopy(buffer, 1, newBuffer, 0, size - 1);
File.WriteAllBytes(sfd.FileName, newBuffer);
MessageBox.Show("保存成功");
}
}
#endregion
}
else if (type == 2) {
//接收震動
ZD();
}
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
/// <summary>
/// 震動,改變窗體作爲位置
/// </summary>
void ZD()
{
for (int i = 0; i < 600; i++)
{
this.Location = new Point(200, 200);
this.Location = new Point(260, 260);
}
}
}
}
運行效果:
想學習Socket知識的小夥伴們可以根據這個簡單的Demo學習一下,幾乎每行代碼都有註釋,簡單明瞭。如果哪有不明白的或不正確的地方可以私信我。