C#基礎整理(八)
1.程序複習
//通過程序去開啟應用程式
Process.Start(“calc”);
Process.Start(“mspaint”);
Process.Start(“notepad”);
Process.Start(“iexplore”, “http://www.baidu.com”);
//通過程序去開啟指定的檔案
ProcessStartInfo psi = new ProcessStartInfo(@“C:\Users\SJD\Desktop\AE.txt”);
Process p = new Process();
p.StartInfo = psi;
p.Start();
//程序和執行緒的關係?一個程序包含多個執行緒
單執行緒容易照成程式假死
//前臺 後臺
Thread
Thread th=new Thread(Test);建立執行緒
th.IsBcakground=true; 標記它為後臺執行緒
start()啟動執行緒(告訴CPU 我已經準備好了可以被執行,但是具體執行時間,由CPU決定)
abort()終止執行緒 (終止完成之後不能再用start())
thread.sleep(1)靜態方法 可以讓當前執行緒停止一段時間執行
//執行緒中如何訪問控制元件
程式不允許跨執行緒訪問,會拋異常。
想要不拋異常,在程式載入的時候,取消跨執行緒的檢查
雖然還是不允許這麼做,但不讓它去檢查,它就不知道這個事
control.checkforillegalcrossthreadcalls=false;
//執行緒裡的方法傳參
如果執行緒執行的方法需要引數,那麼要求這個引數必須是object型別。
並且引數是傳到呼叫的start()裡面
2.socket
電腦,程式之間的電話機 收發資料
協議 兩個不同地方的人用普通話通訊
電腦和電腦之間通訊預設的語言
常用協議 UDP和TCP
socket 孔,插座 作為程序通訊機制,也稱為“套接字” 用來描述IP地址和埠
是一個通訊鏈的控制代碼(其實就是兩個程式通訊用的)
IP是連線伺服器,埠號是連線想連線的應用程式
在服務端必須要有一個負責監聽的socket,看有沒有一個客服端連線到我們的伺服器
負責監聽的socket 建立一個負責通訊的socket
3.tcp和udp協議
客服端要連線伺服器,首先要傳送一個請求過去
要知道伺服器的ip地址,知道伺服器的應用程式的埠號
就可以準確的連線到伺服器的應用程式
TCP比UDP安全穩定,一般不會發生資料丟失
TCP 3次握手 伺服器(必須有) 請求(一定是客服端發給伺服器)
請求 客戶端—>伺服器 你有空嗎?
伺服器—>客戶端 我有空
客戶端—>服務端 我知道你有空了
三次握手 三次握手成功了,客服端才和伺服器相互的收發資料,否則不會進行資料的溝通
安全,穩定,效率相對低一些
UDP 快速 效率高,但不穩定,容易放生資料丟失
客服端給伺服器發訊息,不管伺服器有空沒空就發,訊息全發過去,具體伺服器有沒有精力接受這些訊息,它不管,反正發過去了。
視訊傳輸用UDP(視訊聊天)聊天不清晰但流暢,不要畫面清晰卻一卡一卡的
3.建立和客戶端通訊的socket
private void btnStart_Click(object sender, EventArgs e)
{
//當點選開始監聽的時候 在伺服器端建立一個負責監IP地址跟埠號的Socket
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Any;//IPAddress.Parse(txtServer.Text);
//建立埠號物件
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
//監聽
socketWatch.Bind(point);
ShowMsg(“監聽成功”);
**設定監聽佇列
設定伺服器的最大監聽量
//一個時間點10個人,來11個人就1個人排隊
socketWatch.Listen(10);
Thread th = new Thread(Listen);
th.IsBackground = true;
th.Start(socketWatch);
}
///
/// 等待客戶端的連線 並且建立與之通訊的socket
///
void Listen(object o)
{
Socket socketWatch = o as Socket;
//等待客戶端的連線 並且建立一個負責通訊的socket
while (true)
{
Socket socketSend = socketWatch.Accept();
//192.168.1.50:連線成功
ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + “連線成功”);
}
}
void ShowMsg(string str)
{
txtLog.AppendText(str + “\r\n”);
}
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegal
CrossThreadCalls = false;
}
4.設計協議
實現傳送檔案
如何判斷接收資料是檔案還是文字
設計“協議”:
把要傳遞的位元組陣列前面都加上一個位元組做為標示。0:標示文字。1:標示檔案。
即:文字:0+文字(位元組陣列標示)
檔案:1+檔案的二進位制資訊
將泛型集合轉換為陣列
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
List list = new List();
list.Add(0);
list.AddRange(buffer);
byte[] newBuffer= list.ToArray();
5.建立服務端和客戶端通訊的流程筆記
客戶端 服務端
Socket() Socket()
Bind() 繫結監聽埠
Listen() 設定監聽佇列
while(true)
{
Connect() 連線建立 Accept() 迴圈等待客戶端連線
}
Send() 傳送資料 Receive() 迴圈接收客戶端資訊
Receive() 接收資料 Send()
Close() 傳送資料 捕捉異常 Close()
1.建立服務端的大概介面
第一排 txtServer(輸入IP) txtPort(輸入埠) btnStart(開始監聽) cboUsers(儲存連線到的IP和埠,combobox控制元件)
第二排 txtLog(顯示通訊結果)
第三排 txtMsg(輸入通訊資訊)
第四排 txtPath(儲存和顯示傳輸的檔案及路徑) btnSelect(選擇) btnSendFile(傳送檔案)
第五排 btnSend(傳送訊息) btnZD(震動)
2.進入btnStart 入口(點選開始監聽執行)
當點選開始監聽時 在服務端建立一個負責監聽IP和埠號的socket
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Any;//IPAddress.Parse(txtServer.Text);
建立埠物件 把IP和埠號拼接起來
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
監聽
socketWatch.Bind(point);
3.建立一個方法,提示監聽成功,並賦值給txtLog.Text.
方法要傳入一個string型別的引數,表示監聽成功
並且text要使用追加模式,才不會每次傳輸都覆蓋掉。
文字後面+\r\n 換行
void ShowMsg(string str)
{
txtLog.AppendText(str + “\r\n”);
}
4.使用ShowMsg方法,傳輸"監聽成功"
ShowMsg(“監聽成功”);
5.繼續在btnStart
*ShowMsg(“監聽成功”);
*設定監聽佇列 只能連進來10個
socketWatch.Listen(10);
*等待客戶端連線 並且建立一個負責通訊的socket
Socket socketSend= socketWatch.Accept(); socketWatch.Accept就是連線到的客戶端通訊
*RemoteEndPoint能夠拿到遠端連過來的IP和埠 輸出到txtLog
ShowMsg(socketSend.RemoteEndPoint.ToString()+":"+“連線成功”);
6.出現兩個問題
1.程式出現假死問題,因為只有一個主執行緒執行
開執行緒解決
2.因為接收通訊只接收了一次,所以只有一個客戶端可以連過來
寫在一個迴圈中解決
7.建立一個方法 等待客戶端的連線,並建立與之通訊用的socket
void Listen()
{
把第5步的這兩行剪下過來
**socketWatch只幹一件事情,監聽完後接受
Socket socketSend= socketWatch.Accept();
ShowMsg(socketSend.RemoteEndPoint.ToString()+":"+“連線成功”);
}
外面加上while(true)
{
}
出現問題 負責監聽的socketWatch訪問不到了
方法有2 1.第2步的socketWatch宣告在外面
2.把socketWatch傳進來
跨執行緒傳參只能傳object型別
採用第二種
傳入一個宣告的object型別的引數 o
void Listen(object o)
把object型別的o轉為Socket socketWatch
Socket socketWatch = o as Socket;
- Listen這個函式要被新執行緒執行,才不會假死
在btnStart方法中建立新執行緒 把Listen方法放進去
Thread th = new Thread(Listen);
設定為後臺執行緒
th.IsBackground = true;
執行並傳入socketWatch給Listen函式
th.Start(socketWatch);
9.程式會報錯,在程式載入時,取消跨執行緒的報錯
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
}
用telnet測試一下連線情況
10.在7步中的while迴圈繼續寫
客戶端連線成功後,伺服器應該接收客戶端發來的訊息
通過負責通訊的socket來處理
**跟客戶端收發資料都用負責通訊的socket socketSend
receive這個方法接受客戶端發來的訊息,接受位元組陣列
資料拿過來後先放到一個位元組數組裡面
byte[] buffer = new byte[1024 * 1024 * 2];
r是實際接受到的有效位元組數
int r=socketSend.Receive(buffer);
把傳過來的byte[]轉換成能讀懂的字串
string str= Encoding.UTF8.GetString(buffer, 0, r);
輸出到txtLog文字框裡面 呼叫之前的方法ShowMsg
ShowMsg用來作為輸出文字框的方法(自動換行)
socketSend.RemoteEndPoint表示與客戶端的IP和埠號
.tostring表示轉換為string型別了。和後面的一起作為一個整體的string型別傳參給ShowMsg();
在txtLog上顯示客戶端傳送的資料
ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + str);
11.測試後,發現只能傳輸一次數字。所以需要把傳輸寫入迴圈並封裝起來。
封裝成一個接收客戶端資訊的方法
void Recive()
{
while (true)
{
byte[] buffer = new byte[1024 * 1024 * 2];
//實際接受到的有效位元組數
int r = socketSend.Receive(buffer);
//把buffer轉換成能讀懂的字串
string str = Encoding.UTF8.GetString(buffer, 0, r);
ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + str);
}
}
socketSend讀取有問題,也需要傳參 同object型別
12.返回Listen的while迴圈中
開啟一個新執行緒 不停的接收客戶端傳送過來的訊息
Thread th = new Thread(Recive);
th.IsBackground = true;
th.Start(socketSend);
問題1.現在是發一個字元換一行 但要一行顯示(這個只要用自己寫的客戶端做就是正常的了,跳過)
問題2.關了客戶端,服務端還是一直在傳送。
因為客戶端關閉了,r的實際資訊是空(也就是0),但還是一直在傳送,所以要做一個判斷
if(r==0)
{ break; }
把容易發生異常的地方都try-catch起來。catch裡面什麼都不寫
13.建立客戶端模組
txtServer(192.168.1.42) txtPort(50000) btnStart(連線)
txtLog
txtMsg
14.點選連線進入btnSatrt_Click
建立負責通訊的socket interNetwork表示IPV4 sockettype(型別)流式的 protocoltype(服務)Tcp
Socket socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
客戶端要去連線伺服器
首先要用靜態方法建立ipAddress 的IP地址
IPAddress ip = IPAddress.Parse(txtServer.Text);
再把IP和埠號拼接一起
IPEndPoint point = new IPEndPoint( ip, Convert.ToInt32(txtPort.Text));
獲得要遠端連線的伺服器的IP和埠號
socketSend.Connect(point);
15.建立一個方法 ShowMsg()
void ShowMsg(string str)
{
要使用追加文字的AppendText才不會被覆蓋掉
txtLog.AppendText(str + “\r\n”);
}
在14步中傳入個"連線成功",表示客戶端與服務端連線上了
ShowMsg(“連線成功”);
16.連線上服務端 客戶端就給服務端傳送訊息
需要再txtMsg寫內容 傳送給伺服器
寫在傳送訊息事件裡,進入btnSend_Click
用負責通訊的socketSend來通訊。但是它寫在另一個事件裡拿不到,所以把它宣告到外面去
socketSend.send()發信息到服務端,需要放入一個位元組陣列
我們要傳送的txtMsg.text裡面的資訊,接收到string str裡面,trim是移除空格
string str = txtMsg.Text.Trim();
再把str轉換為位元組陣列
byte[] buffer = Encoding.UTF8.GetBytes(str);
傳送資訊到服務端
socketSend.Send(buffer);
17.服務端向客戶端傳送訊息
寫在傳送訊息事件內 btnSend_Click
先將第7步 socket socketSend宣告到外面
如同第16步 把txtMsg的資料傳到客戶端
string str = txtMsg.Text();
byte[] buffer = Encoding.UTF8.GetBytes(str);
socketSend.Send(buffer);
18.客戶端 發訊息不只一次,要不停的發,所以還要一直接收
寫一個和服務端的Recive方法一樣。使用wile(true)讓他迴圈使用
void Recive()
{
while(true)
{
}
}
用socketSend.Receive接收位元組陣列buffer,返回一個實際接受的有效位元組數r
byte[] buffer = new byte[1024 * 1024 * 3];
int r= socketSend.Receive(buffer);
如12步,再加上判斷位元組數為空的時候跳出迴圈 if(r0)
if(r0)
{
break;
}
再將資訊的陣列解碼為字串s來接收
string str=Encoding.UTF8.GetString(buffer, 0, r);
呼叫ShowMsg方法 把IP和埠號用:和接收的資訊連線起來並換行輸出,顯示在txtMsg上
RemoteEndPoint 獲取IP地址和埠號
ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + s);
19.返回btnStart_Click事件
防止執行單執行緒時候卡死,需要開啟一個新執行緒,把Recive的方法傳進去,不停的接受服務端發來的訊息
Thread th = new Thread(Recive);
設定為後臺執行緒
th.IsBackground = true;
th.Start();
20.為了防止跨執行緒報錯出異常
在form1_load載入時關閉跨執行緒的檢查
Control.CheckForIllegalCrossThreadCalls = false;
凡是涉及網路連線的都try-catch一下
21.現在只能和最後一個連上的客戶端通訊,因為新的客戶端連過來,原來的就沒有了。
所以服務端需要一個來儲存遠端IP和埠的下拉框,給指定的客戶端傳送訊息 cboUsers
用一個鍵值對來儲存 Directory 根據string型別的IP地址去找socketSend 鍵值對
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
22.在Listen的socketSend下面新增集合
將遠端連線的客戶端的IP地址和Socket存入集合中
所以發訊息的時候不能再直接用socketSend了,根據選中的IP地址來發送
dicSocket.d(socketSend.RemoteEndPoint.ToString(), socketSend);
將遠端連線的IP地址和埠號儲存到cboUsers下拉框中
cboUsers.Items.Add(socketSend.RemoteEndPoint.ToString());
轉到 btnSend_Click IP和埠
獲得使用者在下拉框中選中的IP地址指定給string型別的ip
string ip = cboUsers.SelectedItem.ToString();
dicSocket[ip].Send(buffer);
測試
23.現在已經實現了服務端和客戶端互相收發資料,但僅限文字型別資料
服務端還有檔案和震動 客戶端怎麼區分發過來的是什麼。因為發來的是位元組陣列
告訴客戶端 發的是什麼東西
製作一個偽協議
協議是約定 互相都要遵守
在位元組陣列上做手腳
標記一下 根據型別把位元組資料前面的加上一位,[0]為標記 0,文字 1,檔案 3,震動
24.對服務端的接收做判斷
服務端的接收內做判斷
如果是0,按照文字處理,1,按照檔案處理,2,按照震動處理
服務端給客戶端發信息 btnSend_Click
在buffer中安插一個byte[0]=0;
陣列長度不可變,需要新建一個數組來接受,併發送過去
陣列長度 buffer.length+1 buffer[0]=0;
賦值給一個新的buffer 使用一個List的泛型集合新增,再轉換為位元組陣列
List list = new List();
list.Add(0);
list.AddRange(list);
byte[]newByte= list.ToArray();
最後把傳過去的陣列改為 dicSocket[ip].Send(newBuffer);
不能直接賦值給buffer,因為長度不一樣。
25.返回客戶端接收伺服器發來的訊息 Recive
在接收字串後得到實際資料大小下面新增判斷首數字為(0,1,2)
if(buffer[0]==0)
{
}
把得到文字的處理程式碼放入這個判斷中
然後擴充套件其他的
else if(buffer1)
{
}
else if(buffer2)
{
}
if(r==0)的判斷可以放到外面,不用3個都寫
if (r == 0)
{
break;
}
處理文字的程式碼直接套入會有問題,因為位元組多了一個首數字,需要算進來
解碼的時候要從第二個元素(下標為1)開始處理,因為第一個元素是標記類,沒有用
解碼多少個,r-1。因為第一個沒有用。
string s = Encoding.UTF8.GetString(buffer,1 , r+1);
測試 不會丟失文字
26.服務端選擇檔案
點選 選擇 的時候應該彈出對話方塊 選擇檔案 btnSelect事件中 選擇傳送的檔案
OpenFileDialog ofd = new OpenFileDialog();
ofd.Title = “請選擇要傳送的檔案”;
ofd.InitialDirectory = @“C:\Users\SJD\Desktop”;
ofd.Filter = “所有檔案|.”;
ofd.ShowDialog();
txtPath.Text= ofd.FileName;
27.服務端傳送檔案
點選 傳送檔案 btnSend
獲得要傳送的檔案的路徑
string path = txtPath.Text;
讀取資料流
using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
int r= fsRead.Read(buffer,0,buffer.Length);
拿到負責通訊的socket把位元組陣列buffer發過去
設定buffer的大小,讓他以實際引數大小發過去。
buffer陣列,從0到r,無值列舉引數
dicSocket[cboUsers.SelectedItem.ToString()].Send(buffer, 0,r, SocketFlags.None);
}
需要把數值1新增在首位位元組,所以
using (FileStream fsRead = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
int r= fsRead.Read(buffer,0,buffer.Length);
//設定buffer的大小,讓他以實際引數大小發過去。
List list = new List();
list.Add(1);
list.AddRange(buffer);
byte[]newBuffer= list.ToArray();
dicSocket[cboUsers.SelectedItem.ToString()].Send(newBuffer, 0,r+1, SocketFlags.None);
}
28.找到客戶端開始接收
在else if(buffer[0]==1)中建立 saveFileDialog的物件
SaveFileDialog sfd = new SaveFileDialog();
sfd.Title = “請選擇要儲存的檔案”;
sfd.InitialDirectory = @“C:\Users\SJD\Desktop”;
sfd.Filter = “所有檔案|.”;
WIN7,WIN8要加this。否則儲存目錄彈不出來
sfd.ShowDialog(this);
string path=sfd.FileName;
using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
{
fsWrite.Write(buffer, 1, r - 1);
MessageBox.Show(“儲存成功”);
}
會出問題的地方都try-catch一下
29. 傳送震動 進入 btnZD_Click事件
建立一位的位元組陣列,放入2,根據它的鍵值對傳送給指定IP客戶端
byte[] buffer = new byte[1];
buffer[0] = 2;
dicSocket[cboUsers.SelectedIte()m.ToString()].Send(buffer);
30.客戶端設定震動
客戶端建立一個震動的方法
void ZD()
{
迴圈500次 這個form(客戶端)一直不停賦值,位置一直移
for (int i = 0; i < 500; i++)
{
this.Location = new Point(200, 200);
this.Location = new Point(230, 230);
}
進入else if(buffer[0]==2) 把方法 ZD()放進來