[Tutorial] Xây dựng giao thức Kermit truyền file text dùng C#

nguyenquoctrung-hhk

Thành Viên PIF
Tham gia CLB lâu ròi, mà chưa đóng góp gì, thấy CLB cũng phát triển vê software, về moi móc cái bài tập giữa kỳ vừa ròi, tại thấy nó cũng có ích cho những anh em nào học môn truyền số liệu, mà không hiểu gì hết =)),một mặt cũng cố C#.
Khi thầy giao đề tài này để thi giữa kì trong vòng 1 tháng, mình cũng thấy choáng. hiểu môn học đã khó, mà thầy còn chơi bức xô, không hướng dẫn gì hết, tại thầy dùng VB6, đứa nào học VB6 thầy mới hướng dẫn được :gach, mình với kiến thức nông cạn về C#, nhưng cũng liều mạng 1 chuyến, sau 30 ngày đêm :2cool_after_boom: lăn lộn, rong chơi ở google, lụm nhặt những đoạn code rời rạc + kiến thức lập trình về C# để kết thành project, thành quả đạt được là mình có thêm kiến thức về C# + đạt điểm tối đa :4cool_beauty:. nói ra không phải khoe với anh em, mà gửi lời nhắn nhủ đến mọi người, không có việc gì khó, cái nào mình gà thì cứ lao vào mà tìm kiếm học hỏi nâng cao kiến thức, tại vì mình đang trong quá trình "lụm nhặt" kiến thức, không có gì phải sợ thất bại :la:. Nói vòng vo đã ròi, bi giờ vào phần chính :D.
Trước khi nói, vì project này khá dài, mình phải nói từ từ, không thể nào post 1 lúc lên được, nên anh em nào có câu hỏi gì thì mình có thể mở thread mới để mọi người vào thảo luận, tại lỡ post trong mục này làm ngắt quảng project khiến những người tham khảo đọc rất là khó chịu đấy :).
! yêu cầu project :1cool_choler::
_ xem qua giao thức kermit
_cơ chế truyền idle RQ
_mã BCS
_kiến thức về C# (cái này không phải có các class, method sẵn đâu, phải coi cách khởi tạo 1 method, truy suất 1 class như thế nào, nói chung lên google kiếm tài liệu lập trình cơ bản về C#)

ở đây mình sẽ không nhắc lại kĩ về lý thuyết truyền số liệu, chỉ nói sơ thui, còn anh em nào thắc mắc, thì thảo luận ở một box khác nhé :5cool_still_dreaming:.

*Phần 1 : Basic theory
_giao thức kermit là một giao thức truyền file giữa 2 máy tính. file này có thể thuộc nhiều loại như pdf, xls, doc, txt,... thì ở đây để đơn giản và dễ thì mình chọn file txt, tại vì file này chỉ dùng bảng mã ASCII thôi, còn mí cái file khác có thể dùng các bảng mã unicode hay UTF8,... rất phức tạp.
_cơ chế truyền theo idle RQ (stop ang wait) có nghĩa là trạm phát truyền xong và đợi tín hiệu phản hồi từ trạm nhận rồi mới truyền tiếp tục.
_mã BCS (block sum check) là là 1 hình thức sửa sai, khi tín hiệu truyền đi trên đường truyền, chắc chắn phải có lỗi xảy ra, tùy theo tốc độ, độ dài, kết cấu dây cab. mã này các bạn có thể xem trong tài liệu môn truyền số liệu. mình chỉ nói ngắn gọn là như thế này. dữ liệu file của mình là khá lớn, cho nên không thề nào bưng một cục bự mà quăng qua phía nhận, cho nên phương pháp là "chia để trị" có nghĩa mình sẽ cắt nhỏ cái file lớn thành từng frame. Ví dụ 1 file có độ dài 100 byte, thì mình sẽ cắt ra mỗi phần là 20 byte chẳng hạn rồi truyền đi. lý do tại sao phải làm như vậy thì trong các phần sau mình sẽ nói. thì khi nhận được 1 frame sẽ tiến hành sửa sai như sau :
_____giả sử có 1 frame 10 byte, thì mình sẽ sắp mỗi byte (8 bit) là một hàng, mỗi bit là 1 cột. Như vậy mình sẽ có 8 cột và n hàng. n : tùy theo số lượng byte của 1 frame. Sau đó mình sẽ kiểm tra bít parity chẵn theo hàng và parity lẻ theo cột (việc kiểm tra này tùy theo mình quy định), cuối cùng giao cột sai với hàng sai là bit sai. Chú ý là mả này chỉ sửa được có 1 lỗi, 2 hàng hoặc hai cột sai là không sửa được.
_C# tự nghiên cứu lý thuyết cơ bản nhé, nếu có thời gian thì mình sẽ mở 1 thread hướng dẫn 1 tí cơ bản về nền tảng C#:).
_ cấu trúc của một frame kermit như sau :
SOH--LEN--SEQ--TYPE--DATA--BCC--CR​
--SOH : kí tự bắt đầu của một frame (tra trong bảng mã ASCII)
--LEN : số kí tự của 1 khung tính từ sau LEN tới hết BCC
--SEQ : số thứ tự khung
--TYPE : ky ùtö ïcho bieát loïai khung, moãi loïai seõ co ùnoäi dung va ønhieäm vu ïkhaùc nhau
S (send initation): khung start của qua trình truyền
F (filename): chöùa teân file caàn truyeàn.
D (file data): döõ lieäu
Z: coøn goïi laø khung EOF (end of file)
B: coøn goïi laø EOT (End of transacsion), finish truyền.
Y: khung phaûn hoài tín hieäu ACK
N: khung phaûn hoài tín hieäu NAK
E (fatal error): khung baùo loãi

-- DATA : dữ liệu truyền đi
--BCC : mã BSC
--CR : kí tự kết thúc khung (tra bảng mã ASCII)

