Saturday, May 23, 2020

Socket là gì?

1. SOCKET LÀ GÌ:
Socket là điểm cuối end-point trong liên kết truyền thông hai chiều (two-way communication) biểu diễn kết nối giữa Client – Server. Các lớp Socket được ràng buộc với một cổng port (thể hiện là một con số cụ thể) để các tầng TCP (TCP Layer) có thể định danh ứng dụng mà dữ liệu sẽ được gửi tới. Ứng dụng thực tiễn của Socket là gì?
Socket hoạt động thông qua các tầng TCP hoặc TCP Layer định danh ứng dụng, từ đó truyền dữ liệu thông qua sự ràng buộc với một cổng port
Socket là gì? Có thể sử dụng cùng lúc nhiều socket liên tục để tiết kiệm thời gian cũng như nâng cao năng suất làm việc
Socket là giao diện lập trình ứng dụng mạng được dùng để truyền và nhận dữ liệu trên internet. Giữa hai chương trình chạy trên mạng cần có một liên kết giao tiếp hai chiều, hay còn gọi là two-way communication để kết nối 2 process trò chuyện với nhau. Điểm cuối (endpoint) của liên kết này được gọi là socket.
Một chức năng khác của socket là giúp các tầng TCP hoặc TCP Layer định danh ứng dụng mà dữ liệu sẽ được gửi tới thông qua sự ràng buộc với một cổng port (thể hiện là một con số cụ thể), từ đó tiến hành kết nối giữa client và server.
2. CÁC HOẠT ĐỘNG CỦA SOCKET
Có 2 loại socket là TCP và UDP. cách hoạt động của chúng cũng khác nhau.
Socket là gì? Chức năng của socket là kết nối giữa client và server thông qua TCP/IP và UDP để truyền và nhận giữ liệu qua Internet
***Nhận xét: TCP cần giữ connection trong quá trình truyền/nhận dữ liêu. Và khi nhận được dữ liệu nó sẽ kiểm tra và có trả lời do đó đảm bảo được thính toàn vẹn của dữ liệu. nếu dữ liệu nhận được sai thì nó yêu cầu resend. Ngược lại, UDP thì không yêu cầu giữ connection, và khi gửi nhận dữ liệu nó cũng không kiểm tra dữ liệu. ==> do đó tốc độ truyền nhận dữ liệu ở UDP nhanh hơn TCP rất nhiều nhưng chúng ta không thể "make sure" dữ liệu nhận được là đúng.

Chức năng của socket là kết nối giữa client và server thông qua TCP/IP và UDP để truyền và nhận giữ liệu qua Internet. Đương nhiên để truyền và nhận thì chúng ta cần địa chỉ của nơi nhận. Vì vậy, IP address và số Port được chọn làm địa chỉ truyền nhận trong socket
Chúng ta có thể kiểm tra ip của tắt cả các network interface trong máy tinh bằng lệnh "ipconfig" đối với windows và "ifconfig" đối với linux hoặc MacOS
Không những truyền dữ liệu qua network, chúng ta cũng có thể truyền dữ liệu trong local máy tính của mình. Lúc đó ip sẽ của server và client sẽ giống nhau hoặc mặc định la "127.0.0.1" và port là khác nhau.
*** Chú ý: trong lúc mở port, thì có một số port chúng ta không thể mở được, vì có thể có một chương trình nào đó đang sử dụng port đó, nên chúng ta sẽ thử với port khác.

3. TCP SOCKET (STREAM SOCKET)
Stream Socket là gì? Nó chỉ hoạt động khi server và client đã kết nối với nhau


Sunday, May 17, 2020

HÀM TẠO SAO CHÉP (COPY CONTSTRUCTOR)

1. HÀM TẠO SAO CHÉP MẶC ĐỊNH.
   Giả sử đã định nghĩa một lớp PS (phân số):
class PS
{
private:
int tu;
int mau;
public:
PS()
{
tu = 0;
mau = 0;
}
PS(int t, int m)
{
tu = t;
mau = m;
}
};
    Ta có thể dùng câu lệnh khia báo để tạo một đối tượng từ một đối tượng mới
PS x(1, 2);
     Ta cũng có thể dùng lệnh khai báo để tạo một đối tượng mới từ đối tượng đã tồn tại.
PS y(x);
   Khi đó hàm này sẽ được sao chéo nội dung theo từng bit của x tới vùng nhớ của y. ==> Vùng nhớ của x và y có cũng giá trị. ==> các thuộc tính và phương thức của x và y đều có giá trị như nhau.

