2008-11-21 23 views
14

Tôi chỉ mới bắt đầu thực hiện một số chương trình mạng cơ bản với C và tôi tìm thấy tất cả những điều này về ổ cắm và tất cả chúng đều rất phức tạp. Có lẽ mở ổ cắm với C chỉ là phức tạp chính nó, nhưng tôi muốn biết cách đơn giản và hiệu quả nhất để mở và ghi dữ liệu vào một ổ cắm trong ngôn ngữ lập trình C.Cách đơn giản nhất để mở và sử dụng ổ cắm trong C

Cảm ơn

Trả lời

21

Bạn nói đúng, việc sử dụng ổ cắm trong C có một cú pháp khó. Các ngôn ngữ sau như Java và Python làm cho nó dễ dàng hơn bằng cách so sánh. Các hướng dẫn tốt nhất tôi đã tìm thấy để lập trình socket trong C là Beej's Guide to Network Programming. Tôi khuyên bạn nên bắt đầu ngay từ đầu để có cái nhìn tổng quan tốt, nhưng nếu bạn chỉ cần nhận một số mã hoạt động bây giờ, bạn có thể chuyển đến phần có tiêu đề Client-Server Background.

Chúc may mắn!

+1

Đ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

0

Đọc và viết từ ổ cắm cơ bản không phải là bất kỳ khó hơn đọc và ghi các file bình thường (chỉ cần sử dụng recv thay vì đọc và gửi thay vì nếu ghi). Những điều có được một chút trickey khi bạn cần phải mở một ổ cắm. Lý do là vì có nhiều cách khác nhau để giao tiếp bằng cách sử dụng socket (TCP, UDP, v.v.).

+0

những gì bạn nói là đúng trên hệ điều hành thực, nhưng tiếc là Windows làm cho nó hơi khó hơn bằng cách sử dụng một tệp đơn giản bằng cách vẽ phân biệt giữa ổ cắm và tệp. – rmeador

5

Bạn không đề cập đến những gì nền tảng bạn đang ở trên, nhưng một bản sao của Unix Network Programming bởi Stevens sẽ là một bổ sung tốt cho kệ sách của bạn. Hầu hết các hệ điều hành đều thực hiện Ổ cắm Berkley bằng cách sử dụng socket, bind, connect,…

+0

Tôi đã cố gắng nhớ tên của cuốn sách đó cho câu trả lời của riêng tôi nhưng nó hiện đang được lưu trữ. Đây là kinh thánh cho lập trình socket. – paxdiablo

+0

Vâng, đó là một cuốn sách tuyệt vời. Tôi giữ nó trong tầm tay của công việc. –

+0

Tất cả sách của Stevens đều tuyệt vời nhưng UNP là tốt nhất. Cả hai tập. – qrdl

3

Trừ khi bạn viết mạng daemon, hầu hết các mạng trong C có thể được thực hiện ở mức cao hơn bằng cách sử dụng các thư viện thích hợp. Ví dụ:

Ví dụ: nếu bạn chỉ muốn truy xuất tệp có HTTP, hãy sử dụng Neon hoặc libcurl. Nó sẽ đơn giản hơn, nó sẽ ở mức cao hơn và bạn sẽ có SSL, IPv6 miễn phí, v.v.

+0

Một vài phiếu bầu và không phải là một bình luận để giải thích tại sao. Điều này nói lên rất nhiều về trình độ kỹ thuật của nhiều người dùng SO ... – bortzmeyer

+8

Nhiều khả năng đó là trận chiến giữa những người đồng ý với bạn và những người nghĩ rằng bạn không trả lời câu hỏi được hỏi. –

+0

có bạn đi, bây giờ bạn đang ở 0. Là đúng là không dễ dàng – droope

0

Nhiều lời khuyên tốt ở đây cho đến nay. Tôi thường viết trong C++, nhưng bạn có thể tìm thấy một số sử dụng trong một bài báo trắng tôi đã viết "Làm thế nào để tránh Top Ten Sockets Lập trình lỗi" - bỏ qua lời khuyên để sử dụng bộ công cụ ACE (vì nó đòi hỏi C + +) nhưng lưu ý của các ổ cắm lỗi trong bài báo - chúng dễ thực hiện và khó tìm, nhất là đối với người mới bắt đầu. http://www.riverace.com/sockets10.htm

0

Hướng dẫn dứt khoát về lập trình mạng Linux mô tả ổ cắm dễ dàng và giải thích nhiều thứ như luồng máy chủ, thiết kế giao thức ... Ngoài ra TCP/IP Sockets in C, Second Edition là tốt.

1

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.

Các vấn đề liên quan