_Ở đây sẽ truyền qua cổng COM của máy tính, nếu anh em nào đã sử dụng FT232 thì có thể kết nối với USB của laptop, hình thức truyền để kiểm tra thì mình sẽ dùng cái jumper nối 2 đâu Rx và Tx trên cái board FT232 có nghĩa mình truyền xong òi tự nhận về lun :D.

continue ... :)

 

nguyenquoctrung-hhk

Thành Viên PIF
phần 1 : Basic Theory (continue)

Phần này mình sẽ trình bày :
_ quá trình thực thi của Kermit Protocol
_ giải thuật.

1. Quá trình thực thi kermit protocol

Bước 1 : trạm phát đưa ra thông tin kết nối bằng cách truyền khung S (start)
Bước 2 : trạm nhận thu được khung S và báo về bằng khung Y (ACK) để bắt đầu quá trình nhận data
bước 3 : trạm phát gửi khung F (tên file)
bước 4 : nhận được tín hiệu ACK, trạm phát gửi tiếp n khung D cho đến khi hết khung
bước 5 : để kết thúc khung truyền thì trạm phát gửi khung Z
bước 6 : khi kết thúc khung truyền, thì trạm phát sẽ gửi khung cuối cùng là khung B để kết thúc quá trình truyền.

chú ý khi trạm thu nhận được data, nếu data đúng hoặc sửa lỗi được thì báo ACK ngược lại là NAK. Trong phần này mình chưa tính đến việc mất gói trên đường truyền và cơ chế truyền lại vì nó nằm trong phần giải thuật. Và đây là hình thức truyền 1 phía, bên trạm thu chỉ có nhận và trả lời ACK hoặc NAK. còn bên trạm phát chỉ được truyền tiếp khi có tín hiệu ACK từ trạm thu. còn nếu NAK thì phải truyền lại.

2. giải thuật
trong phần này chỉ nói tới việc đóng khung của dữ liệu đọc từ file txt, không nói tới đóng khung S,Z,B vì mấy cái này là khởi tạo và kết thúc quá trình truyền, ta chỉ thêm vào cho thành mô hình kermit, cái chính là xử lý data đọc được từ file txt

trạm phát :
----------------------Tx-----------------------------
B1 : đọc toàn bộ dữ liệu có trong file txt
B2 : chia nhỏ dung lượng file txt này theo một con số quy định (sẽ nói sau trong phần viết code)
B3 : đóng gói từng file nhỏ theo kiểu chuỗi đúng với cấu trúc khung kermit
B4 : chuyển dữ liệu khung kiểu chuỗi thành mảng byte
B5 : tính mã sửa sai, sau đó add vào vị trí BCC
B6 : chuyển tới cổng COM và truyền đi và chờ tín hiệu ACK hoặc NAK từ trạm phát. nếu ACK thì truyền khung kế tiếp, NAK thì truyền lại. khi hết khung truyền thì đóng cổng COM lại.
----------------------Rx-----------------------------
B7 : đọc dữ liệu từ COM
B8 : sắp xếp n byte thu được thành mảng 2 chiều có n hàng và 8 cột. sau đó tiền hành giải thuật sửa sai
B8 : nếu là ACK hoặc NACK thì quay lại B6. trong trường hợp mất khung dữ liệu,ở đây ta sẽ có 1 cái clock quy định thời gian tồn tại của khung truyền, quá thời gian này thì sẽ tự động truyền lại (nhưng mà mình chưa khắc phục được code chổ này :D).

trạm thu :
nếu đã viết code được cho trạm phát thì trạm thu cũng đơn giản nhưng có phần khác chút xíu thui. :D
B1 : đọc data từ COM và sửa sai
B2 : nếu sửa không được thì gửi NAK và quay B1
B3 : nếu data đúng, thì kiểm tra loại khung.
B4 : nếu khung F thì mở file txt và lưu tên file nhận được, hoặc nếu là khung D thì mở file lưu giữ liệu vào.
B5 : gửi ACK và quay lại B1

như vậy về mặt lý thuyết cũng như giải thuật đã tóm gọn một cách sơ lược, có thể sẽ không rõ ràng lắm, nhưng cũng có thể giúp mọi người hình dung vấn đề. để phân tích chi tiết hơn sẽ đi qua phần coding :), cái phần nài mới là "tả bí lù" =))
**** P/s : ngồi viết mà trong chóng mặt luôn :5cool_sweat:, chưa quen xài phần mềm clip của CLB nên chấp nhận ngồi viết tay :-s
 

nguyenquoctrung-hhk

Thành Viên PIF
phần 2 : Coding - Trạm phát

