Lập trình socket trong C++

Lap Trinh Socket Trong C



Lập trình socket đã trở thành một chủ đề quan trọng trong lĩnh vực mạng máy tính. Nó liên quan đến việc thiết lập kết nối giữa hai nút, máy chủ và máy khách để liên lạc với nhau mà không bị gián đoạn. Máy chủ đóng vai trò là người nghe trong kênh liên lạc và lắng nghe máy khách trên một cổng cụ thể tại địa chỉ IP. Mặt khác, khách hàng đóng vai trò là người giao tiếp trong kênh liên lạc. Máy khách liên hệ với máy chủ để tạo kết nối và liên hệ với máy chủ. Bài viết này nhằm mục đích cung cấp hướng dẫn toàn diện và chi tiết về lập trình socket trong C++, bao gồm những điều cơ bản, trình bày các ví dụ thực tế và cung cấp giải thích chi tiết về mã.

Thiết lập mô hình Client-Server

Lập trình socket là quá trình xây dựng kênh liên lạc giữa máy chủ và máy khách bằng cách sử dụng socket. Trong mã ví dụ sau, máy khách bắt đầu liên hệ với máy chủ và máy chủ được thiết lập để chấp nhận các kết nối máy khách. Hãy để chúng tôi hiểu các phân đoạn mã máy chủ và máy khách bằng cách chứng minh hoạt động cốt lõi của chúng trong giao tiếp mạng. Sau đây là mã phía máy chủ. Chúng ta hãy xem mã trước và sau đó giải thích chi tiết về mã, từng điểm một.

1. Phía máy chủ







Mã cho phía máy chủ của mô hình được đưa ra như sau. Hãy cho chúng tôi xem những gì đang xảy ra trong mã:



#include
#include
#include
#include

sử dụng không gian tên tiêu chuẩn ;

#xác định CỔNG 8080
#define MAX_BUF_SIZE 1024

int chủ yếu ( ) {
int ser_socket, cli_socket ;
cấu trúc sockaddr_in ser_address, cli_address ;
ký tự buf [ MAX_BUF_SIZE ] = { 0 } ;

nếu như ( ( ser_socket = ổ cắm ( AF_INET, SOCK_STREAM, 0 ) ) == - 1 ) {
lỗi lầm ( 'Lỗi khi tạo Ổ cắm' ) ;
lối ra ( EXIT_FAILURE ) ;
}

ser_địa chỉ. gia đình tội lỗi = OF_INET ;
ser_địa chỉ. sin_addr . s_addr = INADDR_ANY ;
ser_địa chỉ. sin_port = hton ( HẢI CẢNG ) ;

nếu như ( trói buộc ( be_socket, ( cấu trúc sockaddr * ) & ser_địa chỉ, kích thước của ( ser_địa chỉ ) ) == - 1 ) {
lỗi lầm ( 'Thất bại trong liên kết' ) ;
lối ra ( EXIT_FAILURE ) ;
}

nếu như ( Nghe ( be_socket, 3 ) == - 1 ) {
lỗi lầm ( 'Không thể nghe được' ) ;
lối ra ( EXIT_FAILURE ) ;
}

cout << 'Máy chủ nghe trên cổng' << HẢI CẢNG << '... \N ' ;

socklen_t cli_address_len = kích thước của ( cli_địa chỉ ) ;
nếu như ( ( cli_socket = chấp nhận ( be_socket, ( cấu trúc sockaddr * ) & cli_address, & cli_address_len ) ) == - 1 ) {
lỗi lầm ( 'Không thể chấp nhận' ) ;
lối ra ( EXIT_FAILURE ) ;
}

đọc ( cli_socket, buf, MAX_BUF_SIZE ) ;
cout << 'Tin nhắn của khách hàng là:' << buf << kết thúc ;

gửi ( cli_socket, 'Tin nhắn của máy chủ' , căng thẳng ( 'Tin nhắn của máy chủ' ) , 0 ) ;

đóng ( cli_socket ) ;
đóng ( ser_socket ) ;

trở lại 0 ;
}

