Lập trình socket, giao tiếp client và server bằng ngôn ngữ C (với giao thức TCP)

Bạn đã bao giờ tự hỏi các chương trình mà bạn viết làm thế nào để hoạt động và giao tiếp trong mạng máy tính chưa? Hoặc làm thế nào để server của bạn có thể trò chuyện với client một cách hiệu quả?

Lập trình mạng là một trong những nhiệm vụ cơ bản cần thiết để phát triển các ứng dụng. Một chương trình mạng được viết để cho phép các chương trình trên các máy tính khác nhau truyền thông với nhau một cách hiệu quả và an toàn, cho dù chúng được cài đặt trên mạng LAN, WAN hoặc mạng Internet toàn cầu. Điều này là cực kỳ quan trọng để đảm bảo sự thành công của rất nhiều hệ thống.

Vậy bạn đã sẵn sàng cùng khám phá và thực hành viết 1 chương trình socket giao tiếp client và server đơn giản nhất?

Giới thiệu về Socket

Socket là một giao diện lập trình ứng dụng mạng (API – Application Program Interface) cho phép chúng ta lập trình điều khiển việc truyền thông giữa 2 máy sử dụng các giao thức mức thấp như TCP, UDP. Bạn có thể tưởng tượng socket như một thiết bị truyền thông 2 chiều tương tự như một tệp tin, cho phép gửi và nhận dữ liệu giữa 2 máy tính, tương tự như việc đọc và ghi trên tệp tin.

Với mô hình khách-chủ TCP

Mô hình khách chủ TCP

Bây giờ chúng ta sẽ cùng tìm hiểu cách làm và thực hành.

Viết chương trình phía server

Dưới đây là các bước để tạo lên 1 chương trình phía server:

  1. Tạo socket với hàm socket(int family, int type, int protocol). Các tham số trong đó lần lượt là họ giao thức, kiểu socket và kiểu giao thức.
  2. Gán địa chỉ cho socket với hàm bind(int sockfd, const struct sockaddr *sockaddr, socklen_t addrlen). Các tham số lần lượt là mô tả socket vừa tạo, con trỏ chỉ đến địa chỉ socket và độ lớn địa chỉ.
  3. Chỉ định socket lắng nghe kết nối với hàm listen(int sockfd, int backlog). Trong đó, sockfd là mô tả socket vừa tạo và backlog là số lượng tối đa các kết nối đang chờ.
  4. Chờ/chấp nhận kết nối với hàm accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen). Các tham số lần lượt là mô tả socket vừa tạo, con trỏ tới cấu trúc địa chỉ socket của tiến trình kết nối đến và độ lớn cấu trúc địa chỉ.
  5. Thiết lập kết nối với máy chủ TCP bằng hàm connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen).

Viết chương trình phía client

Dưới đây là các bước để tạo lên 1 chương trình phía client:

  1. Tạo socket với hàm socket(int family, int type, int protocol). Các tham số trong đó lần lượt là họ giao thức, kiểu socket và kiểu giao thức.
  2. Kết nối tới địa chỉ server với hàm connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)).
  3. Đọc dữ liệu từ server và ghi vào biến buffer với read(sock, buffer, 1024).

Sau đây, mình sẽ thực hành với một requirement đơn giản:

Mô tả chương trình:

  • Server mở cổng kết nối 8080.
  • Client nhập địa chỉ IP để kết nối với server qua cổng 8080.
  • Server hiển thị địa chỉ IP và cổng của client.
  • Người dùng nhập chuỗi kí tự bất kỳ từ bàn phím.
  • Client đọc chuỗi kí tự và gửi cho server.
  • Server nhận tin nhắn từ client và trả về tin nhắn đã viết hoa các kí tự.
  • Client hiển thị thông báo từ server.
  • Lặp lại quá trình cho đến khi client nhập “bye” thì đóng kết nối.

Dưới đây là code phía server:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080

int main(int argc, char const *argv[]) {
    int server_fd, new_socket, valread;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char mess_from_client[225];
    char buffer[1024] = {0};
    char *hello = "Hello from server";
    int continu = 1;

    // Tạo socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // Gán địa chỉ cho socket
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons( PORT );

    // Bind
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // Listen
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // Accept
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    while(continu == 1){
        char str_cli_ip[INET_ADDRSTRLEN];
        struct sockaddr_in* ip_client = (struct sockaddr_in*)&address;
        inet_ntop(AF_INET, &ip_client->sin_addr, str_cli_ip, INET_ADDRSTRLEN);

        printf("ipclient: %sn", str_cli_ip);
        printf("port: %dn", ntohs(ip_client->sin_port));
        printf("Tin nhan ban nhan dc tu client: n");

        // Read
        valread = read( new_socket, buffer, 1024);

        // Viết hoa
        ToUp(buffer);

        // Trả về tin nhắn đã viết hoa
        hello = &buffer;
        printf("%sn",buffer );
        send(new_socket, hello, strlen(hello), 0 );
    }

    close(new_socket);
    return 0;
}

void ToUp( char *p ) {
    while( *p ) {
        *p=toupper( *p );
        p++;
    }
}

Code phía client:

#include 
#include 
#include 
#include 
#include 

#define PORT 8080

int main(int argc, char const *argv[]) {
    struct sockaddr_in address;
    int sock = 0, valread;
    struct sockaddr_in serv_addr;
    char *hello = "Hello from client";
    char buffer[1024] = {0};
    char add[225];
    int continu = 1;

    printf("Nhap dia chi server: ");
    gets(add);

    // Tạo socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("n Socket creation error n");
        return -1;
    }

    memset(&serv_addr, '0', sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // Convert địa chỉ IP từ text sang dạng nhị phân
    if(inet_pton(AF_INET, add, &serv_addr.sin_addr) <= 0) {
        printf("nInvalid address/ Address not supported n");
        return -1;
    }

    // Connect
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("nConnection Failed n");
        return -1;
    }

    while(continu == 1){
        char mess_from_client[225];

        printf("Nhap noi dung tin nhan gui den server: ");
        gets(mess_from_client);
        fflush(stdin);

        hello = &mess_from_client;
        printf("Tin nhan ban nhan dc tu server: n");

        send(sock , hello , strlen(hello) , 0 );

        valread = read(sock , buffer, 1024);
        printf("%sn",buffer );

        if (strcmp(mess_from_client, "bye") == 0) {
            continu = 0;
        }

        fflush(stdin);
    }

    close(sock);
    return 0;
}

Sau khi biên dịch và chạy chương trình, bạn sẽ nhận được kết quả như sau:

Kết quả chương trình

Đó là một demo về chương trình socket sử dụng ngôn ngữ C đơn giản nhất để tìm hiểu về lập trình mạng. Cảm ơn bạn đã đọc!

FEATURED TOPIC