" method " dịch ra tiếng việt nghĩa là " phương thức ". Nghe từ phương thức thì chỉ có dân lập trình đối tượng nói chuyện với nhau mới hiểu, lúc trước mình nghe cũng chả hình dung nó là cái chi :(. giờ đã hiểu nói diễn dãi cho những ai chưa hiểu, bản chất nó mình có thể hình dung là có một người nào đó chuyên làm 1 công việc nhất định. Mình là người biết nó làm được cái việc đó, nên đưa tiền nhờ nó làm, rồi nhận hàng từ nó là xong. liên hệ với C cổ điển thì nó là các hàm, nhưng nói hàm thì chỉ tổng quát. còn trong lập trình đối tượng nói "phương thức" thì nó chỉ cụ thể, vì C# nó bao gồm class, method, ... cho nên nói hàm thì không chỉ ra được đâu là class, đâu là method :D. Trong phần này sẽ trình bày các method để phục vụ cho việc truyền file.

*** chú ý bên dưới chỉ phân tích các method, sẽ không hướng dẫn tạo một button hay ô text box,... vì mấy cái này anh Lee đã hướng dẫn trong phần C#

A. Tạo nút browser
cái này rất quen thuộc khi bạn muốn đính kèm một tập tin nào đó có trong máy tính của mình lên mail, face book, ... đề làm được điều này, bạn phải truy suất tới phương thức "OpenFileDialog" và "FileStream" .
_ OpenFileDialog : khi truy suất tới phương thức này nó sẽ tìm đến hộp thoại quản lý windown và mình có thể mở hộp thoại này lên bằng phương thức ShowDialog().
_ FileStream : đây là một phương thức dùng để theo tác trên file, FileStream tạm dịch ra là dòng chảy của tập tin, có một số phương thức để thao tác trên file, nhưng tại sao phải dùng cái này thì nó thuộc về lý thuyết C# :), cho nên sẽ không nói ở đây, nếu các bạn thắc mắc có thể lên google search còn không thì có thề thảo luận ở một box nào đó, hehe ":*".

trước tiên mình sẽ khao báo mới một cái đối tượng để thao tác trên hai cái phương thức "OpenFileDialog" và "FileStream"

--- private OpenFileDialog openFileDialog1 = new OpenFileDialog(); ------
--- public FileStream file; ----

ở đây có hai đối tượng được khai báo là openFileDialog1 , file
_ khi đó để mở hộp thoại :
openFileDialog1.ShowDialog();​
_ lấy tên file trong hộp thoại và hiện lên một cái ô textbox nào đó :
txtBrowsFile.Text = openFileDialog1.FileName;​
ở đây lấy tên file trong hộp hoại thì ta sẽ truy suất tới đối tượng FileName dùng lệnh openFileDialog1.FileName​
_đọc data có trong file mình vừa mới lấy dùng phương thức FileStrream :​
file = new FileStream(txtBrowsFile.Text, FileMode.Open, FileAccess.Read);​
giải thích các tham chiếu :​
** txtBrowsFile.Text : tham chiếu này sẽ đưa đường dẫn của file, ở đây thì mình lấy nội dung trong cái ô textbox là txtBrowsFile.Text​
** FileMode.Open : chế độ mở file hay đóng file thì ở đây là mở file được dùng với đối tượng Open​
** FileAccess.Read : mở file rồi thì sẽ có nhiệm vụ là đọc file hay ghi lên file thì cái này là đọc nội dung file bằng đối tượng Read.​
code nút browser như sau :​
Code:
private OpenFileDialog openFileDialog1 = new OpenFileDialog();
public FileStream file;
private void brows_Click_Click(object sender, EventArgs e)
{
            // khi nhấn nút browse hàm sẽ cố gắng làm việc trong vùng "try"
            try
            {
                openFileDialog1.ShowDialog(); // mở cửa sổ hộp thoại
                txtBrowsFile.Text = openFileDialog1.FileName; //lấy đường dẫn từ hộp thoại
                file = new FileStream(txtBrowsFile.Text, FileMode.Open, FileAccess.Read);
                lblSendingResult.Text = "";
            }
            // Nếu việc trên bị hủy. để tránh tình trạng treo máy, sẽ làm việc trong vùng "cath"
            catch
            {
                // hiện thị thông báo lên textBox
                lblSendingResult.Text = "You don't add file! please add file in your computer";
            }
 
}
dỹ nhiên mấy cái lblSendingResult.Tex, txtBrowsFile.Text là phải tự tạo mấy cái textbox. mà có tạo tên khác thì nhớ thay tên trong code luôn, không thì bị báo lỗi đấy :D. cấu trúc try -- catch thì tham khảo C# nhé.

continue... :)
 

nguyenquoctrung-hhk

