Есть хитрость в использовании сокетов датаграмм unix. В отличие от потоковых сокетов (домен tcp или unix), сокетам дейтаграмм необходимы конечные точки, определенные как для сервера, так и для клиента. Когда устанавливается соединение в потоковых сокетах, операционная система неявно создает конечную точку для клиента. Соответствует ли это эфемерному порту TCP / UDP или временному индоду для домена unix, для вас создается конечная точка для клиента. Вот почему вам обычно не нужно вызывать bind () для потоковых сокетов в клиенте.
Причина, по которой вы видите «Адрес уже используется», заключается в том, что вы говорите клиенту привязаться к тому же адресу, что и сервер. bind()
- это утверждение внешней идентичности. Два сокета обычно не могут иметь одинаковое имя.
С сокетами дейтаграмм, в частности сокетами дейтаграмм домена unix, клиент должен bind()
до своей собственной конечной точки, затем connect()
до конечной точки сервера . Вот ваш клиентский код, слегка измененный, с некоторыми другими добавленными вкусностями:
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);
На этом этапе ваш сокет должен быть полностью настроен. Я думаю, теоретически вы можете использовать read()
/ write()
, но обычно я бы использовал send()
/ recv()
для сокетов дейтаграмм.
Обычно вы хотите проверять ошибку после каждого из этих вызовов и затем выдавать perror()
. Это очень поможет вам, когда что-то пойдет не так. В общем, используйте шаблон, подобный этому:
if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
perror("socket failed");
}
Это относится практически ко всем системным вызовам Си.
Лучшим примером для этого является "Сетевое программирование Unix" Стивена. В 3-м издании, раздел 15.4, на страницах 415-419 приведены некоторые примеры и перечислены многие предостережения.
Кстати, со ссылкой на
Полагаю, это потому, что ни один процесс получения не прослушивает этот локальный сокет, верно?
Я думаю, что вы правы насчет ошибки ENOTCONN от write()
на сервере. Сокет UDP обычно не будет жаловаться, потому что у него нет возможности узнать, слушает ли клиентский процесс. Однако сокеты датаграмм домена unix отличаются. Фактически, write()
будет фактически блокироваться, если приемный буфер клиента заполнен, а не отбрасывать пакет. Это делает сокеты дейтаграмм в домене Unix намного лучше, чем UDP для IPC, потому что UDP, безусловно, будет отбрасывать пакеты при загрузке даже на локальном хосте. С другой стороны, это означает, что вы должны быть осторожны с быстрыми писателями и медленными читателями.