#include <stdio.h>
class PS
{
public:
int tu;
int mau;

PS(int t, int m)
{
tu = t;
mau = m;
}
};
void main()
{
PS x(1, 2);
PS y(x);
printf("X: tu = %d, mau = %d\r\n", x.tu, x.mau);
printf("Y: tu = %d, mau = %d\r\n", y.tu, y.mau);
y.tu = 2; y.mau = 3;
printf("X: tu = %d, mau = %d\r\n", x.tu, x.mau);
printf("Y: tu = %d, mau = %d\r\n", y.tu, y.mau);
}

khi chạy chương trình ta được kết quả:
X: tu = 1, mau = 2
Y: tu = 1, mau = 2
X: tu = 1, mau = 2
Y: tu = 2, mau = 3
*** nhận xét: Khi ban đầu khởi tạo thì giá trị tử mẫu của y bằng x (vì y được tạo ra từ x). Nhưng khi ta thay đổi giá trị tử mẫu của y, thì không làm thay đổi giá trị của x. Vì  lúc này x.tu, x.mau và y.tu, y.mau ở 2 vùng nhớ khác nhau.

Ta tiếp tục xét ví dụ ở class PS được xây dựng như sao.
#include <stdio.h>
#include <malloc.h>
class PS
{
public:
int *tu;
int *mau;
PS(int t, int m)
{
tu = (int*)malloc(4);
mau = (int*)malloc(4);
*tu = t;
*mau = m;
}
};
void main()
{
PS x(1, 2);
PS y(x);
printf("X: tu = %d, mau = %d\r\n", *x.tu, *x.mau);
printf("Y: tu = %d, mau = %d\r\n", *y.tu, *y.mau);
*y.tu = 2; *y.mau = 3;
printf("X: tu = %d, mau = %d\r\n", *x.tu, *x.mau);
printf("Y: tu = %d, mau = %d\r\n", *y.tu, *y.mau);
}
 Ta nhận được kết quả:
X: tu = 1, mau = 2
Y: tu = 1, mau = 2
X: tu = 2, mau = 3
Y: tu = 2, mau = 3
*** Nhận xét: 
          Khi ban đầu khởi tạo thì giá trị tử mẫu của y bằng x (vì y được tạo ra từ x). Khi ta thay đổi giá trị tử mẫu của y, thì giá trị tử mẫu của x cũng thay đổi theo. Vì  lúc này x.tu, x.mau và y.tu, y.mau là những con trỏ( một biến chứa địa chỉ) và điều trỏ tới một địa chỉ giống nhau.
==> Vì thế trường hợp cần sao chép giá trị của các biến con trỏ thì người ta cần phải xây dụng hàm tạo sao chép (copy constructor) mới chứ không dùng hàm mặc định của C++

CÁC HÀM TRỰC TUYẾN (INLINE)

1. ƯU NHƯỢC ĐIỂM CỦA HÀM.
- Việc tổ chức chương trình thanh các hàm có 2 ưu điểm rỏ rệt:
       + Thứ nhất: là chia chương trình thành các đơn vị đọc lập, làm cho chương trình được tổ chức một các khoa học, dễ kiểm soát, dễ phát hiện lỗi, dễ phát triển, mở rộng.
       + Thứ hai: giảm được kích thước chương trình, vì mỗi đoạn chương trình thực hiện nhiệm vụ của hàm được thay bằng một lời mời gọi hàm.

- Tuy nhiên hàm cũng có một số nhược điểm làm chậm tốc độ chương trình do:
        + Cấp phát vùng nhớ cho các đối số và biến cục bộ.
        + Truyền dữ liệu của các tham số cho các đối.
        + Giải phóng vùng nhớ khi thực hiện xong hàm.

==> Các hàm trực tuyến (inline) trong C++ ra đời để khác phục các nhược điểm nói trên.