Thành Viên PIF
phần 2 : Coding - Trạm phát (continue)
kể từ phần này, các method sẽ liên quan với nhau. tại mình muốn viết theo cấu trúc logic từ trên xuống dưới, cho nên anh em nào mà nhảy vào ngang là không hiểu gì hết =)), hehe.
Trong phần này mình sẽ trình bày 2 method là "đóng gói" và "tách chuỗi". Trước khi vào code mình sẽ nói sơ qua về việc tách cái dự liệu. Để dễ hình dung mình sẽ lấy một ví dụ minh họa sau :​
mình có một cái bồn chứa 500 lit nước, bây giờ mình sẽ vẫn chuyển lượng nước này trên đoạn đường 3km để về đến nhà, chưa yêu cầu về tời gian, vì chạy nhanh quá lỡ bị va quẹt nữa :D. Vậy thử nghĩ xem mình sẽ có bao nhiêu cách làm :​
1. chở nguyên cái bồn 500 lít về tới nhà :5cool_sweat: -----> không khả thi​
2. kiếm cái can nào 100 lít vận chuyển 5 lần :) -------> thấy có vẻ được nhưng vẫn chuyển cũng hơi cực, cồng kềnh, dễ làm rơi nước nếu can dzỏm :gach
3. nếu 100 lít không được thì dùng 10 lít :2cool_sexy_girl: -----> khả thi nhưng hao xăng chạy tới chạy lui 50 lần, hehe​
4. cân đo đong đếm thì cỡ 80 lít cho 1 lần là ok :6cool_boss: -----> cách này có vẻ khả quan​
Trên đây chỉ nêu ra một cách vui nhộn, nhưng chắc rằng cho ta hình dung được việc vận chuyển data tương tự. vậy việc truyền dữ liệu, còn phải cân nhắc đến vần đề :​
_ độ dài đường truyền​
_ tốc độ truyền​
_ băng thông​
_ chất liệu dây dẫn để chống nhiễu, ít gây lỗi trên đường truyền​
về việc chia nhỏ gói data ra nhằm để đảm bảo được dữ liệu truyền đi ít bị lỗi, tốc độ truyền nhanh trên 1 gói nhỏ, giảm băng thông lại và ít bị nhiễu. Đặt ra vấn đề vậy chọn dung lượng của 1 gói truyền đi là bao nhiêu ? nếu chọn ít quá thì làm cho việc chờ đợi truyền cho hết 1 file là khá lâu nếu tốc độ nhỏ. Tốc độ nhanh thì gây ra lỗi nhiều :-(, do đó phải có một cơ cấu xử lý lỗi bên đầu thu tốt. Tuy nhiên trong thực tế người ta sẽ có các quy định về tốc độ đường truyền, băng thông, độ dài đường truyền theo các chuẩn (cái này có thể tham khảo google)​
*** Trong project chọn truyền qua cổng COM với cự ly cỡ 10-15m với tốc độ 9600 bps, băng thông có thể không nói tới vì với cự ly này chắc chắn sẽ không có lỗi gì hết, vì theo chuẩn của FT232 và RS232​
*** Trong code thì vừa có tiếng anh, xen lẫn tiếng việt :), cái này dó mình lười biếng làm xong project, mệt quá, cái rồi không chỉnh lại. nên mọi người thông cảm :D
B. Tách chuỗi
nhiệm vụ của method này sẽ nhận nội dung của file và sau đó tiền hành cắt nó ra từng gói nhỏ sau đó sẽ đưa qua method đóng gói thành cấu trúc khung kermit kiểu chuỗi. với phương thức này ta sẽ khai báo như sau :​
public void Tach_chuoi(string chuoi)​
{​
lệnh xữ lý chuỗi​



}​
đối số "chuoi" truyền vào sẽ là nội dung của file. Nội dung trong hàm được làm như sau :​
_ khi ở dạng chuỗi mỗi kí tự như một ô nhớ trong 1 mảng, do đó ta sẽ cho vòng lặp 0 --> chiều dài chuỗi. ở đây mình chọn cắt 1 lần là 20 byte (1 byte tương ứng 1 kí tự)​
_ cần 1 biến tạm để lưu cái chuỗi được cắt, cứ mỗi 20 byte sẽ cắt 1 lần​
_ sau đó truyền cái chuỗi được cắt qua method đóng gói​
_ sau đó reset lại biến tạm.​
_ ngoài ra mình sẽ có 1 biến " dem " để lưu số thứ tự chuổi được cắt. ở đây mình dùng cơ chế Idle RQ stop and wait, cho nên đơn giản là chuỗi chẵn ( số 0), chuỗi lẻ (số 1)​
_ khi chia chuỗi còn dư < 20 thì ta sẽ tự động thêm "kí tự không in được " trong bảng mã ASCII để đủ 20 byte, điều này được thực hiện trong method đóng gói.​
code như sau :​
Code:
public void Tach_chuoi(string chuoi)
{
            string temp = "";
            int dem = 0;
            //tách chuỗi
            for (int i = 0; i < chuoi.Length; i++)
            {
                temp += chuoi[i];
                if (temp.Length == 20)
                {
                    temp = Dong_Goi(temp, "D", dem); // truyền tới method đóng gói
                    Start(temp);
                    if (dem == 0) // thay đổi số thự tự
                        dem = 1;
                    else
                        dem = 0;
                    temp = "";
                }
                if ((i == chuoi.Length - 1)&&(temp.Length < 20)) // chuỗi còn dư cuối cùng
                {
                    temp = Dong_Goi(temp, "D", dem);
                    Start(temp);
                }
            }
}
hàm Start là mình viết đễ dễ hiểu là bắt đầu truyền file. sẽ được nói tới trong phần sau.​
C. Đóng gói
việc đóng gói theo kiểu chuỗi rất dễ, chỉ việc cộng các chuỗi đưa vào là xong, và sẽ kiểm tra coi chuỗi cắt nhỏ truyền vào đã đủ 20 byte chưa, chưa đủ thì thêm ký tự không in được trong bảng mã ASCII​
code :​
Code:
public string Dong_Goi(string frame, string Type_file, int num_file)
{
            char len;
            len = (char)(3 + frame.Length + 32);
            while(frame.Length < 20)
            {
                frame += ((char)3).ToString();
            }
            frame = ((char)1).ToString() + ((char)len).ToString() + ((char)num_file).ToString() + Type_file + frame + ((char)0).ToString() + ((char)13).ToString();
            return frame;
}
biến "len" là chiều dài của chuỗi : data + BCC + TYPE + SEQ ===> len = (char)(3 + frame.Length + 32). ở đây dữ liệu đang ở dạng số, mình phải ép nó về kiểu char sau đó biến kiểu char thành kiểu string thì mới cộng chuỗi được. Điều đáng chú ý, trong bảng mã ACII có phân ra 2 dạng kí tự : in được và không in được.​
các con số thì nó lại nằm ở vùng kí tự in được, do đó muốn thể hiện 1 con số trong bảng mã thì phải cộng thêm cho 32. đó là lý do tại sao mình cộng cho 32.​
Trong method có 3 tham chiếu :​
__ frame : chuỗi được cắt ra từ nội dung file​
__ Type : loại khung gì​
__ num_file : số thứ tự của 1 khung​
Nội dung trả về sẽ là 1 chuổi cấu trúc kermit.​
continue ... :5cool_sweat:
 

nguyenquoctrung-hhk

