2010-07-24 30 views
29

Tôi muốn thiết lập kết nối IPC giữa một số quy trình trên Linux. Tôi chưa bao giờ sử dụng ổ cắm UNIX trước đây, và do đó tôi không biết nếu đây là cách tiếp cận chính xác cho vấn đề này.Ổ cắm tên miền Unix: Sử dụng giao tiếp datagram giữa một quá trình máy chủ và một số quy trình máy khách

Một quy trình nhận dữ liệu (không được định dạng, nhị phân) và sẽ phân phối dữ liệu này qua ổ cắm AF_UNIX cục bộ bằng giao thức datagram (tức là tương tự UDP với AF_INET). Dữ liệu được gửi từ quá trình này đến một ổ cắm Unix cục bộ sẽ được nhận bởi nhiều máy khách đang lắng nghe trên cùng một socket. Số lượng người nhận có thể khác nhau.

Để đạt được điều này các mã sau đây được sử dụng để tạo ra một socket và gửi dữ liệu đến nó (quá trình máy chủ):

struct sockaddr_un ipcFile; 
memset(&ipcFile, 0, sizeof(ipcFile)); 
ipcFile.sun_family = AF_UNIX; 
strcpy(ipcFile.sun_path, filename.c_str()); 

int socket = socket(AF_UNIX, SOCK_DGRAM, 0); 
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile)); 
... 
// buf contains the data, buflen contains the number of bytes 
int bytes = write(socket, buf, buflen); 
... 
close(socket); 
unlink(ipcFile.sun_path); 

viết này trả -1 với errno báo cáo ENOTCONN ("endpoint Giao thông vận tải không được kết nối "). Tôi đoán điều này là do không có quá trình nhận hiện đang nghe ổ cắm cục bộ này, đúng không?

Sau đó, tôi đã cố tạo một ứng dụng khách kết nối với ổ cắm này.

struct sockaddr_un ipcFile; 
memset(&ipcFile, 0, sizeof(ipcFile)); 
ipcFile.sun_family = AF_UNIX; 
strcpy(ipcFile.sun_path, filename.c_str()); 

int socket = socket(AF_UNIX, SOCK_DGRAM, 0); 
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile)); 
... 
char buf[1024]; 
int bytes = read(socket, buf, sizeof(buf)); 
... 
close(socket); 

Ở đây, liên kết không thành công ("Địa chỉ đã sử dụng"). Vì vậy, tôi cần phải thiết lập một số tùy chọn ổ cắm, hoặc thường là cách tiếp cận sai?

Cảm ơn trước cho bất kỳ nhận xét/giải pháp nào!

+0

Kiểm tra cũng php như khách hàng và C như máy chủ [ở đây] (http://stackoverflow.com/a/43421610/4626775) –

Trả lời

-3

Bạn nên xem xét kỹ thuật đa hướng IP thay vì bất kỳ tên miền Unix nào. Hiện tại bạn chỉ đang cố gắng viết ra hư không. Và nếu bạn kết nối với một khách hàng, bạn sẽ chỉ viết thư cho khách hàng đó.

Công cụ này không hoạt động theo cách bạn nghĩ.

+0

Multicasting cho các quy trình? –

+0

Chắc chắn, tại sao không? Nếu không, anh ta phải gửi mỗi tin nhắn N lần, và khách hàng không phải tất cả đều nhận được tin nhắn cùng một lúc, điều này làm tăng vấn đề công bằng. – EJP

+0

Tôi xin lỗi; Tôi chưa bao giờ thấy các địa chỉ multicast cho các socket UNIX. Bạn có một ví dụ làm việc hay tôi hiểu nhầm bài đăng của bạn? –

-1

Sẽ dễ dàng hơn khi sử dụng bộ nhớ chia sẻ hoặc các đường ống được đặt tên? Ổ cắm là kết nối giữa hai quá trình (trên cùng một máy hoặc một máy khác). Nó không phải là một phương pháp truyền thông đại chúng.

Nếu bạn muốn cung cấp thứ gì đó cho nhiều khách hàng, bạn tạo một máy chủ chờ kết nối và sau đó tất cả các máy khách có thể kết nối và cung cấp cho họ thông tin. Bạn có thể chấp nhận các kết nối đồng thời bằng cách làm cho chương trình đa luồng hoặc bằng các quy trình forking. Máy chủ thiết lập nhiều kết nối dựa trên socket với nhiều máy khách, thay vì có một socket mà nhiều máy khách kết nối đến.

7

Nguyên nhân gần đúng của lỗi của bạn là write() không biết nơi bạn muốn gửi dữ liệu đến. bind() đặt tên của bên của ổ cắm - ví dụ: nơi dữ liệu đến từ. Để đặt phía đích của ổ cắm, bạn có thể sử dụng connect(); hoặc bạn có thể sử dụng sendto() thay vì write().

Lỗi khác ("Địa chỉ đã được sử dụng") là do chỉ có một quá trình có thể bind() đến một địa chỉ.

Bạn sẽ cần thay đổi cách tiếp cận để tính đến điều này. Máy chủ của bạn sẽ cần phải nghe trên một địa chỉ nổi tiếng, được đặt với bind(). Khách hàng của bạn sẽ cần phải gửi một tin nhắn đến máy chủ tại địa chỉ này để đăng ký sự quan tâm của họ trong việc nhận các gói dữ liệu. Máy chủ sẽ nhận được thông báo đăng ký từ khách hàng sử dụng recvfrom() và ghi lại địa chỉ được sử dụng bởi từng khách hàng.Khi nó muốn gửi một tin nhắn, nó sẽ phải lặp lại tất cả các khách hàng mà nó biết, sử dụng sendto() để gửi tin nhắn cho mỗi người một lần lượt.

Hoặc, bạn có thể sử dụng IP multicast cục bộ thay vì ổ cắm miền UNIX (ổ cắm miền UNIX không hỗ trợ phát đa hướng).

-4

Bạn có thể giải quyết các lỗi ràng buộc với đoạn mã sau:

int use = yesno; 
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&use, sizeof(int)); 