Ví dụ đã cho là mã phía máy chủ của chương trình C++. Mã này hoạt động để một máy chủ TCP đơn giản lắng nghe các kết nối trên một cổng cụ thể. Khi kết nối được tạo thành công, máy chủ sẽ nhận được tin nhắn được gửi từ máy khách. Sau đó, nó in nó ra bàn điều khiển và gửi thông báo phản hồi cho khách hàng. Hãy để chúng tôi hiểu từng dòng mã.



Chương trình bắt đầu bằng việc bao gồm các thư viện: “iostream” cho các định nghĩa đầu vào/đầu ra tiêu chuẩn, “cstring” cho các hàm xử lý chuỗi, “unistd.h” để cung cấp quyền truy cập vào API hệ điều hành POSIX và “arpa/inet.h” cho thực hiện các hoạt động trên mạng. Câu lệnh “#define PORT 8080” có nghĩa là nó xác định số cổng 8080 mà máy chủ sẽ lắng nghe. “#define MAX_BUF_SIZE 1024” có nghĩa là kích thước bộ đệm tối đa cho dữ liệu đến là 1024.





Trong hàm chính, hai biến được khởi tạo, “ser_socket” và “cli_socket”, để đại diện tương ứng cho cả máy chủ và máy khách. Ba biến còn lại là “sockaddr_in”, “ser_address” và “cli_address” thuộc loại “struct” được khởi tạo làm cấu trúc địa chỉ cho máy chủ và máy khách. Sau đó, bộ đệm có tên “buf” được khởi tạo để lưu trữ dữ liệu đến từ máy khách.

Hàm socket() trong điều kiện “if” sẽ tạo một socket TCP mới. AF_INET biểu thị IPv4, SOCK_STREAM đại diện cho ổ cắm TCP hướng kết nối và đáng tin cậy, đối số cuối cùng bằng 0 được đưa ra để chọn giao thức TCP mặc định, INADDR_ANY chấp nhận các kết nối trên bất kỳ địa chỉ IP nào và htons (PORT) chuyển đổi số cổng từ thứ tự byte máy chủ theo thứ tự byte mạng.



Vì mọi thứ đều được xác định chính xác nên bước tiếp theo là thiết lập máy chủ dưới dạng bộ lắng nghe trên cổng nhất định và chấp nhận các kết nối trên bất kỳ giao diện mạng nào. Ổ cắm được cung cấp thông tin trong “ser_address” bằng phương thức bind(). Chúng tôi in lỗi và kết thúc quá trình nếu liên kết không thành công. Hàm Accept() mở một ổ cắm mới cho kết nối với máy khách, trong khi hàm listen() hướng dẫn máy chủ chờ các kết nối đến. Nếu hàm Accept() không thành công, thông báo lỗi sẽ được in ra và hàm sẽ thoát.

Tiếp theo, máy chủ đọc thông báo máy khách có hàm read() vào bộ đệm “buf” rồi in nó ra bàn điều khiển. Hàm send() được máy chủ sử dụng để gửi tin nhắn phản hồi cho máy khách. Cuối cùng, bằng cách sử dụng close(), máy chủ sẽ đóng ổ cắm của máy khách, chấm dứt chương trình để tất cả các kết nối được đóng đúng cách và không có khả năng xảy ra vi phạm dữ liệu.

2. Phía khách hàng

Bây giờ, hãy xem điều gì xảy ra trong mô hình máy khách:

#include
#include
#include
#include

#xác định CỔNG 8080
#define SERVER_IP '127.0.0.1'

