前段时间发了个TCP通信的例子,现在再来一个UDP通信的例子。这些可以作为样本程序,用到开发中。“裸写”socket老是记不住步骤,经常被鄙视……
下面的例子很简单,写一个UDP的server用于收包,写一个UDP的client用于发包并接收来自server的回复。其中UDP的client写了两个,一个是不需要connect的,另一个是带上connect的,两个client实现的功能是一样的。从效率上,带上connect的UDP肯定效率稍微高一些。不过UDP的connect和TCP里面非常不一样。在UDP里面connect的时候并没有三次握手的过程,但是它指定了与自己通信的对方的具体地址,内核中会将次地址记录下来,如果你的UDP就是在确定了两台机器之间传送信息,建议选取带有connect的套接字。connect之后与对方通信直接write或者read函数就可以,不用再指定对方ip和port,并且connect之后的套接字可以自动过滤掉不是来自指定通信方的信息。UDP可以调用多次connect函数,但是TCP套接字只能调用一次,再次调用会出现错误。
1. 首先是服务端的程序:
UDPserver.cpp
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9
10 #define PORT 1234
11 #define MAXDATASIZE 100
12
13 int main(void)14 {15 intsockfd;16 structsockaddr_in server;17 structsockaddr_in client;18 socklen_t len;19 intnum;20 charbuf[MAXDATASIZE];21 if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)22 {23 perror("Creating socket failed.\n");24 exit(1);25 }26 bzero(&server, sizeof(server));27 server.sin_family =AF_INET;28 server.sin_port =htons(PORT);29 server.sin_addr.s_addr =htonl(INADDR_ANY);30 if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)31 {32 perror("Bind() error.\n");33 exit(1);34 }35
36 len = sizeof(client);37 while(1)38 {39 num = recvfrom(sockfd, buf, MAXDATASIZE, 0, (struct sockaddr *)&client, &len);40 if(num < 0)41 {42 perror("recvfrom() error.\n");43 exit(1);44 }45 buf[num] = '\0';46 printf("You got a message from client. \nIt's ip is %s, port is %d. \n", buf, inet_ntoa(client.sin_addr),htons(client.sin_port));47 sendto(sockfd, "Welcome\n", 8, 0, (struct sockaddr *)&client, len);48 if ( !strcmp(buf, "bye") ){49 break;50 }51 }52 close(sockfd);53 }
2. 然后,我们给出带有connect的客户端程序:
UDPclientWithConnect.cpp
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10
11 #define PORT 1234
12
13 #define MAXDATASIZE 100
14
15 int main(int argc, char *argv[])16 {17 intsockfd, num;18 charbuf[MAXDATASIZE];19 struct hostent *he;20 structsockaddr_in server, peer;21 if(argc != 3)22 {23 printf("Usage: %s \n", argv[0]);24 exit(1);25 }26
27 if((sockfd=socket(AF_INET, SOCK_DGRAM, 0)) == -1)28 {29 printf("socket() error\n");30 exit(1);31 }32 bzero(&server, sizeof(server));33 server.sin_family =AF_INET;34 server.sin_port =htons(PORT);35
36 server.sin_addr.s_addr = inet_addr(argv[1]);37 //server.sin_addr.s_addr = inet_addr(argv[1]);
38 if(connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)39 {40 printf("connect() error.\n");41 exit(1);42 }43
44 send(sockfd, argv[2], strlen(argv[2]), 0);45
46 while(1)47 {48 if((num = recv(sockfd, buf, MAXDATASIZE, 0)) == -1)49 {50 printf("recv() error.\n");51 exit(1);52 }53
54 buf[num] = '\0';55 printf("Server Message: %s.\n", buf);56 break;57 }58 close(sockfd);59 }
3. 最后,再给一个不带connect的客户端程序。
UDPclientNoConnect.cpp
#include #include#include#include#include#include#include#include
#define PORT 1234
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{intsockfd, num;charbuf[MAXDATASIZE];struct hostent *he;structsockaddr_in server, peer;if(argc != 3)
{
printf("Usage: %s \n", argv[0]);
exit(1);
}if((he = gethostbyname(argv[1]))==NULL)
{
printf("gethostbyname() error\n");
exit(1);
}if((sockfd=socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
printf("socket() error\n");
exit(1);
}
bzero(&server, sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr= *( (struct in_addr *)he->h_addr);
sendto(sockfd, argv[2], strlen(argv[2]), 0, (struct sockaddr *)&server, sizeof(server));
socklen_t len;
len= sizeof(server);while(1)
{if((num = recvfrom(sockfd, buf, MAXDATASIZE, 0, (struct sockaddr *)&peer, &len)) == -1)
{
printf("recvfrom() error\n");
exit(1);
}if(len != sizeof(server) || memcmp((const void *)&server, (const void *)&peer, len) != 0)
{
printf("Receive message from other server.\n");continue;
}
buf[num]= '\0';
printf("Server Message: %s.\n", buf);break;
}
close(sockfd);
}
执行一下命令进行编译:
$ g++ -o UDPserver UDPserver.cpp
$ g++ -o UDPclient2 UDPclientWithConnect.cpp
$ g++ -o UDPclient1 UDPclientNoConnect.cpp
完了以后就看到三个可执行文件了。
打开一个命令行,执行./UDPserver启动服务端程序,再打开另外一个命令行,执行./UDPclient1 127.0.0.1 "nihaonihao"或者./UDPclient2 127.0.0.1 "testtest"即可查看到以下效果:
[horstxu@vps ~/Cprog/udpCSmodel]$ ./UDPserver
You got a messagefrom client.
It's ip is 127.0.0.1, port is 25595.
You got a message from client.
It's ip is 127.0.0.1, port is 27396.
[horstxu@vps ~/Cprog/udpCSmodel]$ ./UDPclient1 127.0.0.1 "nihaonihao"Server Message: Welcome
.
[horstxu@vps~/Cprog/udpCSmodel]$ ./UDPclient2 127.0.0.1 "testtest"Server Message: Welcome
.
[horstxu@vps~/Cprog/udpCSmodel]$
最后再来解释一个带有connect的UDP的好处。由于UDP是不可靠传输,如果我发了数据出去,对方其实服务器是关闭的,这时会有什么结果呢?对于刚才的UDPclient1,也就是不带connect的,客户端程序会卡在recvfrom这里,因为对方是关闭的,它永远也收不到来自对方的回包。但是对于UDPclient2,也就是带有connect,我们其实可以收到一个错误,并设置errno(errno:111,connection refused)。这样看上去就比卡死在那里友好多了。对于这个问题的具体分析可以参考这篇文章:
[horstxu@vps ~/Cprog/udpCSmodel]$ ./UDPclient2 127.0.0.1 "testtest"recv() error.
[horstxu@vps~/Cprog/udpCSmodel]$ ./UDPclient1 127.0.0.1 "testtest"#注释:程序在这里卡死