Thành Viên PIF
phần 2 : Coding - Trạm phát (continue)
Sau khi đóng gói thành cấu trúc kermit ở dạng là một chuỗi các ký tự liền nhau, ta sẽ chuyển chuỗi này thành 1 mảng kiểu byte, có nghĩa mỗi ký tự sẽ được lưu dưới dạng là số có kiểu byte, được lưu trữ trong 1 ô nhớ của mảng byte.​
Mục đích của việc chuyển qua mảng byte để ta tách từng bit của mỗi ký tự để mã hóa bit parity hàng, sau đó xử lý từng byte để mã hóa bit parity theo cột.​
Trong phần này sẽ trình bày 3 method :​
_ chuyển đổi kiểu String thàng mảng byte​
_ tính mã parity theo hàng​
_ tính mã parity theo cột​
_ trả về 1 khung kermit hoàn chỉnh theo kiểu mảng byte​
việc tính mã parity theo hàng và parity theo cột để ta tìm được cái mã BSC​
D. chuyển đổi String thành mảng byte
Để chuyển đổi kiểu string thành mảng byte thì trong thư viện System.Text có lớp ASCIIEncoding.
Với lớp này sẽ cho phép chuyển đổi String có mã ASCII thành 1 mảng byte thông qua 1 cái method GetBytes(str)
*** với str là chuỗi cần chuyển đổi​
Bên cạnh đó mình sẽ khai báo 1 cái mảng byte để lưu dữ liệu đã được chuyển đổi.​
sau phương thức này sẽ trả về 1 mảng byte​
Code:
public byte[] StrToByteArray(string str)
{
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            Byte[] bytes = encoding.GetBytes(str);  // sau lệnh này dữ liệu đã được chuyển đổi
            return bytes;
}
E. tính mã parity theo hàng (patity lẻ)
Để tính được parity theo hàng của từng byte. ta phải gửi cái byte đó vào 1 cái method "tách bit" nhiệm vụ của cái method "tách bit" :​
_ đếm số bit "1" có trong 1 byte gửi đến​
_ nếu số bít chẵn thì trả vể giá trị "true" ngược lại là "false"​
Lưu ý : tính mã patity theo hàng và cột bắt đầu tự vị trí LEN tới vị trí kí tự cuối cùng của DATA​
*** ở đây có thêm 1 kiểu bool sẽ cho phép ta trả về 2 giá trị true hoặc false
Code:
public bool Tach_bit(int a)
{
            int phan_du, dem = 0;
            do
            {
                phan_du = a % 2;
                if (phan_du == 1)
                    dem += 1;
                a = a / 2;
            }
            while (a != 0);
            if (dem % 2 == 0)
                return true;
            else
                return false;
}
_ nếu method nào mà gọi method " Tach_bit " thì tính mã parity hàng theo lệnh sau :​
Code:
if (Tach_bit(frame))
 
    frame = (byte)(frame | 0x80);
*** lưu ý, khi tính toán là dạng số kiểu int cho nên khi tính xong phải ép kiểu byte trở lại mới lưu được vào mảng byte
F. tính mã parity theo cột (parity chẵn)
sau khi tính parity theo hàng thì mới tính tiếp parity theo cột, rất dễ dàng là ta chỉ việc truyền 1 mảng byte vào và xor từ vĩ trí LEN tới vị trí kí tự cuối cùng của DATA​
sau method này sẽ trả về mã BSC​
Code:
public byte GetBCC(byte[] inputStream)
{
            byte bcc = 0;
 
            if (inputStream != null && inputStream.Length > 0)
            {
                for (int i = 1; i < inputStream.Length - 2; i++)
                {
                    bcc ^= inputStream[i];
                }
            }
            return bcc;
}
*** chú ý cách khởi tao method trả về giá trị là 1 mảng
G. Trả về mảng byte hoàn chỉ để truyền tới COM
Sau khi tính được BSC bước kế tiếp là mình phải update cái giá trị này vào vị trí BCC trong mảng byte. vì các đoạn code trước thì mình chỉ gán ở vị trí này là 0, tức là chưa có mã sửa sai.​
Code:
public byte[] Return_full_byte(byte[] frame)
{
 
            byte BCC;
            for (int i = 1; i < frame.Length - 2; i++)
            {
                if (Tach_bit(frame[i]))
                    frame[i] = (byte)(frame[i] | 0x80);
            }
            BCC = GetBCC(frame);
            frame[frame.Length - 2] = BCC;  // update BSC tại đây
            return frame;
}
fame i : phần tử thứ i trong mảng byte
_ở phần trên mình có nhắc tới cái method " Start " thì cái này mình tóm gọn lại để khi nhìn vào cái đoạn code chính thì sẽ hình dung quá trình truyền hơn :D, thì trong method "Start" nó bao gồm những gì mình vừa nói ở trên thui :) :
Code:
public void Start(string str_send)
{
          byte[] by_send;
          by_send = StrToByteArray(str_send);
          by_send = Return_full_byte(by_send);
          Truyen_Data(by_send);
}
trong này thì có một cái method "Truyen_Data" thì mình sẽ trình bày trong phần sau :2cool_sexy_girl:
to be continue .... :met:
 

nguyenquoctrung-hhk

