引言
在进行网络程序的设计时,有时候我们不想要手动指定接收方的ip尤其是在做聊天小程序的时候,因为这个时候对方的ip不是固定的,那么我们该怎么办呢?一个方法就是接收方创建一个socket监听一个端口,发送方通过广播的方式给所有局域网内的主机发送一个“hello”包,当然这个包是发往接收方监听的端口的,接收方接收到“hello”包后获取自己的ip地址然后封装在数据报里再发回给发送方,这样发送方就能获得局域网内所有在线的接收方的信息了!
但这个过程有几个问题要考虑。第一点,如果接收方有多个网卡,我怎么知道“hello”包是通过哪个网卡接收的呢?第二点,在接收方我可以用监听那个广播端口的socket来发送消息嘛?
对于第一点,也是苦恼了我很久的问题,最终找到了方法:getsockname,这个函数可以根据addr来得到ip地址,用C语言学习网络编程的都知道接收函数是有一个sockaddr_in结构体来接收发送方的ip和端口信息的,而这个函数就是根据这个结构体来解析的,但是这个函数有一个条件是一定要在连接的情况下才能获取本地ip地址,不过也对,连接了,网卡一定也就确定了。既然要连接那反正udp套接字也可以connect,我们多写一点就是了。
对于第二点,答案是不可以,因为我也尝试了这样的做法,在发送方接收到的ip消息会出现乱码的情况。所以在发送方和接收方都要多创建一个用于传送该消息的socket。总体来说就是,发送方和接收方都要有一个socket用来广播"hello"包和接收"hello"包,并且还都要有一个udp socket来发送udp消息和接收udp消息。
话不多说,上代码
//发送方--广播“hello”包然后接收接收方发送的ip消息
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <WinSock2.h>
#pragma comment (lib,"ws2_32.lib")
#define BUF_SIZE 100
void error_handling(const char* message);
int main(int argc,char* argv[])
{
WSADATA wsadata;
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
error_handling("wsastartup error!");
if (argc != 2) {
printf("usage: %s <port>\n", argv[0]);
exit(1);
}
SOCKET send_sock,recv_sock;
SOCKADDR_IN broad_adr;
char send_msg[BUF_SIZE]="hello,world",rec_msg[BUF_SIZE];;
int so_brd = 1;
//创建send_sock并与广播地址进行关联
send_sock = socket(PF_INET,SOCK_DGRAM,0);
if (send_sock == INVALID_SOCKET)
error_handling("send_sock error!");
memset(&broad_adr,0,sizeof(broad_adr));
broad_adr.sin_family = AF_INET;
broad_adr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
broad_adr.sin_port = htons(atoi(argv[1]));
//设置广播选项
int getinfo = setsockopt(send_sock,SOL_SOCKET,SO_BROADCAST,(const char*)&so_brd,sizeof(so_brd));
if (getinfo == SOCKET_ERROR)
error_handling("setsockopt error!");
printf("setsockopt has done\n");
//创建recv_sock并与本地地址进行绑定
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
if (recv_sock == INVALID_SOCKET)
error_handling("recv_sock error!");
struct sockaddr_in myadr, youradr;
memset(&myadr, 0, sizeof(myadr));
myadr.sin_family = AF_INET;
myadr.sin_addr.s_addr = htonl(INADDR_ANY);
myadr.sin_port = htons(10244);
if (bind(recv_sock, (struct sockaddr*)&myadr, sizeof(myadr)) == SOCKET_ERROR)
error_handling("bind error!");
else printf("bind has done\n");
//发送helloworld消息
int send_len;
send_len = sendto(send_sock,send_msg,strlen(send_msg),0,(SOCKADDR*)&broad_adr,sizeof(broad_adr));
if(send_len == -1)
error_handling("sendto error!");
else printf("sendto has done\n");
closesocket(send_sock); //发送完后关闭发送的socket
//接收来自receive.cpp运行端的ip消息并输出ip
int yadr_sz = sizeof(youradr);
int recv_len = recvfrom(recv_sock, rec_msg, BUF_SIZE, 0, (SOCKADDR*)&youradr, &yadr_sz);
if (recv_len == -1)
error_handling("recvfrom error!");
else {
printf("%s\n", rec_msg);
}
closesocket(recv_sock);
WSACleanup();
return 0;
}
void error_handling(const char* message)
{
printf("%s\n", message);
exit(1);
}
//发送方接收到“hello”包后获取本机ip地址然后封装到udp数据报里发送
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib,"ws2_32.lib")
#define BUF_SIZE 100
void error_handling(const char* message);
int main(int argc, char* argv[])
{
WSADATA wsadata;
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
error_handling("wsastartup error!");
if (argc != 2) {
printf("usage: %s <port>\n", argv[0]);
exit(1);
}
char rec_msg[BUF_SIZE],send_msg[BUF_SIZE] = "the receiver ip is ";
SOCKET send_sock,recv_sock;
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
if (send_sock == INVALID_SOCKET)
error_handling("send_sock error!");
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
if(recv_sock == INVALID_SOCKET)
error_handling("recv_sock error!");
SOCKADDR_IN adr,youradr;
//set adr
memset(&adr, 0, sizeof(adr));
adr.sin_family = AF_INET;
adr.sin_addr.s_addr = htonl(INADDR_ANY);
adr.sin_port = htons(atoi(argv[1]));
//bind socket and adr
if (bind(recv_sock,(SOCKADDR*)&adr,sizeof(adr)) == -1)
error_handling("bind error!");
else printf("bind has done\n");
//receive message
int your_adr_sz = sizeof(youradr);
int strlen = recvfrom(recv_sock, rec_msg, BUF_SIZE, 0, (SOCKADDR*)&youradr, &your_adr_sz);
if (strlen == -1)
error_handling("recvfrom error!");
else {
//输出接收到的消息和发送端的ip地址
char* ip = inet_ntoa(youradr.sin_addr);
printf("the message from sender: %s\n", rec_msg);
printf("the sender ip is : %s\n", ip);
//与发送端建立连接,通过getsockname得到自己的ip地址,将自己的ip地址发送给发送端
//getsockname函数要在连接时才能得到本地ip地址,所以要先connect
//另外要注意在发送端的接收socket的端口设定在10244,所以在这里也要改下
youradr.sin_port = htons(10244);
int con = connect(send_sock, (SOCKADDR*)&youradr, your_adr_sz);
if (con == 0)
printf("connect has done\n");
else error_handling("connect error!");
SOCKADDR_IN myaddr;
int sz = sizeof(myaddr);
int ret = getsockname(send_sock, (SOCKADDR*)&myaddr, &sz); //得到我自己的IP地址
if (ret != 0)
error_handling("getsockname error!");
printf("my ip is %s", inet_ntoa(myaddr.sin_addr));
char temp[BUF_SIZE];
char *myip = inet_ntoa(myaddr.sin_addr);
strcpy(temp, myip);
strncat(send_msg, temp, ::strlen(temp)); //这里strlen前面加::是因为我前面有一个strlen变量重名了
int sendmsg_sz = ::strlen(send_msg);
send_msg[sendmsg_sz] = '\0';
sendmsg_sz++;
//printf("send_msg:%s\n", send_msg);
int sendlen = send(send_sock,send_msg,sendmsg_sz,0);
if (sendlen == -1)
error_handling("sendto error!");
else printf("send has done\n");
}
closesocket(send_sock);
closesocket(recv_sock);
WSACleanup();
return 0;
}
void error_handling(const char* message)
{
printf("%s\n", message);
exit(1);
}
以下是在虚拟机上调试的结果: