C/S模型之UDP协议
说明:
利用UDP协议,创建一个服务器和一个客户端。两者间进行通信。
由客户端进行输入内容,而服务器将接受的内容进行再一次返回,并显示在服务端。
// UDP_Seversock.cpp : 定义控制台应用程序的入口点。 #include "stdafx.h" #include <WinSock2.h> #include <Windows.h> #pragma comment (lib,"WS2_32.lib") //服务器端 int _tmain(int argc, _TCHAR* argv[]) { //检测版本 WSADATA wsaData; int nErr = WSAStartup(MAKEWORD(2, 2), &wsaData); if (nErr != 0) { printf("WSAStartup Failed %d", GetLastError()); return -1; } if (HIBYTE(wsaData.wVersion != 2 || wsaData.wVersion != 2)) { printf("WinSock2 version not is WS2_32"); WSACleanup(); return -1; } //开始 SOCKET sockSever = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //设置端口号和IP地址,INADDR_ANY是任意IP地址 SOCKADDR_IN sockSeverAddr; sockSeverAddr.sin_family = AF_INET; sockSeverAddr.sin_port = htons(10086); //sockSeverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); sockSeverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //将socket进行绑定 int nBindErr = bind(sockSever, (sockaddr*)&sockSeverAddr, sizeof(SOCKADDR_IN)); if (nBindErr == SOCKET_ERROR) { printf("bind Error!"); closesocket(sockSever); WSACleanup(); } printf("connect sucess!\n"); SOCKADDR_IN SenderAddr; int SenderAddrSize = sizeof (SenderAddr); char pRecvBuf[MAXBYTE] = { 0 }; char pPrintBuf[MAXBYTE] = { 0 }; while (1) { int nResult = recvfrom(sockSever, pRecvBuf, MAXBYTE, 0, (SOCKADDR *)&SenderAddr, &SenderAddrSize); if (nResult == SOCKET_ERROR) { printf("recvfrom failed with error "); } //重置,将IP地址和端口号,内容存放在pPrintBuf中 sprintf_s(pPrintBuf, "%s(%d):%s\n", inet_ntoa(sockSeverAddr.sin_addr), ntohs(sockSeverAddr.sin_port), pRecvBuf); //输出在窗口 printf("%s\n", pPrintBuf); //客户端请求退出 if (strcmp(pRecvBuf, "Q") == 0) { break; } int nSendResult = sendto(sockSever, pRecvBuf, MAXBYTE, 0, (sockaddr*)&SenderAddr, SenderAddrSize); if (nSendResult == SOCKET_ERROR) { printf("sendto Error!"); break; } } closesocket(sockSever); WSACleanup(); return 0; }
#include "stdafx.h" #include <WinSock2.h> #include <Windows.h> #pragma comment(lib,"WS2_32.lib") //客户端 int _tmain(int argc, _TCHAR* argv[]) { //socket版本检测 WSAData wsaData; int nErr=WSAStartup(MAKEWORD(2, 2), &wsaData); if (nErr != 0) { printf("WSAStartup Failed %d", GetLastError()); return -1; } if (HIBYTE(wsaData.wVersion) != 2 || LOBYTE(wsaData.wVersion) != 2) { printf("WinSock2 version not is WS2_32"); WSACleanup(); return -1; } //程序开始 SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //设定端口和IP地址,127.0.0.1是本机回环地址 SOCKADDR_IN sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(10086); sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int RecvAddrSize = sizeof (SOCKADDR_IN); //int nBindErr=bind(sockClient, (sockaddr*)&sockAddr, sizeof(SOCKADDR_IN)); //if (nBindErr == SOCKET_ERROR) //{ // printf("bind Error!"); // closesocket(sockClient); // WSACleanup(); // return -1; //} printf("connect sucess!\n"); char pSendBuf[MAXBYTE] = { 0 }; //发送服务器的内容 char pRecvBuf[MAXBYTE] = { 0 }; //接受服务器返回的内容 while (1) { gets_s(pSendBuf); //发送给服务器 int nResule = sendto(sockClient, pSendBuf, MAXBYTE, 0, (SOCKADDR *)& sockAddr, RecvAddrSize); if (nResule == SOCKET_ERROR) { printf("sendto Error!"); } //接受服务器返回的内容 int nRecvErr = recvfrom(sockClient, pRecvBuf, MAXBYTE, 0, (SOCKADDR *)& sockAddr, &RecvAddrSize); if (nRecvErr == SOCKET_ERROR) { break; } //服务器返回内容 printf("return from sever:%s\n", pRecvBuf); } closesocket(sockClient); WSACleanup(); return 0; }
注意点;
问题:为什么UDP协议的客户端的socket不需要绑定IP地址和端口号?
解析;
书上都是这么说的,UDP客户端不用绑定IP和端口,操作系统会给它自动分配端口。。。。
但是虽然没有显示绑定,但是操作系统却似乎做了些隐蔽的事情。
首先,在客户端,fd = socket(AF_INET, SOCK_DGRAM, 0),然后就想在此fd下进行recvfrom是收不到对方(假设对方就是服务器吧)的消息是办不到的,
其实想想也很容易明白,这是fd未和任何端口、IP产生关联要是这样都能收到消息,那真要乱套了。想要在没绑定的情况下受到服务器发来的消息,
首先客户端得通过fd描述符首先向服务器发信息,然后这时在fd下进行阻塞recvfrom就能收到消息了,如果再在客户端上fd 1= socket(AF_INET, SOCK_DGRAM, 0),
想在fd1 上进行recvfrom依然收不到消息,因为fd和服务器同过信,但fd1没有,所以fd1收不到,但是从fd1向服务器发消息没问题!所一总结一下就是,
只有当已通过fd向服务器发送了消息时(并且已经发通了),才能在fd处收到服务器发回来的消息,但是向服务器发送消息就不需要。
所以说操作系统在此做了些隐蔽的事情,当fd首先向服务器发消息时客户端自动选折IP和一个PORT与该fd关联了起来,(我觉得相当于背后还是绑定了一样)。
而后面创建的fd1和之前的fd他们出客户端的PORT是不同的(我在服务器端检测了一下),所以通过fd向服务器发了消息但想在新建立的fd1下去recvfrom收不到消息。
另外,只能对一个socket描述符绑定一次,不能绑定多次,除非前面已经将该描述符close了。
反过来一个端口也只能被绑定到同一个socket描述符上,除非他们使用的不同的协议。
注意:如果客户端绑定了,那么在服务器端将无法输出任何东西,在recvfrom()该函数阻塞。
1.每一次都要进行版本检测。
2.头文件的包含,#include <WinSock2.h>一定要在#include <Windows.h>的前面。
如:
#include <WinSock2.h>
#include <Windows.h>