Thành Viên PIF
phần 2 : Coding - Trạm phát (continue)
ở phần trên ta đã hoàn thiện việc đóng gói dữ liệu thành 1 mảng byte, việc bây giờ ta sẽ đưa mảng byte kermit này tới buffer của cổng COM để cổng COM làm nhiểm vụ truyền tải chúng một cách tuần tự đến trạm nhận. Trong phần này sẽ trình bày :​
_ truyền dữ liệu ra COM dưới dạng 1 mảng byte​
_ giới thiệu sử dụng ô listbox để hiện thông báo, theo dõi việc truyền nhận​
trước khi đi vào code, mình sẽ nói thêm một vài thông số lưu ý khi sử dụng cổng COM :​
_ writebuffersize : dung lượng để ghi dữ liệu vào cổng COM ở đây mặc định là 2048 byte ta cũng có thể sửa thông số này đi​
_ Readbuffersize : dung lượng để COM nhận dữ liệu từ bên ngoài vào, dung lương này mặc định là 4096 byte, nó gấp đôi writebuffersize, và mình cũng có thể thay đổi thông số này​
_ Receivedbyte threasold : môi lần cổng COM đọc về bao nhiêu byte từ bên ngoài nó sẽ dựa vào thông số này để ngắt, mặc định thộng số này là 1 byte, có nghỉa khi đọc đủ 1 byte nó sẽ ngắt và mình sẽ dựa vào hàm ngắt đễ xử lý 1 byte này, và thông số này có thể thay đổi được.​
Vậy đặt ra vấn để thay đổi như thế nào ?? :6cool_boss:
ta hình dung buffer của cổng COM như là 1 cái bể chứa nước, cái bể này có 1 cái vách ngăn di động được. vì thế ta phải biết được thể tích lớn nhất của nó là bao nhiêu ? để có thể chia lượng nước bơm từ máy bơm vào (writebuffersize), và lượng nước lấy từ nơi khác đến (Readbuffersize). Tại sao lại chia Readbuffersize gấp 2 lần writebuffersize thì cái nì search google :). còn Receivedbyte threasold mình liên tưởng tới cái rờ le khi đo đúng mực nước ở Readbuffersize lên tới mức quy định thì nó sẽ ngắt cái van không cho nước bơm vào, chờ khi nào lượng nước được lấy ra thì nó bắt đầu mở van trở lại, nói đến đây mình hy vọng các bạn sẽ rất dễ hình dung :D. bây giờ thì mình có thể đi vào phần code :3cool_adore:
H. truyền data tới COM dưới dạng byte
lúc trước anh Lee có hướng dẫn cách truyền data tới COM nhưng cái đó chỉ là truyền 1 chuỗi ký tự. còn bây giờ mình sẽ nói thêm một hình thức đưa 1 mảng byte tới cổng COM như sau :​
Com.Write(frame, 0, frame.Length);
trong đó :​
_ frame : mảng byte cần truyền​
_ 0 : là vị trí bắt đầu trong mảng, ở đây mình chọn là 0​
_ frame.Length : vị trí kết thúc ở đây mình chọn là độ dài của mảng​
COM sẽ đọc tuần tự bắt đầu từ vị trí 0 đến ( frame.Length - 1 ) và truyền tuần tự ra bên ngoài.​
giải thuật truyền như sau :​
_ kiểm tra máy tính đã được kết nối với COM chưa​
_ sau khi kết nối xong sẽ đưa dữ liệu tới COM vả truyền đi​
_ delay 1 khoảng thời gian để chờ tín hiệu từ trạm phát ở đây sẽ có 1 cái cờ để báo hiệu nhận tín hiệu​
_ phân tích cái cờ này là cờ ACK hày là NAK nếu là ACK thì truyền tiếp và hiện thông trên ô listbox đã truyền được khung thứ mấy.​
_ nếu là NACK và SEQ của cái khung đó = "0" thì sẻ kiểm tra cái khung bên trạm phát gửi về là loại khung gì, nếu là khung S thì sẽ là hủy đường truyền, do trong phần lập trình mình quy định bên trạm phát gửi khung S sau đó bên trạm thu sẽ có 2 sự lựa chọn 1 là hủy đường truyền hay là kết nối đường truyền bằng cách máy bên trạm thu sẽ có cái nút cancel trên giao diện và một cái listbox thông báo kết nối từ trạm phát.​
_ ngoài ra việc hủy đường truyền khi đã truyền xong khung B (code : sothutu == int_frame + 3)​
*** ở đây xuất hiện cái biến int_frame thì nó là số phần sau khi mình chia cái nội dung đọc từ file text. do đó cũng chính là số khung D. sau đó cộng thêm 3 cái khung : S,Z,B là thành số thứ tự của khung cuối cùng :D. sothutu cái biến này để mình theo dõi quá trình truyền khung, nó sẽ không liên quan gì tới cái SEQ đâu đấy, đừng có nhầm lẫn :)
Code:
public void Truyen_Data(byte[] frame)
{
 
            if (LbStatus.Text == "Connect")// kiểm tra COM đã kết nối chưa
            {
                do
                {
                    flag = " ";
                    Com.Write(frame, 0, frame.Length);
 
                    while (flag == " ") // xem cờ có thay đổi hay không
                        delay_ms(100);
                    // kiểm tra việc hủy đường truyền hoặc khung cuối cùng
                    if ((sothutu == 0 && flag == "NAK") || sothutu == int_frame + 3)
                    {
                        if(frame[3] == 211) // kiểm tra có phải là khung S hay không
                            txtListBox.Items.Add("transmission discard");
                        else
                            txtListBox.Items.Add("sent !"); // khung B sau khi đã hết khung truyền
                        Com.Close();
                        LbStatus.Text = "Disconnect";
                        PbConnect.Text = "Connect";
                        sothutu = 0;
                        break;
                    }
                }
                while(flag == "NAK");
                if (LbStatus.Text == "Connect")
                {
                    if (frame[3] == 211)
                        txtListBox.Items.Add("connected...");
                    else if (frame[3] == 218) // khung Z
                        txtListBox.Items.Add("End of file...");
                    else
                        txtListBox.Items.Add("...Data block " + sothutu.ToString() + " sent");
                    sothutu += 1; // tăng số thứ tự theo dõi quá trình truyền khung
                }
            }
}
*** lưu ý các biến flag, sothutu, int_frame phải khai báo biến toàn cục​
Việc để sử dụng ô listbox rất dễ, cú pháp như sau :​
txtListBox.Items.Add("chuỗi cần hiện lên listbox");

dỉ nhiên bạn phải tao ô listbox rồi đặt tên cho nó, ở đây minh đặt tên là txtListBox. Sang phần sau mình sẽ trình bày tiếp phần cuối cùng là nhận dữ liệu từ COM và sửa lỗi và sẽ kết thúc bên trạm phát. bên trạm thu mình sẽ nói những cái gì khác bên phát còn giống thì mình sẽ không nói lại nữa :D để kết thúc project nhanh chóng còn bàn luận chỉnh sửa code, vì code của mình chưa có tối ưu, còn khuyết điểm nhiều lắm :1cool_byebye:
P/s : cái hàm delay_ms(x) mình cũng chỉ copy từ mạng, cũng chưa hiểu cái code của nó nữa, tạm xài đỡ =)).​
Code:
private void timer2_Tick(object sender, EventArgs e)
{
            x++;
            if (x > 65000) x = 0;
}
private void delay_ms(int delayms)
{
            x = 0;
            do
            {
                Application.DoEvents();
            }
            while (x < (delayms / 100));
            Application.DoEvents();
}
cái này cần tạo thêm 1 cái timer trong form và một cái biến x toàn cục :doc

