POSIX 7 khách hàng máy chủ TCP dụ
Cách sử dụng
Nhận hai máy tính trong một mạng LAN.
Chạy máy chủ trên một máy tính với:
./server.out
Lấy IP của máy tính máy chủ với ifconfig
, ví dụ 192.168.0.10
.
Trên máy tính khác, hãy chạy:
./client.out 192.168.0.10
Bây giờ gõ dòng trên máy khách và máy chủ sẽ trả lại tăng thêm 1 (ROT-1 cypher).
server.c
#define _XOPEN_SOURCE 700
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char **argv) {
char buffer[BUFSIZ];
char protoname[] = "tcp";
struct protoent *protoent;
int enable = 1;
int i;
int newline_found = 0;
int server_sockfd, client_sockfd;
socklen_t client_len;
ssize_t nbytes_read;
struct sockaddr_in client_address, server_address;
unsigned short server_port = 12345u;
if (argc > 1) {
server_port = strtol(argv[1], NULL, 10);
}
protoent = getprotobyname(protoname);
if (protoent == NULL) {
perror("getprotobyname");
exit(EXIT_FAILURE);
}
server_sockfd = socket(
AF_INET,
SOCK_STREAM,
protoent->p_proto
/* 0 */
);
if (server_sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed");
exit(EXIT_FAILURE);
}
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(server_port);
if (bind(
server_sockfd,
(struct sockaddr*)&server_address,
sizeof(server_address)
) == -1
) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(server_sockfd, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
fprintf(stderr, "listening on port %d\n", server_port);
while (1) {
client_len = sizeof(client_address);
client_sockfd = accept(
server_sockfd,
(struct sockaddr*)&client_address,
&client_len
);
while ((nbytes_read = read(client_sockfd, buffer, BUFSIZ)) > 0) {
printf("received:\n");
write(STDOUT_FILENO, buffer, nbytes_read);
if (buffer[nbytes_read - 1] == '\n')
newline_found;
for (i = 0; i < nbytes_read - 1; i++)
buffer[i]++;
write(client_sockfd, buffer, nbytes_read);
if (newline_found)
break;
}
close(client_sockfd);
}
return EXIT_SUCCESS;
}
client.c
#define _XOPEN_SOURCE 700
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char **argv) {
char buffer[BUFSIZ];
char protoname[] = "tcp";
struct protoent *protoent;
char *server_hostname = "127.0.0.1";
char *user_input = NULL;
in_addr_t in_addr;
in_addr_t server_addr;
int sockfd;
size_t getline_buffer = 0;
ssize_t nbytes_read, i, user_input_len;
struct hostent *hostent;
/* This is the struct used by INet addresses. */
struct sockaddr_in sockaddr_in;
unsigned short server_port = 12345;
if (argc > 1) {
server_hostname = argv[1];
if (argc > 2) {
server_port = strtol(argv[2], NULL, 10);
}
}
/* Get socket. */
protoent = getprotobyname(protoname);
if (protoent == NULL) {
perror("getprotobyname");
exit(EXIT_FAILURE);
}
sockfd = socket(AF_INET, SOCK_STREAM, protoent->p_proto);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* Prepare sockaddr_in. */
hostent = gethostbyname(server_hostname);
if (hostent == NULL) {
fprintf(stderr, "error: gethostbyname(\"%s\")\n", server_hostname);
exit(EXIT_FAILURE);
}
in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list)));
if (in_addr == (in_addr_t)-1) {
fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list));
exit(EXIT_FAILURE);
}
sockaddr_in.sin_addr.s_addr = in_addr;
sockaddr_in.sin_family = AF_INET;
sockaddr_in.sin_port = htons(server_port);
/* Do the actual connection. */
if (connect(sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) {
perror("connect");
return EXIT_FAILURE;
}
while (1) {
fprintf(stderr, "enter string (empty to quit):\n");
user_input_len = getline(&user_input, &getline_buffer, stdin);
if (user_input_len == -1) {
perror("getline");
exit(EXIT_FAILURE);
}
if (user_input_len == 1) {
close(sockfd);
break;
}
if (write(sockfd, user_input, user_input_len) == -1) {
perror("write");
exit(EXIT_FAILURE);
}
while ((nbytes_read = read(sockfd, buffer, BUFSIZ)) > 0) {
write(STDOUT_FILENO, buffer, nbytes_read);
if (buffer[nbytes_read - 1] == '\n') {
fflush(stdout);
break;
}
}
}
free(user_input);
exit(EXIT_SUCCESS);
}
On GitHub with a Makefile. Thử nghiệm trên Ubuntu 15.10.
dài nhắn
Các read
cuộc gọi trên cả client và server chạy bên trong vòng lặp while.
Giống như khi đọc từ tệp, hệ điều hành có thể chia nhỏ các thông điệp một cách tùy tiện để làm mọi việc nhanh hơn, ví dụ: một gói có thể đến sớm hơn gói kia.
Vì vậy, giao thức phải chỉ định quy ước nơi thư dừng. phương pháp phổ biến bao gồm:
- một tiêu đề với một chỉ số chiều dài (ví dụ HTTP
Content-Length
)
- một chuỗi duy nhất chấm dứt các thông điệp. Ở đây chúng tôi sử dụng
\n
.
- máy chủ đóng kết nối: HTTP cho phép https://stackoverflow.com/a/25586633/895245. Hạn chế tất nhiên vì tin nhắn tiếp theo yêu cầu kết nối lại.
Tiếp bước
Ví dụ này là hạn chế vì:
- máy chủ chỉ có thể xử lý một kết nối khách hàng tại một thời điểm
- thông tin liên lạc được đồng bộ đơn giản. Ví dụ: trên một ứng dụng trò chuyện P2P, máy chủ (người khác) có thể gửi tin nhắn bất cứ lúc nào.
Giải quyết những vấn đề đó yêu cầu luồng và có thể các cuộc gọi khác như poll
.
Điều này thật ngớ ngẩn. Các ổ cắm được * định nghĩa * là một API C và "các ngôn ngữ sau" phải thực hiện tất cả các cuộc gọi C đó ở một mức nào đó. Có "thư viện sau này" trong C mà cũng sẽ làm điều đó một cách dễ dàng, ngay lập tức để thực hiện, nói, một yêu cầu HTTP thay vì mucking xung quanh với ổ cắm. Bạn có thể dễ dàng có một chức năng máy khách sẽ lấy một địa chỉ IP trong ký hiệu host hoặc dấu chấm dưới dạng một chuỗi 'char *', một số cổng như là một 'int', và trả về cho bạn một dòng' FILE * 'biểu thị mạch kết nối, hoặc một con trỏ null với 'errno' được đặt thành một cái gì đó hữu ích. – Kaz