Với giao thức UDP, bạn phải gọi connect() nếu bạn muốn sử dụng write() hoặc send(), nếu không bạn nên sử dụng sendto() để thay thế.

Để đạt được yêu cầu của bạn, mã giả sau đây có thể giúp đỡ:

sockfd = socket(AF_INET, SOCK_DGRAM, 0) 
set RESUSEADDR with setsockopt 
bind() 
while (1) { 
    recvfrom() 
    sendto() 
} 
+0

OP hỏi về giải pháp cho AF_UNIX không AF_INET – Flow

36

Có một mẹo nhỏ để sử dụng unix socket datagram. Không giống như các khe cắm dòng (tcp hoặc unix domain), các ổ đĩa datagram cần các điểm cuối được xác định cho cả máy chủ và máy khách. Khi một thiết lập kết nối trong các ổ cắm luồng, một điểm cuối cho máy khách được tạo ra ngầm bởi hệ điều hành. Cho dù điều này tương ứng với một cổng TCP/UDP tạm thời, hoặc một inode tạm thời cho miền unix, điểm cuối cho máy khách được tạo ra cho bạn. Thats lý do tại sao bạn thường không cần phải thực hiện một cuộc gọi đến bind() cho các socket trong máy khách.

Lý do bạn thấy "Địa chỉ đã được sử dụng" là vì bạn đang yêu cầu khách hàng liên kết với cùng địa chỉ với máy chủ. bind() là về việc xác nhận danh tính bên ngoài. Hai ổ cắm thông thường không có cùng tên.

Với ổ cắm datagram, đặc biệt là unix socket datagram miền, khách hàng phải bind()-riêng endpoint của nó, sau đó connect()- endpoint các của máy chủ. Đây là mã khách hàng của bạn, một chút thay đổi, với một số tính năng khác ném vào:

char * server_filename = "/tmp/socket-server"; 
char * client_filename = "/tmp/socket-client"; 

struct sockaddr_un server_addr; 
struct sockaddr_un client_addr; 
memset(&server_addr, 0, sizeof(server_addr)); 
server_addr.sun_family = AF_UNIX; 
strncpy(server_addr.sun_path, server_filename, 104); // XXX: should be limited to about 104 characters, system dependent 

memset(&client_addr, 0, sizeof(client_addr)); 
client_addr.sun_family = AF_UNIX; 
strncpy(client_addr.sun_path, client_filename, 104); 

// get socket 
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0); 

// bind client to client_filename 
bind(sockfd, (struct sockaddr *) &client_addr, sizeof(client_addr)); 

// connect client to server_filename 
connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)); 

... 
char buf[1024]; 
int bytes = read(sockfd, buf, sizeof(buf)); 
... 
close(sockfd); 

Tại thời điểm này ổ cắm của bạn nên được đầy đủ thiết lập. Tôi nghĩ về mặt lý thuyết bạn có thể sử dụng read()/write(), nhưng thường tôi sẽ sử dụng send()/recv() cho ổ cắm datagram.

Thông thường bạn sẽ muốn kiểm tra lỗi sau mỗi cuộc gọi này và phát hành perror() sau đó. Nó sẽ giúp bạn rất nhiều khi mọi thứ xảy ra sai. Nói chung, hãy sử dụng mẫu như sau:

if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { 
    perror("socket failed"); 
} 

Điều này thực hiện rất nhiều cuộc gọi hệ thống của C.

Tham chiếu tốt nhất cho điều này là "Lập trình mạng Unix" của Steven. Trong ấn bản thứ 3, mục 15.4, các trang 415-419 hiển thị một số ví dụ và liệt kê nhiều cảnh báo.

Bằng cách này, khi đề cập đến

Tôi đoán đây là vì không có quá trình tiếp nhận hiện đang nghe ổ cắm địa phương này, có đúng không?

Tôi nghĩ bạn đúng về lỗi ENOTCONN từ write() trong máy chủ. Một ổ cắm UDP thường sẽ không phàn nàn vì nó không có cơ sở để biết nếu quá trình khách hàng đang lắng nghe.Tuy nhiên, unix domain datagram sockets thì khác nhau. Trong thực tế, write() sẽ thực sự chặn nếu bộ đệm nhận của khách hàng đầy hơn là thả gói. Điều này làm cho unix domain datagram socket cao hơn nhiều so với UDP cho IPC bởi vì UDP chắc chắn sẽ thả các gói tin khi tải, ngay cả trên localhost. Mặt khác, nó có nghĩa là bạn phải cẩn thận với các nhà văn nhanh và người đọc chậm.

+0

Tôi có thể kết nối các dấu chấm bằng câu trả lời này, câu trả lời từ @caf và 2 tệp nguồn cuối cùng tại http://www.thomasstover.com/uds.html (ghi chú rằng có một vài lỗi nhỏ trong mã đó). Mã máy chủ trong câu hỏi sẽ không hoạt động nếu không có địa chỉ khách hàng, mà tôi nhận ra khi tôi đọc câu trả lời của quán cà phê. – hBrent

+1

Các câu bắt đầu 'Một ổ cắm UDP bình thường sẽ không phàn nàn' và 'Điều này làm cho ổ cắm datagram unix miền cao hơn nhiều so với UDP' đều không chính xác. Hầu hết nếu không phải tất cả những gì bạn đã nói về các socket datagram miền Unix áp dụng như nhau đối với các socket UDP miền IP: đặc biệt, chúng phải được kết nối để sử dụng 'write(),' và chúng chặn trong 'write() 'hoặc' send() 'trong khi bộ đệm gửi đầy. -1 cho thông tin sai lạc. – EJP

+0

@EJP: Tôi nghĩ bạn đã hiểu nhầm một số câu trả lời của tôi. Tôi đã không có nghĩa là ngụ ý ổ cắm có thể không được kết nối trong 'write()', và tôi đã không đề cập đến bất cứ điều gì về một bộ đệm gửi đầy đủ (chỉ có một bộ đệm nhận đầy đủ). Tôi đã chuyển đoạn bạn đã đề cập dưới đây bởi vì nó thảo luận về một câu hỏi phụ trợ, có thể là một phần của sự nhầm lẫn. – adamlamar

1

Nếu câu hỏi dự định được về phát thanh truyền hình (như tôi hiểu nó), sau đó theo unix(4) - UNIX-domain protocol family, phát sóng nó không có sẵn với UNIX Sockets miền:

Các Unix Ns -domain gia đình giao thức không hỗ trợ địa chỉ quảng bá hoặc bất kỳ hình thức "ký tự đại diện" nào khớp với trên thư đến. Tất cả địa chỉ là tuyệt đối hoặc tên đường dẫn tương đối của các ổ cắm miền Unix Ns khác.

Có thể là đa phương tiện có thể là một tùy chọn, nhưng tôi cảm thấy rằng nó không khả dụng với POSIX, mặc dù Linux supports UNIX Domain Socket multicast.

Xem thêm: Introducing multicast Unix sockets.

-1

Điều này sẽ xảy ra vì máy chủ hoặc ứng dụng khách chết trước khi hủy liên kết/xóa đối với liên kết tệp bind(). bất kỳ máy khách/máy chủ nào sử dụng đường dẫn liên kết này, hãy thử chạy lại máy chủ.

giải pháp: khi bạn muốn liên kết lại chỉ cần kiểm tra xem tệp đó đã được liên kết rồi hủy liên kết tệp đó chưa. Cách thực hiện: kiểm tra quyền truy cập đầu tiên của tệp này bằng cách truy cập (2); nếu có thì hủy liên kết (2). đặt mã hòa bình này trước lệnh bind(), vị trí độc lập.

if(!access(filename.c_str())) 
    unlink(filename.c_str()); 

để tham khảo hơn đọc unix (7)

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