continue ... :5cool_sweat:
 

nguyenquoctrung-hhk

Thành Viên PIF
phần 2 : Coding - Trạm phát (continue)
tiếp nối phần trên, mình sẽ trình bày cách sửa sai, đây là phần giải thuật hơi khó một tí chứ không phải là khó lắm :D. Trong truyền thông, để đánh giá tốt hệ thống ngoài mặt đường truyền dẫn, sửa lỗi là 1 phần quyết định chất lượng hệ thống, hệ thống nếu sửa được càng nhiều lỗi thì giá thành của đường truyền sẽ giảm xuống, tiết kiệm được 1 khoảng chi phí, còn nếu hệ thống sửa lỗi kém thì sẽ nâng mức giá thành đường truyền, và phạm vi đường truyền sẽ thu hẹp lại, gia tăng các trạm tiếp sức. Trước khi vào thì mình sẽ nhắc tới cổng COM lần nữa :D vì đây chính là sự khác biệt giữa trạm phát và trạm thu :​
trạm phát : ReceivedByte Threadsold = 7​
trạm thu : ReceivedByte Threadsold = 26​
Các thông số trên có được là mình căn cứ vào độ dài khung truyền đi. Đối với trạm phát mỗi lần truyền đi 1 khung là 26 byte do đó bên trạm thu phải đợi đúng 26 byte mới ngắt, nếu ngắt sớm quá thì sẽ bị thiếu dữ liệu.​
Tương tự đối với trạm thu thì nó truyền 1 khung chỉ có 7 byte thôi vì cái phần DATA nó chỉ có mỗi cái ký tự ACK hoặc NAK và cộng thêm các phần khác trong cấu trúc khung kermit để thành 7 byte.​
I. lấy data từ cổng COM dùng ngắt OnCom
ở cái phần này thì anh Lee cũng nói qua, mình chỉ thêm vào là cách đọc 1 mảng byte từ COM, tương tự như việc ghi vào COM thì cú pháp đọc 1 mảng byte từ COM :​
Code:
byte[] ReadBuffer = new byte[Com.BytesToRead];
Com.Read(ReadBuffer, 0, ReadBuffer.Length); // đọc data từ cổng COM
đầu tiên sẽ khai báo một mảng byte, thì số phần tử của nó là Com.BytesToRead có nghĩa là đọc toàn bộ byte có trong buffer vì số byte cũng chính là số phần tử.​
sau đó ta sẽ dùng lệnh : Com.Read(ReadBuffer, 0, ReadBuffer.Length) nó tương tự như lệnh ghi vào COM ta chỉ việc thay đổi Write thành Read. sau cái lệnh này thì dữ liệu đã được ghi vào trong mảng ReadBuffer và mình sẽ xử lý cái mảng này =))
Như vậy mình sẽ có 1 cái method để xử lý data thu được. sau khi xử lý xong nó sẽ tách cái phần DATA trong cái khung truyền, nhiệm vụ sau đó sẽ biến đổi DATA này từ String thành mảng byte kiểm tra xem nó có phải là ACK hay NAK để mình thay đổi cờ flag :​

Code:
private void OnCom(object sender, SerialDataReceivedEventArgs e)
{
            string data;
            byte ACK = 6;
            byte[] ReadBuffer = new byte[Com.BytesToRead];
            Com.Read(ReadBuffer, 0, ReadBuffer.Length); // đọc data từ cổng COM
            data = xuly_data(ReadBuffer);
            if (StrToByteArray(data)[0] == ACK)
                flag = "ACK";
            else
                flag = "NAK";
}
ở đâu mình cho cái biến ACK = 6 vì mã ASCII của nó là 6​
K. xử lý data
giải thuật :​
_ tạo một mảng 2 chiều, tách từng bít trong khung và gán tuần tự vào mảng​
_ kiểm tra hàng xem từng hàng có đúng là parity lẻ không, nếu sai 1 hàng thì thì lưu giá trị của hàng đó vào 1 cái biến . Nếu sai 2 hàng thì return kí tự null​
_ kiểm tra cột xem từng cột có đúng là parity chẵn không, nếu sai 1 cột thì lưu vào trong một cái biến, còn nếu sai 2 cột thì return kí tự null​
_ kiểm tra xem có lỗi sai không, nếu có thì sửa vị trí sai theo công thức sau :​
frame[row] ^ (byte)(Math.Pow(2, column))​

frame : là 1 mảng​
row : vị trí hàng sai​
column : vị trí cột sai​
*** ở đây ta sử dụng hàm toán học pow trong lớp Math​
_ sau khi sữa lỗi xong, tạo một cái mảng byte tạm, để lưu cái phần DATA sau khi tách nó ra và chuyển nó về khiểu chuỗi dùng method biến đổi mảng byte thành kiểu string​
_ trả về giá trị là một chuỗi​