2. CÁCH BIÊN DỊCH HÀM TRỰC TUYẾN
 - Chương trình dịch xử lý các hàm imline như các macro ( được định nghĩa bằng #define), nghĩa là nó sẽ thay trược tiếp mỗi lời gọi hàm bằng đoạn chương trình của hàm được khai báo inline. Cách này làm cho chương trình dài ra từ đó giúp tốc độ chương trình được tăng lên, do không cần phải thực hiện các bước như truyền dữ liệu vào đôi số....

Tuesday, May 5, 2020

Phân loại các vùng nhớ (stack & heap ...)

Sau khi tìm hiểu một số khái niệm cơ bản về con trỏ, cấp phát động, ... chúng ta đã thấy được dãy địa chỉ bộ nhớ ảo được chia thành nhiều phân vùng khác nhau và được sử dụng cho những mục đích khác nhau. Trong bài học này, mình sẽ cùng các bạn tổng hợp lại chức năng của một số phân vùng trên bộ nhớ ảo.
Dưới đây là hình ảnh minh họa cho thứ tự các phân vùng trên bộ nhớ ảo:
Code segment
Code segment (text segment) là nơi mà lưu trữ các mã lệnh đã được biên dịch của các chương trình máy tính. Những mã lệnh trong phân vùng này sẽ được chuyển đến CPU xử lý khi cần thiết. Code segment chỉ chịu sự chi phối của hệ điều hành, các tác nhân khác không thể can thiệp trực tiếp đến phân vùng này. Việc đưa các mã lệnh đã được biên dịch của chương trình lên phân vùng code segment là công việc đầu tiên mà hệ điều hành cần làm khi chúng ta chạy chương trình.
Data segment
Data segment (initialized data segment) là phân vùng mà hệ điều hành sử dụng để khởi tạo giá trị cho các biến kiểu static, biến toàn cục (global variable) của các chương trình.
BSS segment
BSS segment (uninitialized data segment) cũng được dùng để lưu trữ các biến kiểu static, biến toàn cục (global variable) nhưng chưa được khởi tạo giá trị cụ thể.
Heap segment
Heap segment (free srote segment) được sử dụng để cấp phát bộ nhớ thông qua kỹ thuật Dynamic memory allocation.
Để sử dụng kỹ thuật cấp phát bộ nhớ động, ngôn ngữ C++ đã hổ trợ sẵn cho chúng ta toán tử new. Ví dụ:
new int; //allocate 4 bytes on Heap segment
new int[10]; //allocate (4 * 10) bytes on Heap segment
Toán tử new sau khi thực thi thành công sẽ trả về địa chỉ của vùng nhớ được cấp phát trên heap, chúng ta có thể sử dụng con trỏ có kiểu dữ liệu phù hợp để lưu trữ địa chỉ trả về này, và con trỏ cũng là công cụ duy nhất giúp chúng ta có thể xác định được vị trí vùng nhớ được cấp phát là ở đâu, và cũng thông qua con trỏ để chúng ta có thể giải phóng vùng nhớ đã được cấp phát.
int *pInt = new int;
int *pArr = new int[10];
Chúng ta không cần biết rõ cơ chế quản lý bộ nhớ Heap như thế nào, mà chỉ cần biết rằng bộ nhớ được cấp phát trên Heap sẽ không tự giải phóng cho đến khi nào toàn bộ chương trình đang chạy kết thúc. Do đó, nếu chương trình có thời gian chạy quá lâu mà không được giải phóng các vùng nhớ một cách hợp lý, điều này sẽ làm ảnh hưởng đến việc cấp phát bộ nhớ động cho các chương trình khác.
Mình có thể kể ra một số ưu điểm và nhược điểm đáng chú ý khi sử dụng phân vùng Heap như sau:
  • Việc cấp phát bộ nhớ trên Heap chậm hơn các phân vùng khác.
  • Vùng nhớ đã được cấp phát sẽ vẫn thuộc quyền kiểm soát của chương trình đang chạy cho đến khi chúng được giải phóng, hoặc nhận được tín hiệu kết thúc chương trình.
  • Vùng nhớ được cấp phát phải được quản lý bởi ít nhất 1 con trỏ.
  • Toán tử dereference truy xuất đến vùng nhớ chậm hơn các biến thông thường.
  • Phân vùng Heap có dung lượng lớn nhất, nên chúng ta có thể sử dụng một cách thoải mái hơn các phân vùng khác.
Stack segment
Call Stack (thường được gọi là Stack) được dùng để cấp phát bộ nhớ cho tham số của các hàm (function parameters) và biến cục bộ (local variables). Call Stack được thực hiện theo cấu trúc dữ liệu stack, do đó, trước khi nói về phân vùng Stack trên bộ nhớ ảo mình sẽ trình bày cho các bạn về cấu trúc dữ liệu stack trước.
Stack data structure
Stack là một cơ chế tổ chức dữ liệu. Các bạn cũng từng làm việc với một kiểu tổ chức dữ liệu khá phổ biến là mảng một chiều. Mỗi cấu trúc dữ liệu sẽ tổ chức dữ liệu dưới một cơ chế khác nhau để sử dụng hiệu quả trong từng công việc cụ thể. Bây giờ chúng ta xem xét cấu trúc dữ liệu stack.
Dưới đây là một hình ảnh minh họa cho một stack trong đời sống hằng ngày:
Những đĩa CD này được đặt chồng lên nhau. Khi nhìn vào chồng đĩa CD này, chúng ta chỉ có thể thực hiện 3 công việc:
(1) Nhìn vào đĩa CD trên cùng của chồng đĩa.
(2) Lấy ra một đĩa CD nằm trên cùng.
(3) Đặt thêm một đĩa CD lên trên cùng của chồng đĩa.
Do đó, chúng ta có thể nhận thấy ngay việc tổ chức dữ liệu theo cơ chế stack gặp nhiều hạn chế hơn so với tổ chức dữ liệu theo mảng một chiều.
Khi sử dụng mảng một chiều, chúng ta có thể truy cập vào bất kì phần tử nào bên trong mảng bằng cách đưa ra chỉ số của phần tử. Nhưng đối với stack thì không được. Chúng ta chỉ có thể thao tác với phần tử nằm trên cùng (ngoài cùng). Chúng ta thường nói stack hoạt động theo cơ chế "Last-in, first-out". Có nghĩa là phần tử nào được thêm vào mảng sau cùng thì sẽ được lấy ra đầu tiên.
Ví dụ:
Stack ban đầu của chúng ta là
 --------------------------------
|   4   7   2   5 
 --------------------------------
Thêm vào phần tử có giá trị là 3
 --------------------------------
|   4   7   2   5   3
 --------------------------------
Thêm vào phần tử có giá trị 9
 --------------------------------
|   4   7   2   5   3   9
 --------------------------------
Lấy một phần tử ra khỏi stack
 --------------------------------
|   4   7   2   5   3
 --------------------------------
Call Stack segment
Call stack segment cũng hoạt động dựa trên cơ chế tổ chức dữ liệu như stack. Khi bắt gặp một dòng lệnh khai báo biến, nếu biến đó là biến cục bộ hoặc tham số hàm, nó sẽ được cấp phát tại địa chỉ lớn nhất hiện tại trên Stack. Khi một biến cục bộ hoặc tham số của hàm ra khỏi phạm vi khối lệnh, nó sẽ được đưa ra khỏi Stack.
Để kiểm chứng điều này, các bạn có thể chạy thử đoạn chương trình sau:
int main()
{
 int n1, n2, n3, n4, n5;
 
 cout << "Address of " << &n1 << endl;
 cout << "Address of " << &n2 << endl;
 cout << "Address of " << &n3 << endl;
 cout << "Address of " << &n4 << endl;
 cout << "Address of " << &n5 << endl;
 
 return 0;
}
Đoạn chương trình này khai báo lần lượt 5 biến cục bộ liên tiếp nhau. Nếu trong trường hợp tại thời điểm khai báo, chỉ có chương trình này được CPU xử lý, chúng ta sẽ thấy địa chỉ của 5 biến cục bộ này có địa chỉ liên tiếp nhau.
Địa chỉ sau cách địa chỉ trước đó đúng bằng kích thước của kiểu dữ liệu int.
Như vậy, lần lượt biến n1 n2 n3 n4 và n5 được cấp phát tại những địa chỉ tiếp theo (từ thấp đến cao) trên phân vùng Stack, và khi ra khỏi hàm main, lần lượt biến n5 n4 n3 n2 và n1 sẽ bị đưa ra khỏi Stack.
Stack overflow
Phân vùng Stack có kích thước khá hạn chế. Trên hệ điều hành Windows mà mình đang sử dụng, Call Stack chỉ có kích thước khoảng 1MB. Nếu chúng ta cố gắng cho chương trình cấp phát vùng nhớ trên Stack vượt quá kích thước của Stack, chúng ta gọi đó là hiện tượng tràn bộ nhớ phân vùng Stack (Stack overflow).
Một số ưu và nhược điểm có thể nhận thấy khi sử dụng phân vùng Stack
  • Việc cấp phát bộ nhớ trên Call Stack khá nhanh.
  • Nhìn vào mã nguồn chương trình, chúng ta có thể biết được thời điểm cấp phát và hủy vùng nhớ của biến trên Stack.
  • Kích thước vùng nhớ cấp phát trên phân vùng Stack phải được khai báo rõ ràng trước khi biên dịch.
  • Vùng nhớ trên phân vùng Stack có thể được truy cập trực tiếp thông qua định danh.
  • Kích thước của phân vùng Stack khá hạn chế.

Tổng kết

Trong bài học này, chúng ta đã cùng tìm hiểu qua một số phân vùng bộ nhớ trên dãy địa chỉ bộ nhớ ảo. Còn một phân vùng nữa thuộc vùng dịa chỉ nhỏ nhất, đứng trước Code segment là phân vùng dành cho hệ điều hành. Vì hệ điều hành cũng là một chương trình (nhưng thuộc về hệ thống) nên nó cũng cần được load lên bộ nhớ ảo như những chương trình thông thường. Điều đặc biệt là phân vùng này ngăn chặn mọi hành vi truy cập từ phía người dùng, do đó mình không đề cập đến trong bài học này.

Thư viện string.h trong C

Thư viện string.h trong C

Đây là thư viện cung cấp rất nhiều hàm hữu ích giúp các bạn thuận tiện để làm việc với chuỗi. Để sử dụng các đoạn code trong bài viết này, bạn vui lòng thêm thư viện sau:
Ngay sau đây, chúng ta sẽ cùng làm quen với các hàm trong thư viện string.h được sử dụng phổ biến nhé.

Các hàm trong thư viện string.h

Mình sẽ đi qua từng hàm, và mỗi hàm đều sẽ có những ví dụ cụ thể cho bạn.

Hàm strlen – hàm lấy chiều dài chuỗi

Vai trò: Hàm strlen trong thư viện string.h cung cấp cho bạn độ dài của chuỗi mà nó đang lưu.
Ví dụ:
Lưu ý:
  • Chúng ta cần lấy strlen(s) - 1 là do hàm fgets() đọc cả ký tự ‘\n’. Nếu bạn dùng hàm gets() thì không cần bớt đi 1 đơn vị. Nhưng chúng ta không nên dùng hàm gets() -> lý do ở đây.

Hàm strcmp – hàm so sánh 2 chuỗi

Trong ngôn ngữ C, bạn không thể dùng dấu == để so sánh 2 chuỗi. Lý do là bởi khi bạn truy xuất tên chuỗi thì thực tế là bạn đang truy xuất tới địa chỉ của nó chứ không phải giá trị. Xem ví dụ sau:
Kết quả chạy:
Bạn thấy đấy, đây là 2 địa chỉ khác nhau. Do đó, nếu bạn dùng == để so sánh thì bạn đang so sánh 2 địa chỉ chứ không phải cái bạn mong muốn đâu. Đó là lý do chúng ta cần hàm strcmp().
Giá trị trả về của hàm này bạn xem trong bảng dưới đây:
Giá trị trả vềGiải thích
một số nguyên < 0Khi ký tự đầu tiên của 2 chuỗi không giống nhau và ký tự này ở chuỗi str1 có giá trị nhỏ hơn ở chuỗi str2
giá trị 0hai chuỗi giống nhau
một số nguyên > 0Khi ký tự đầu tiên của 2 chuỗi không giống nhau và ký tự này ở chuỗi str1 có giá trị lớn hơn ở chuỗi str2
Xem ví dụ sau đây:
Kết quả chạy:

Hàm strcat – hàm nối chuỗi

Vai trò: Ghép chuỗi chuỗi nguồn vào phía sau chuỗi đích.
Kết quả chạy:

Hàm strcpy – hàm copy chuỗi

Vai trò: Copy giá trị của chuỗi nguồn và lưu vào chuỗi đích. Bạn cần dùng hàm này khi muốn gán giá trị của chuỗi này cho chuỗi khác thay vì sử dụng dấu = nhé.
Kết quả chạy:

Hàm strlwr – Đưa chuỗi về dạng lowercase

Xem ví dụ sau đây:
Kết quả:

Hàm strupr – đưa chuỗi về dạng uppercase

Xem ví dụ sau:
Kết quả:

Hàm strrev – hàm đảo ngược chuỗi

Công dụng: Đảo ngược chuỗi trong C
Kết quả:

Hàm strchr – Trả về vị trí đầu tiên của ký tự cần tìm

Hàm này trả về con trỏ trỏ tới vị trí xuất hiện đầu tiên của ký tự c. Còn hàm thì trả về vị trí cuối cùng. Nếu không tồn tại, trả về con trỏ NULL.
Kết quả:

Hàm strstr – hàm tìm chuỗi con trong chuỗi

Hàm này tìm kiếm sự xuất hiện của chuỗi con sub trong chuỗi str. Nếu không tìm thấy thì trả về con trỏ NULL. Nếu tìm thấy thì trả về con trỏ trỏ tới vị trí tìm thấy.
Kết quả chạy:
Tại sao lại là “khong kho”? Bởi vì chữ “khong” có chữ “kho”.
Trên đây là các hàm trong thư viện string.h sử dụng nhiều. Bạn có thể xem đầy đủ các hàm của thư viện string.h ở link trong tài liệu tham khảo.