int chủ yếu ( ) {
int cli_socket ;
cấu trúc sockaddr_in ser_address ;
hằng số ký tự * tin nhắn = 'Khách hàng đang gửi lời chào!' ;

nếu như ( ( cli_socket = ổ cắm ( AF_INET, SOCK_STREAM, 0 ) ) == - 1 ) {
lỗi lầm ( 'Lỗi khi tạo ổ cắm' ) ;
lối ra ( EXIT_FAILURE ) ;
}

ser_địa chỉ. gia đình tội lỗi = OF_INET ;
ser_địa chỉ. sin_port = hton ( HẢI CẢNG ) ;

nếu như ( inet_pton ( AF_INET, SERVER_IP, & ser_địa chỉ. sin_addr ) <= 0 ) {
lỗi lầm ( 'Sai địa chỉ' ) ;
lối ra ( EXIT_FAILURE ) ;
}

nếu như ( kết nối ( cli_socket, ( cấu trúc sockaddr * ) & ser_địa chỉ, kích thước của ( ser_địa chỉ ) ) == - 1 ) {
lỗi lầm ( 'Kết nối thất bại' ) ;
lối ra ( EXIT_FAILURE ) ;
}
gửi ( cli_socket, tin nhắn, căng thẳng ( tin nhắn ) , 0 ) ;

ký tự buf [ 1024 ] = { 0 } ;
đọc ( cli_socket, buf, kích thước của ( buf ) ) ;
tiêu chuẩn :: cout << 'Phản hồi của máy chủ:' << buf << tiêu chuẩn :: kết thúc ;

đóng ( cli_socket ) ;
trở lại 0 ;
}

Chúng ta hãy xem từng dòng mã để hiểu chương trình hoạt động như thế nào.

Bốn thư viện tương tự – iostream, cstring, unistd.h và arpa/inet.h – cũng được đưa vào phía máy khách. Số cổng cũng được xác định cùng với địa chỉ IP của máy chủ cục bộ 127.0.0.1. Thông báo phải được gửi đến máy chủ sẽ được đưa ra. Máy khách và máy chủ cần thiết lập kết nối theo bước sau:

“if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1);” tạo một ổ cắm cho IPv4 với loại luồng và giao thức mặc định TCP. perror() in chi tiết lỗi nếu hàm socket() không thiết lập được kết nối và thoát khỏi chương trình.

“server_address.sin_port = htons(PORT);” đặt số cổng sau khi chuyển đổi sang thứ tự byte mạng. Sau đó, một thông báo lỗi khác là “Địa chỉ sai” được đưa ra ở đây và được in nếu có gì đó không đúng với địa chỉ. Bằng cách định vị địa chỉ trong “ser_address”, máy khách sẽ kết nối với máy chủ. Nếu kết nối không thành công, chi tiết lỗi sẽ được in. Hàm send() sẽ chuyển tin nhắn đến máy chủ, đảm bảo rằng nó không chứa bất kỳ cờ nào.

Để nhận và lưu trữ phản hồi từ máy chủ, bộ đệm có tên “buf” thuộc loại “char” được khởi tạo. Hàm read() đọc phản hồi của máy chủ vào bộ đệm. Cuối cùng, phản hồi của máy chủ được in ra bàn điều khiển. Cuối cùng, kết nối được đóng bằng câu lệnh close() để kết thúc socket. Sau đây là đầu ra của chương trình:

Phần kết luận

Lập trình socket là một phần quan trọng của giao tiếp mạng trong khoa học máy tính. Nó cho phép phát triển các ứng dụng có thể giao tiếp qua mạng, cho phép thực hiện nhiều khả năng từ kiến ​​trúc máy khách-máy chủ đơn giản đến các hệ thống phân tán có cấu trúc. Khi một ổ cắm được tạo trong ngữ cảnh lập trình, chương trình phải định cấu hình các đặc điểm điểm cuối của nó như các giao thức, TCP hoặc UDP và địa chỉ mạng như địa chỉ IP và số cổng. Các ổ cắm này cho phép máy chủ gửi và nhận dữ liệu. Bài viết này trình bày một ví dụ thực tế về cách hoạt động của mô hình client-server trong lập trình socket.