Code:
public string xuly_data(byte[] frame)
{
            byte[] temp = new byte[frame.Length - 6];
            byte[,] check_data = new byte[frame.Length - 2, 8];
            int phan_du, dem = 0, row = 0, column = 0, error = 0, sum = 0, a;
            string data;
 
            ****** tách từng bít lưu vào mảng 2 chiều ******
            for (int i = 1; i < frame.Length - 1; i++)
            {
                a = frame[i];
                do
                {
                    phan_du = a % 2;
                    check_data[i - 1, dem] = (byte)phan_du;
                    dem += 1;
                    a = a / 2;
                }
                while (dem != 8);
                dem = 0;
            }
 
            /***************** kiem tra hang **********************/
            for (int i = 1; i < frame.Length - 1; i++)
            {
                for (int j = 0; j < 8; j++)
                    sum += check_data[i - 1, j];
                if (sum % 2 == 0)
                {
                    row = i - 1;
                    error += 1;
                    if (error == 2)
                        return "";  // trả vể ký tự null
                }
                sum = 0;
            }
            error = 0;
            sum = 0;
            /****************** kiem tra cot ************************/
            for (int i = 0; i < 8; i++)
            {
                for (int j = 1; j < frame.Length - 1; j++)
                    sum += check_data[j - 1, i];
                if (sum % 2 != 0)
                {
                    column = i;
                    error += 1;
                    if (error == 2)
                        return "";
                }
                sum = 0;
            }
            if (error != 0)
                frame[row] = (byte)(frame[row] ^ (byte)(Math.Pow(2, column)));
 
       
 
            /***** tach lay data ******/
            for (int i = 0; i < frame.Length - 6; i++)
                temp[i] = (byte)(frame[i + 4] & 0x7f); // giải mã parity lẻ và gán vào mảng lưu tạm
            data = ByteArrayToStr(temp); //biến đổi về kiểu string
         
            return data;
}
 
        // chuyển kiểu byte thành String
public string ByteArrayToStr(byte[] bytes)
{
            string String = Encoding.ASCII.GetString(bytes);
            return String;
}
tới đây coi như đã xong xuối toàn bộ phần trạm phát, chỉ còn cái đoạn code main là cái nút send, khi ấn vào nó sẽ gửi dữ liệu đi theo đúng tuần tự của kermit protocol, thì phần này sẽ nói ngắn gọn trong phần cuối cùng của trạm phát trươc khi qua trạm thu :1cool_byebye:
P/s : sáng bận rộn quá không có thời gian soạn, tối ngồi soạn đáng lẽ cái nút send code ngắn ủn à, mà bùn ngủ quá, phải thăng :5cool_still_dreaming:
 

nguyenquoctrung-hhk

Thành Viên PIF
phần 2 : Coding - Trạm phát (continue)
trong phần này chỉ là thu gom tất cả những gì đã nói ở phía trên để tích hợp lại thành quá quá trình truyền của kermit protocol.​
ở đây có thêm một cái nút send. cái nút này khi có thông báo trên txtList box bên trạm thu đã chấp nhận kết nối đường truyền thì mình mới gửi đi được. code của nút send như sau :​
_ đọc tên file từ ô txtBrowsFile​
_ đọc toàn bộ nội dung có trong file text​
_ tính số phần của nội dung file text​
_ truyền khung S​
_ truyền khung F​
_ truyền khung D​
_ truyền khung Z​
_ truyền khung B​

Code:
private void PbSend_Click(object sender, EventArgs e)
{
            string FileName = Path.GetFileName(txtBrowsFile.Text);
            string ketnoi, end_frame, end_transf;
            StreamReader sr = new StreamReader(Path.GetFullPath(txtBrowsFile.Text));
            string data = "";
            // đọc data trong file cần truyền
            data = sr.ReadToEnd(); // đọc toàn bộ nội dung file text
            sr.Close();
   
            ***** tính số phần của nội dung file text
            int mod_frame = data.Length % 20;
 
            int_frame = data.Length / 20;//20 = số ký tự truyền trong từng frame
 
            if(mod_frame != 0)
                int_frame = int_frame + 1;
   
            ***** truyền khung S ******
            ketnoi = Dong_Goi("", "S", 0);
            Start(ketnoi);
 
            ****** truyền khung F ****
            FileName = Dong_Goi(FileName, "F", 1);
            Start(FileName);
 
            **** truyền khung D *****
            Tach_chuoi(data);
 
            ***** truyền khung Z *****
            if (int_frame % 2 == 0)
            {
                end_frame = Dong_Goi("", "Z", 0);
                Start(end_frame);
            }
            else
            {
                end_frame = Dong_Goi("", "Z", 1);
                Start(end_frame);
 
            }
 
 
            //truyen khung B
 
            if (int_frame % 2 == 0)
            {
                end_transf = Dong_Goi("", "B", 1);
                Start(end_transf);
            }
            else
            {
                end_transf = Dong_Goi("", "B", 0);
                Start(end_transf);
            }
}
ở đây có lớp path , lớp này sẽ truy suất các thuộc tính của file : đường dẫn file, tên file, loại file, ... thì ở đây mình sẽ truy suất tên file dùng method GetFileName(source file) :​
Code:
string FileName = Path.GetFileName(txtBrowsFile.Text);
lớp StreamReader thì anh Lee cũng đã giới thiệu ròi, mình sẽ nói thêm một method đọc toàn bộ file là ReadToEnd() với phương thức này sẽ đọc toàn bộ nội dung trong file, nếu mà dùng ReadLine() thì nó chỉ đọc có 1 dòng mà thôi. :D
Tới đây đã kết thúc trạm phát, mình sẽ tiếp tục nói tiếp phần 3 là trạm thu :1cool_byebye:
đây là toàn bộ code trạm phát dùng visual studio 2010 :​
Code:
http://www.mediafire.com/?80789r1nx7fpmbs

P/s : hehe, :D, lúc trước có đọc bài chị phương nói để link không bị die post kèm theo cái thẻ gì em quên mất tiu, thấy có cái QUOTE cứ kẹp đại hong biết đúng không :).
[2death: để trong thẻ code thông thường thôi :) )

trong giao diện có cái phần chọn COM thì mình không nói tới tại anh Lee đã hướng dẫn kỹ trong phần C# :D.
 

luckyboythong

Trứng gà
ý tưởng hay! Bạn tiếp tục phần 3 la trạm phát đi. Mình đang quan tâm về phần này. thank trước nha !:1cool_byebye:
 

nguyenquoctrung-hhk

Thành Viên PIF
sory, vi minh khong go duoc tieng viet co dau. de minh coi lai coded thu, don nha may tinh ngung hoat dong. ps : nho admin xoa muc post nay vao ngay mai
 
Top