私はポイントツーポイントのメッセージキューシステムを書いています、そしてそれはUDP上で動作できなければなりません。どちらか一方を勝手に「サーバー」として選択することもできますが、両端が同じタイプのデータをもう一方から送受信しているため、正しくないようです。
相互にのみ送受信するように、両端をbind()およびconnect()することは可能ですか?これは、対称的な方法のように思えます。
UDPはコネクションレスであるため、OSが実際に何らかの接続を行うことにはほとんど意味がありません。
BSDソケットでは、UDPソケットでconnect
を実行できますが、これは基本的にsend
のデフォルトの宛先アドレスを設定するだけです(代わりにsend_to
)。
UDPソケットのバインドは、ソケットの種類に関係なく、実際にパケットを受け入れる着信アドレスをOSに指示します(他のアドレスへのすべてのパケットはドロップされます)。
受信時には、recvfrom
を使用して、パケットの送信元を識別する必要があります。何らかの認証が必要な場合、関係するアドレスだけを使用すると、ロックがまったくないのと同じくらい安全ではないことに注意してください。 TCP接続がハイジャックされる可能性があり、ネイキッドUDPは文字通りIPスプーフィングが全面的に書き込まれます。何らかのHMACを追加する必要があります
こんにちは、2018年の遠い未来から2012年まで。
実際には、実際にUDPソケットをconnect()
ingする背後に理由があります(ただし、祝福されたPOSIXは理論的にはそうする必要はありません)。
通常のUDPソケットは将来の宛先について何も知らないため、 sendmsg()
が呼び出されるたびにルートルックアップを実行します 。
ただし、特定のリモートレシーバーのIPとポートでconnect()
が事前に呼び出された場合、オペレーティングシステムカーネルは ルートへの参照を書き留めてソケットに割り当てる 、後続のsendmsg()
呼び出しがレシーバーを指定しない場合( それ以外の場合、前の設定は無視されます )、代わりにデフォルトを選択します。
if (connected)
rt = (struct rtable *)sk_dst_check(sk, 0);
if (!rt) {
[..skip..]
rt = ip_route_output_flow(net, fl4, sk);
[..skip..]
}
Linuxカーネル4.18まで、この機能は主にIPv4アドレスファミリのみに制限されていました。ただし、4.18-rc4以降(できればLinuxカーネルリリース4.18も同様)、 IPv6ソケットでも完全に機能します 。
深刻なパフォーマンス上の利点 のソースである可能性がありますが、使用しているOSに大きく依存します。少なくとも、Linuxを使用していて、複数のリモートハンドラーにソケットを使用しない場合は、試してみてください。
以下は、同じUDPソケットで特定の送信元ポートと宛先ポートのセットにそれぞれbind()およびconnect()する方法を示すプログラムです。このプログラムは任意のLinuxマシンでコンパイルでき、次の使用方法があります。
usage: ./<program_name> dst-hostname dst-udpport src-udpport
このコードをテストして、2つの端末を開きました。宛先ノードにメッセージを送信し、そこからメッセージを受信できるはずです。
ターミナル1の実行時
./<program_name> 127.0.0.1 5555 5556
ターミナル2の実行時
./<program_name> 127.0.0.1 5556 5555
単一のマシンでテストしましたが、正しいファイアウォール設定をセットアップすると、2つの異なるマシンでも動作するはずです。
フローの説明は次のとおりです。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define STDIN 0
int sendall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we've sent
int bytesleft = *len; // how many we have left to send
int n;
while(total < *len) {
n = send(s, buf+total, bytesleft, 0);
fprintf(stdout,"Sendall: %s\n",buf+total);
if (n == -1) { break; }
total += n;
bytesleft -= n;
}
*len = total; // return number actually sent here
return n==-1?-1:0; // return -1 on failure, 0 on success
}
int main(int argc, char *argv[])
{
int sockfd;
struct addrinfo hints, *dstinfo = NULL, *srcinfo = NULL, *p = NULL;
int rv = -1, ret = -1, len = -1, numbytes = 0;
struct timeval tv;
char buffer[256] = {0};
fd_set readfds;
// don't care about writefds and exceptfds:
// select(STDIN+1, &readfds, NULL, NULL, &tv);
if (argc != 4) {
fprintf(stderr,"usage: %s dst-hostname dst-udpport src-udpport\n");
ret = -1;
goto LBL_RET;
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM; //UDP communication
/*For destination address*/
if ((rv = getaddrinfo(argv[1], argv[2], &hints, &dstinfo)) != 0) {
fprintf(stderr, "getaddrinfo for dest address: %s\n", gai_strerror(rv));
ret = 1;
goto LBL_RET;
}
// loop through all the results and make a socket
for(p = dstinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("socket");
continue;
}
/*Taking first entry from getaddrinfo*/
break;
}
/*Failed to get socket to all entries*/
if (p == NULL) {
fprintf(stderr, "%s: Failed to get socket\n");
ret = 2;
goto LBL_RET;
}
/*For source address*/
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM; //UDP communication
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
/*For source address*/
if ((rv = getaddrinfo(NULL, argv[3], &hints, &srcinfo)) != 0) {
fprintf(stderr, "getaddrinfo for src address: %s\n", gai_strerror(rv));
ret = 3;
goto LBL_RET;
}
/*Bind this datagram socket to source address info */
if((rv = bind(sockfd, srcinfo->ai_addr, srcinfo->ai_addrlen)) != 0) {
fprintf(stderr, "bind: %s\n", gai_strerror(rv));
ret = 3;
goto LBL_RET;
}
/*Connect this datagram socket to destination address info */
if((rv= connect(sockfd, p->ai_addr, p->ai_addrlen)) != 0) {
fprintf(stderr, "connect: %s\n", gai_strerror(rv));
ret = 3;
goto LBL_RET;
}
while(1){
FD_ZERO(&readfds);
FD_SET(STDIN, &readfds);
FD_SET(sockfd, &readfds);
/*Select timeout at 10s*/
tv.tv_sec = 10;
tv.tv_usec = 0;
select(sockfd + 1, &readfds, NULL, NULL, &tv);
/*Obey your user, take his inputs*/
if (FD_ISSET(STDIN, &readfds))
{
memset(buffer, 0, sizeof(buffer));
len = 0;
printf("A key was pressed!\n");
if(0 >= (len = read(STDIN, buffer, sizeof(buffer))))
{
perror("read STDIN");
ret = 4;
goto LBL_RET;
}
fprintf(stdout, ">>%s\n", buffer);
/*EOM\n implies user wants to exit*/
if(!strcmp(buffer,"EOM\n")){
printf("Received EOM closing\n");
break;
}
/*Sendall will use send to transfer to bound sockfd*/
if (sendall(sockfd, buffer, &len) == -1) {
perror("sendall");
fprintf(stderr,"%s: We only sent %d bytes because of the error!\n", argv[0], len);
ret = 5;
goto LBL_RET;
}
}
/*We've got something on our socket to read */
if(FD_ISSET(sockfd, &readfds))
{
memset(buffer, 0, sizeof(buffer));
printf("Received something!\n");
/*recv will use receive to connected sockfd */
numbytes = recv(sockfd, buffer, sizeof(buffer), 0);
if(0 == numbytes){
printf("Destination closed\n");
break;
}else if(-1 == numbytes){
/*Could be an ICMP error from remote end*/
perror("recv");
printf("Receive error check your firewall settings\n");
ret = 5;
goto LBL_RET;
}
fprintf(stdout, "<<Number of bytes %d Message: %s\n", numbytes, buffer);
}
/*Heartbeat*/
printf(".\n");
}
ret = 0;
LBL_RET:
if(dstinfo)
freeaddrinfo(dstinfo);
if(srcinfo)
freeaddrinfo(srcinfo);
close(sockfd);
return ret;
}
本当にキーはconnect()
です:
ソケットsockfdのタイプがSOCK_DGRAMの場合、addrはデフォルトでデータグラムが送信されるアドレスであり、データグラムが受信される唯一のアドレスです。
UDPでconnect()を使用していません。 connect()はUDPとTCPの下で2つの全く異なる目的のために設計されたと感じています。
manページ には、UDPでのconnect()の使用に関する簡単な詳細があります。
一般に、接続ベースのプロトコル(TCPなど)ソケットは、一度だけ正常にconnect()できます。コネクションレスプロトコル(UDPなど)ソケットは、connect()を複数回使用して関連付けを変更できます。
このページには、接続ソケットと非接続ソケットに関する優れた情報が含まれています。 http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html
この引用はあなたの質問に答えます:
通常、接続を呼び出すのはUDPクライアントですが、UDPサーバーが単一のクライアントと長時間通信するアプリケーションがあります(TFTPなど)。この場合、クライアントとサーバーの両方がconnectを呼び出すことができます。
コードに問題があります:
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM; //UDP communication
/*For destination address*/
if ((rv = getaddrinfo(argv[1], argv[2], &hints, &dstinfo))
AF_UNSPECおよびSOCK_DGRAMのみを使用することにより、可能なすべてのaddrsのリストを取得します。そのため、ソケットを呼び出すとき、使用しているアドレスは期待したUDPアドレスではない可能性があります。あなたが使用する必要があります
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE;
代わりに、取得するaddrinfoが希望どおりであることを確認してください。
別のWordでは、作成したソケットはUDPソケットではない可能性があり、それが機能しない理由です。
はい、できます。私もやります。
そして、あなたのユースケースはこれが有用なものです:両側がクライアントとサーバーの両方として機能し、両側に1つのプロセスしかありません。