Linux Sockets là gì?
Hầu hết các cơ chế giao tiếp giữa các tiến trình (interprocess communication – IPC) sử dụng mô hình client-server. Các thuật ngữ này chỉ hai tiến trình sẽ giao tiếp với nhau. Một trong hai tiến trình, gọi là client, sẽ kết nối với tiến trình còn lại, gọi là server, thường là để gửi yêu cầu thông tin. Một ví dụ dễ hình dung là một người gọi điện thoại cho người khác.
Cần lưu ý rằng client cần biết sự tồn tại và địa chỉ của server, nhưng server không cần biết địa chỉ (hoặc thậm chí sự tồn tại) của client trước khi kết nối được thiết lập.
Cũng cần lưu ý rằng sau khi kết nối được thiết lập, cả hai bên đều có thể gửi và nhận thông tin.
Các system call để thiết lập kết nối có phần khác nhau giữa client và server, nhưng cả hai đều dựa trên cấu trúc cơ bản của socket.
Socket là một đầu của kênh giao tiếp giữa các tiến trình. Mỗi tiến trình sẽ thiết lập socket riêng của mình.
Các loại socket.
Có 2 loại socket được sử dụng rộng rãi là: stream sockets và datagram sockets.
- Stream sockets: Dựa trên giao thức TCP (Tranmission Control Protocol), là giao thức hướng luồng (stream oriented). Việc truyền dữ liệu chỉ thực hiện giữa 2 tiến trình đã thiết lập kết nối. Giao thức này đảm bảo dữ liệu được truyền đến nơi nhận một cách đáng tin cậy, đúng thứ tự nhờ vào cơ chế quản lý luồng lưu thông trên mạng và cơ chế chống tắc nghẽn.
- Datagram sockets: Dựa trên giao thức UDP (User Datagram Protocol), là giao thức hướng thông điệp (message oriented). Việc truyền dữ liệu không yêu cầu có sự thiết lập kết nối giữa tiến quá trình. Ngược lại với giao thức TCP thì dữ liệu được truyền theo giao thức UDP
không được tin cậy, có thế không đúng trình tự và lặp lại. Tuy nhiên vì nó không yêu cầu thiết lập kết nối không phải có những cơ chế phức tạp nên tốc độ nhanh…ứng dụng cho các ứng dụng truyền dữ liệu nhanh như chat, game…..
Các bước thiết lập socket:
Ví dụ, để sử dụng socket TCP hướng dòng (stream-oriented) trong Linux, người dùng cần bắt đầu bằng cách chỉ định loại socket với system call socket().Các lệnh API tiếp theo sẽ khác nhau tùy thuộc vào vai trò của hệ thống trong mạng
Phía client:
- Tạo socket bằng system call socket()
- Kết nối socket tới địa chỉ của server bằng system call connect().
- Gửi và nhận dữ liệu. Có nhiều cách để làm việc này, nhưng đơn giản nhất là dùng system call read() và write(). (Hoặc send() và recv()).
Phía server:
- Tạo socket bằng system call socket().
- Gán socket vào một địa chỉ bằng system call bind(). Đối với server trên Internet, địa chỉ bao gồm số cổng (port) trên máy chủ.
- Lắng nghe các kết nối bằng system call listen().
- Chấp nhận một kết nối bằng system call accept(). Lệnh này thường block cho tới khi một client kết nối tới server.
- Gửi và nhận dữ liệu .(Hoặc send() và recv()).
Ngược lại, giao tiếp dạng datagram (UDP) không yêu cầu thiết lập kết nối. Do đó, server và client dùng cùng các system call để trao đổi thông tin giữa các socket.
Lệnh socket() vẫn được dùng để khởi tạo socket. Tuy nhiên, vì không cần thiết lập kết nối lâu dài, socket dạng datagram không sử dụng các lệnh bind(), listen(), và accept() như socket kiểu stream (TCP). Thay vào đó, các lệnh send() và recv() sẽ thực hiện việc trao đổi gói dữ liệu trực tiếp.
Sample code
Code C cho một client và server rất đơn giản được cung cấp. Chúng giao tiếp với nhau bằng stream socket trong Internet domain (TCP/IP). Code sẽ được mô tả chi tiết bên dưới. Tuy nhiên, trước khi đọc mô tả và xem code, bạn nên biên dịch và chạy hai chương trình để xem chúng hoạt động như thế nào.
socket-client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
void error(const char *msg)
{
perror(msg);
exit(0);
}
int main(int argc, char *argv[])
{
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];
if (argc < 3) {
fprintf(stderr,"usage %s hostname port\n", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(portno);
if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
printf("Please enter the message: ");
bzero(buffer,256);
fgets(buffer,255,stdin);
n = write(sockfd,buffer,strlen(buffer));
if (n < 0)
error("ERROR writing to socket");
bzero(buffer,256);
n = read(sockfd,buffer,255);
if (n < 0)
error("ERROR reading from socket");
printf("%s\n",buffer);
close(sockfd);
return 0;
}
socket-server.c
/* A simple server in the internet domain using TCP
The port number is passed as an argument */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
void error(const char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
int sockfd, newsockfd, portno;
socklen_t clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
if (argc < 2) {
fprintf(stderr,"ERROR, no port provided\n");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
error("ERROR on binding");
listen(sockfd,5);
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr,
&clilen);
if (newsockfd < 0)
error("ERROR on accept");
bzero(buffer,256);
n = read(newsockfd,buffer,255);
if (n < 0) error("ERROR reading from socket");
printf("Here is the message: %s\n",buffer);
n = write(newsockfd,"I got your message",18);
if (n < 0) error("ERROR writing to socket");
close(newsockfd);
close(sockfd);
return 0;
}
Chúng thường không cần flag biên dịch đặc biệt, nhưng trên một số hệ thống Solaris, bạn có thể cần liên kết với thư viện socket bằng cách thêm -lsocket vào lệnh compile.
Tốt nhất, bạn nên chạy client và server trên hai máy khác nhau trên Internet.
Khởi động server trước. Giả sử server chạy trên máy có tên elab. Khi chạy server, bạn cần truyền số cổng (port number) làm tham số. Bạn có thể chọn số từ 2000 đến 65535.
Nếu port này đã được sử dụng, server sẽ báo và thoát. Lúc đó, bạn chọn port khác và thử lại.
Nếu port khả dụng, server sẽ block và chờ client kết nối. Đừng lo lắng nếu server không làm gì trong lúc này; nó không làm gì cho tới khi kết nối được thiết lập.
Ví dụ lệnh chạy server:
server 8386
Để chạy client, bạn cần truyền hai tham số: tên máy nơi server chạy và số port mà server đang lắng nghe.
Ví dụ lệnh chạy client kết nối server:
client elab 8386
Client sẽ yêu cầu bạn nhập một thông điệp. Nếu mọi thứ hoạt động bình thường:
- Server sẽ hiển thị thông điệp trên stdout, gửi tin nhắn xác nhận (acknowledgement) cho client và thoát.
- Client sẽ in tin nhắn xác nhận từ server và thoát.
- Bạn có thể mô phỏng trên cùng một máy bằng cách chạy server trong một cửa sổ terminal và client trong cửa sổ khác. Lúc này, bạn có thể dùng localhost làm tham số đầu tiên cho client.
Kết quả:


