这里使用“2030”作为服务程序的端口,使用“INADDR_ANY”作为绑定的IP地址即任何主机上的地址。
(3)使用bind()把上面的socket和定义的IP地址和端口绑定。这里检查bind()是否执行成功,如果有错误就退出。这样可以防止服务程序重复运行的问题。
(4)进入无限循环程序,使用recvfrom()进入等待状态,直到接收到客户程序发送的数据,就处理收到的数据,并向客户程序发送反馈。这里是直接把收到的数据发回给客户程序。
2、srv.cpp程序内容
#include <stdio.h> #include <sys/socket.h> #include <string.h> #include <netinet/in.h> int main() { int srvSock=socket(AF_INET, SOCK_DGRAM, 0); if(srvSock==-1) { perror("创建套接字出错\n"); return 1; } struct sockaddr_in srvAddr, clientAddr; srvAddr.sin_family=AF_INET; srvAddr.sin_addr.s_addr=htonl(INADDR_ANY); srvAddr.sin_port=htons(2030); if(bind(srvSock, (struct sockaddr *)&srvAddr, sizeof(srvAddr))==-1) { perror("绑定失败\n"); return 1; } socklen_t len=sizeof(srvAddr); while(1) { char recvMsg[128]={0}; char sendMsg[128]={0}; memcpy(sendMsg, "hello, client", 128); recvfrom(srvSock, recvMsg, 128, 0, (struct sockaddr *)&clientAddr, &len); printf("接收到数据%s\n", recvMsg); sendto(srvSock, sendMsg, 128, 0, (struct sockaddr *)&clientAddr, len); printf("发送数据%s\n", sendMsg); } return 0; }
1、编写UDP Client程序的步骤
(1)初始化sockaddr_in结构的变量,并赋值。
这里使用“2030”作为连接的服务程序的端口,从命令行参数读取IP地址,并且判断IP地址是否符合要求。
(2)使用socket()来建立一个UDP socket,第二个参数为SOCK_DGRAM。
(3)使用connect()来建立与服务程序的连接。
与TCP协议不同,UDP的connect()并没有与服务程序三次握手。上面说了UDP是非连接的,实际上也可以是连接的。使用连接的UDP,kernel可以直接返回错误信息给用户程序,从而避免由于没有接收到数据而导致调用recvfrom()一直等待下去,看上去好像客户程序没有反应一样。
(4)向服务程序发送数据,因为使用连接的UDP,所以使用write()来替代sendto()。这里的数据直接从标准输入读取用户输入。
(5)接收服务程序发回的数据,同样使用read()来替代recvfrom()。
(6)处理接收到的数据,这里是直接输出到标准输出上。
注意:
1. 在linux操作系统中,套接字可视为文件,在调用connect()函数之后就可以调用read()和write()直接在其上进行接收和发送。
2. 当用recvfrom()函数接收的时候并没有错误提示,而当用read()函数读取的时候有错误提示。原因尚未知。
client.cpp程序内容:#include <netinet/in.h> #include <string.h> #include <sys/socket.h> #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> int main() { int clientSock=socket(AF_INET, SOCK_DGRAM, 0); if(clientSock==-1) { perror("建立套接字出错\n"); return 1; } struct sockaddr_in destAddr; destAddr.sin_family=AF_INET; destAddr.sin_addr.s_addr=inet_addr("127.0.0.1"); destAddr.sin_port=htons(2030); socklen_t len=sizeof(destAddr); connect(clientSock, (struct sockaddr *)&destAddr, len); /*即便链接出错,也不会返回任何信息 if(connect(clientSock,(struct sockaddr *)&destAddr, len)==-1) { perror("与服务器连接失败\n"); return 1; } */ char sendMsg[128]={0}; memcpy(sendMsg, "hello, server", 128); char recvMsg[128]={0}; /* 用sendto()和recvfrom()没有链接错误提示 sendto(clientSock, sendMsg, 128, 0, (struct sockaddr *)&destAddr, len); while(1) { char recvMsg[128]={0}; recvfrom(clientSock, recvMsg, 128, 0, (struct sockaddr *)&destAddr, &len); printf("接收到数据%s\n", recvMsg); break; } */ write(clientSock, sendMsg, 128); while(1) { int n=read(clientSock, recvMsg, 128); if(n==-1) { perror("read error"); return 1; } recvMsg[n]=0; printf("接收到数据%s\n", recvMsg); break; } }运行例子程序
1、编译例子程序
使用如下命令来编译例子程序:
gcc -Wall -o srv srv.cpp
gcc -Wall -o client client.cpp
编译完成生成了udpserv和udpclient两个可执行程序。
2、运行UDP Server程序
执行./srv &命令来启动服务程序。我们可以使用netstat -ln命令来观察服务程序绑定的IP地址和端口,部分输出信息如下:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
udp 0 0 0.0.0.0:2030 0.0.0.0:*
可以看到udp处有“0.0.0.0:2030”的内容,说明服务程序已经正常运行,可以接收主机上任何IP地址且端口为2030的数据。
如果这时再执行./srv &命令,就会看到如下信息:
绑定失败:
Address already in use
说明已经有一个服务程序在运行了。
出现这种情况可以用命令kill结束正在运行的程序,并重新打开。
3、运行UDP Client程序
执行./client 命令来启动客户程序,使用127.0.0.1来连接服务程序,执行效果如下:
接收到数据hello, client
输入的数据都正确从服务程序返回了,按ctrl+c可以退出程序。
如果服务程序没有启动,而执行客户程序,就会看到如下信息:
read error: Connection refused
说明指定的IP地址和端口没有服务程序绑定,客户程序就退出了。
这就是使用connect()的好处,注意,这里错误信息是在向服务程序发送数据后收到的,而不是在调用connect()时。
如果使用tcpdump程序来抓包,会发现收到的是ICMP的错误信息。