C++ Socket编程及TCP/UDP通信代码实现 一、简介 Socket编程的目的是使网络的进程进行通信,基于TCP/IP协议簇,通过三元组(ip地址、协议、端口)标志进程,并通过该标志与其他进行进行交互。使用TCP/IP协议的应用程序通常采用应用编程接口,套接字Socket是当前的主流通信方式,“一切皆可Socket”。 Socket通信流程图: 二、Socket是什么 网络的进程通过Socket进行通信,Socket本身起源于Unix,基于“一切皆文件”理论,通过“打开->读写->关闭”的模式进行操作,而Socket本身也是基于这种模式,可以把Socket理解成一种特殊的文件,而Socket函数就是对其进行操作,如打开、读、写、关闭等。 三、Socket的基本操作 1.socket()函数 #include <sys/socket.h> int socket(int domain, int type, int protocol); 函数描述:创建一个socket描述符,唯一标志一个socket,用于后续的读写操作 参数解释: domain:协议域(协议簇),决定了socket的地址类型,常用的协议有AF_INET(IPV4互联网协议簇)、PF_INET6/AF_INET6(IPV6互联网协议簇)、AF_UNIX/AF_UNIX(要用一个绝对路径名作为地址)。 type:指socket类型,有面向连接的套接字(SOCK_STREAM)和面向消息的套接字(SOCK_DGRAM),其中面向连接的套接字可以理解成TCP协议,数据稳定、按序传输,不存在数据边界,且收发数据在套接字内部有缓冲,所以服务器和客户端进行I/O操作时并不会马上调用,可能分多次调用;面向消息的套接字可以看做UDP,特点:快速传输、有数据边界、数据可能丢失、传输数据大小受限。 protocol:指计算机间通信中使用的协议信息。一般都可以为0(当protocol为0时,会自动选择type类型对应的默认协议。),如果同一协议簇中存在多个数据传输方式相同的协议,则才用第三个参数。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。 2.bind()函数 #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 函数描述:把一个地址族中的特定地址赋给socket(如果一个TCP客户或者服务器不用bind绑定一个端口,则调用connect或listen时,内核就要为相应的套接字选择一个临时端口(一般TCP客户端可这样做,TCP服务器不可,因为服务器的端口是大家熟知的)) 参数解释: sockfd:指的是通过socket()创建的描述字,唯一标识一个socket。 addr:一个指针,指向要绑定的协议地址。 struct sockaddr_in { sa_family_t sin_family; //地址簇,取值AF_INET(IPV4),AF_INET6(IPV6) unit16_t sin_port; //16位TCP/UDP端口号,以网络字节序保存 struct in_addr sin_addr; //32位IP地址,以网络字节序保存 char sin_zero[8]; //不使用,但一般初始化为0 } struct in_addr { In_addr_t s_addr; //32位IP地址 } addrlen:对应的是地址的长度。 注意: 通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。 相关知识: 主机字节序:有大端和小端模式 (1)小端模式:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 (2)大端模式:高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。 //short是16位,一般用于端口号,long一般用于地址 unsigned short htons(unsigned short); //host to network把short类型数据 unsigned short ntohs(unsigned short); unsigned long htonl(unsigned long); unsigned long ntohl(unsigned long); //还有两个可以把字符串形式的IP地址转换成32位整数型数据,成功调用后返回32位大端整数 in_addr_t inet_addr(const char * string); int inet_aton(const char * string, struct in_addr * addr); //iner_aton可以将转换后的IP地址信息带入sockaddr_in结构体中声明in_addr结构体变量。 char * inet_ntoa(struct in_addr adr); //将网络字节序整数型IP地址转换成字符串 //以下两个函数ipv4和ipv6通用 //将点分十进制的ip地址转化为用于网络传输的数值格式 int inet_pton(int family, const char *strptr, void *addrptr); //将数值格式转化为点分十进制的ip地址格式 const char *inet_ntop(int family,const void *addrptr, char *strptr, size_t len); 因此,在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是大端模式。 3.listen()函数 #include <sys/socket.h> int listen(int sockfd, int backlog); 函数描述:listen将socket设置为被动监听类型,等待客户的连接请求。 参数解释: sockfd:要监听的socket描述字。 backlog:可以排队的最大连接个数。 4.connect()函数 #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 函数描述:客户端调用connect()来连接服务器,进行信息传输。 参数解释: sockfd:客户端的socket描述字。 addr:服务器的socket地址。 addrlen:socket地址的长度。 5.accept()函数 #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 函数描述:TCP服务器在依次调用socket()、bind()、listen()后,开始监听指定socket地址,接着调用accept()获取请求,建立连接;TCP客户端依次调用socket()、connect()就可以发送连接请求。 参数解释: sockfd:服务器的socket描述字。 addr:指向 struct sockaddr 的指针,用于返回客户端的协议地址。 addrlen:协议地址的长度。 返回值: 连接成功,会返回由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。 注意: 内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。 6.read()、write()函数 网络I/O操作有以上几组,推荐使用recvmsg() - sendmsg()。 read() - write() recv() - send() readv() - writev() recvmsg() - sendmsg() recvfrom() - sendto() #include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); #include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); 函数描述:上述描述了几组I/O操作的函数 参数解释: read:负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题 write:将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置error变量。 write的返回值大于0,表示写了部分或者是全部的数据。 返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。 7.close()函数 #include <unistd.h> int close(int fd); 函数描述:数据传输结束时调用close()函数。close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。 三、Win10下VS2019实现TCP Socket通信demo(多线程) 1.环境配置 在win10环境下直接使用 #include <sys/socket.h> 会报错,所以需要改用 #include <WinSock2.h> 实现通信,配置过程如下: [右键]项目->属性->链接器->输入->附加依赖项->编辑->添加ws2_32.lib,取消勾选"从父级或项目默认设置继承" 属性->C/C+±>常规->SDL检查 设置为否 配置结束后即可开始通信。 2.TCP服务器代码(多线程) #include <stdio.h> #include <WinSock2.h> //windows socket的头文件 #include <Windows.h> #include <iostream> #include <thread> #include <mutex> #include <process.h> #pragma comment(lib, "ws2_32.lib") //连接winsock2.h的静态库文件 using namespace std; //mutex 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。 //同一时刻,只能有一个线程持有该锁。 mutex m; //定义结构体用来设置 typedef struct my_file { SOCKET clientSocket; //文件内部包含了一个SOCKET 用于和客户端进行通信 sockaddr_in clientAddr; //用于保存客户端的socket地址 int id; //文件块的序号 }F; DWORD WINAPI transmmit(const LPVOID arg) { //实际上这里为了追求并发性不应该加锁,上锁是为了方便看输出 m.lock(); F* temp = (F*)arg; //获取文件的序号 //int file_id = temp->id; //获取客户机的端口号 //ntohs(temp -> clientAddr.sin_port); cout << "测试开始,等待客户端发送消息..." << endl; //从客户端处接受数据 char Buffer[MAXBYTE] = { 0 }; //缓冲区 recv(temp->clientSocket, Buffer, MAXBYTE, 0); //recv方法 从客户端通过clientScocket接收 cout << "线程" << temp->id << "从客户端的" << ntohs(temp->clientAddr.sin_port) << "号端口收到:" << Buffer << endl; //发送简单的字符串到客户端 const char* s = "Server file"; send(temp->clientSocket, s, strlen(s) * sizeof(char) + 1, NULL); cout << "线程" << temp->id << "通过客户端的" << ntohs(temp->clientAddr.sin_port) << "号端口发送:" << s << endl; m.unlock(); return 0; } int main() { //加载winsock库,第一个参数是winsocket load的版本号(2.3) WSADATA wsaData; WSAStartup(MAKEWORD(2, 3), &wsaData); //创建服务器端的socket(协议族, sokcet类型) SOCKET servSocket = socket(AF_INET, SOCK_STREAM, 0);//如果改成SOCK_DGRAM则使用UDP // 初始化socket信息 sockaddr_in servAddr; //服务器的socket地址,包含sin_addr表示IP地址,sin_port保持端口号和sin_zero填充字节 memset(&servAddr, 0, sizeof(SOCKADDR)); //初始化socket地址 //设置Socket的连接地址、方式和端口,并绑定 servAddr.sin_family = PF_INET; //设置使用的协议族 servAddr.sin_port = htons(2017); //设置使用的端口 servAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //设置绑定的IP地址 ::bind(servSocket, (SOCKADDR*)&servAddr, sizeof(SOCKADDR)); //将之前创建的servSocket和端口,IP地址绑定 HANDLE hThread[20]; //获取句柄 listen(servSocket, 20); //监听服务器端口,最多20个连接 for (int i = 0; i < 20; i++) { F* temp = new F; //创建新的传输结构体 sockaddr_in clntAddr; int nSize = sizeof(SOCKADDR); SOCKET clientSock = accept(servSocket, (SOCKADDR*)&clntAddr, &nSize); //temp数据成员赋值 temp->clientSocket = clientSock; temp->id = i + 1; temp->clientAddr = clntAddr; //通过句柄创建子线程 hThread[i] = CreateThread(NULL, 0, &transmmit, temp, 0, NULL); } //等待子线程完成 WaitForMultipleObjects(20, hThread, TRUE, INFINITE); cout << WSAGetLastError() << endl; //查看错误信息 //关闭socket,释放winsock closesocket(servSocket); WSACleanup(); cout << "服务器连接已关闭。" << endl; system("pause"); return 0; } 3.TCP客户端代码 #include <stdio.h> #include <WinSock2.h> //windows socket的头文件 #include <Windows.h> #include <iostream> #include <thread> #include <process.h> #pragma comment(lib, "ws2_32.lib") //连接winsock2.h的静态库文件 using namespace std; int main() { //加载winsock库 WSADATA wsadata; WSAStartup(MAKEWORD(2, 3), &wsadata); //客户端socket SOCKET clientSock = socket(PF_INET, SOCK_STREAM, 0); //初始化socket信息 //memset:作用是在一段内存块中填充某个给定的值,它对较大的结构体或数组进行清零操作的一种最快方法。 sockaddr_in clientAddr; memset(&clientAddr, 0, sizeof(SOCKADDR)); //设置Socket的连接地址、方式和端口 clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); clientAddr.sin_family = PF_INET; clientAddr.sin_port = htons(2017); //建立连接 connect(clientSock, (SOCKADDR*)&clientAddr, sizeof(SOCKADDR)); cout << "已建立连接。" << endl; //发送消息 char* s = new char[100]; cout << "请输入你要发送的文字消息: "; cin >> s; send(clientSock, s, strlen(s) * sizeof(char) + 1, NULL); cout << "已发送:" << s << endl; //接收消息 system("pause"); char Buffer[MAXBYTE] = { 0 }; recv(clientSock, Buffer, MAXBYTE, 0); cout << "通过端口:" << ntohs(clientAddr.sin_port) << "接收到:" << Buffer << endl; //关闭连接 closesocket(clientSock); WSACleanup(); cout << "客户端连接已关闭。" << endl; system("pause"); return 0; } 四、Win10下VS2019实现UDP Socket通信demo 1.环境配置 在win10环境下直接使用 #include <sys/socket.h> 会报错,所以需要改用 #include <WinSock2.h> 实现通信,配置过程如下: [右键]项目->属性->链接器->输入->附加依赖项->编辑->添加ws2_32.lib,取消勾选"从父级或项目默认设置继承" 属性->C/C+±>常规->SDL检查 设置为否 配置结束后即可开始通信。 2.UDP服务器 #include <Winsock2.h> #include <stdio.h> void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(1, 1); err = WSAStartup(wVersionRequested, &wsaData);//错误会返回WSASYSNOTREADY if (err != 0) { return; } if (LOBYTE(wsaData.wVersion) != 1 || //低字节为主版本 HIBYTE(wsaData.wVersion) != 1) //高字节为副版本 { WSACleanup(); return; } printf("server is operating!\n\n"); //创建用于监听的套接字 SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);//失败会返回 INVALID_SOCKET //printf("Failed. Error Code : %d",WSAGetLastError())//显示错误信息 SOCKADDR_IN addrSrv; //定义sockSrv发送和接收数据包的地址 addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); //绑定套接字, 绑定到端口 bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));//会返回一个SOCKET_ERROR //将套接字设为监听模式, 准备接收客户请求 SOCKADDR_IN addrClient; //用来接收客户端的地址信息 int len = sizeof(SOCKADDR); char recvBuf[100]; //收 char sendBuf[100]; //发 char tempBuf[100]; //存储中间信息数据 while (1) { //等待并数据 recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len); if ('q' == recvBuf[0]) { sendto(sockSrv, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrClient, len); printf("Chat end!\n"); break; } sprintf_s(tempBuf, "%s say : %s", inet_ntoa(addrClient.sin_addr), recvBuf); printf("%s\n", tempBuf); //发送数据 printf("Please input data: \n"); gets_s(sendBuf); sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrClient, len); } closesocket(sockSrv); WSACleanup(); } 3.UDP客户端 #include <Winsock2.h> #include <stdio.h> void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(1, 1); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return; } if (LOBYTE(wsaData.wVersion) != 1 || //低字节为主版本 HIBYTE(wsaData.wVersion) != 1) //高字节为副版本 { WSACleanup(); return; } printf("Client is operating!\n\n"); //创建用于监听的套接字 SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0); sockaddr_in addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//输入你想通信的她(此处是本机内部) addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); int len = sizeof(SOCKADDR); char recvBuf[100]; //收 char sendBuf[100]; //发 char tempBuf[100]; //存储中间信息数据 while (1) { printf("Please input data: \n"); gets_s(sendBuf); sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len); //等待并数据 recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrSrv, &len); if ('q' == recvBuf[0]) { sendto(sockSrv, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrSrv, len); printf("Chat end!\n"); break; } sprintf_s(tempBuf, "%s say : %s", inet_ntoa(addrSrv.sin_addr), recvBuf); printf("%s\n", tempBuf); //发送数据 } closesocket(sockSrv); WSACleanup(); } /****************************** C++完成UDP接收数据功能 #include <stdio.h> #include <WinSock2.h> #pragma comment(lib,"WS2_32.lib") int main(void) { WSADATA wsd; // 初始化Socket的变量 SOCKET s; // 用于通信的Socket句柄 SOCKADDR_IN sRecvAddr, sSendAddr; // 分别为接收地址和发送地址 USHORT uPort = 14555; // 通信端口 CHAR szBuf[1024] = { 0 }; // 通信数据缓冲区 int nBufLen = 1024, nResult = 0, nSenderAddrSize = sizeof(sSendAddr); // 初始化Socket2.2版本 nResult = WSAStartup(MAKEWORD(2, 2), &wsd); if (nResult != NO_ERROR) { printf("WSAStartup failed:%d\n", WSAGetLastError()); return 1; } // 创建一个Socket,SOCK_DGRAM表示UDP类型 s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) { printf("socket failed:%d\n", WSAGetLastError()); return 1; } // 填充Socket接口 sRecvAddr.sin_family = AF_INET; // 地址协议,AF_INET支持TCP和UDP sRecvAddr.sin_port = htons(uPort); // 通信端口,htons转为网络字节顺序 //INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP sRecvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 接收任意地址数据 // 绑定Socket至本机 nResult = bind(s, (SOCKADDR *)&sRecvAddr, sizeof(sRecvAddr)); if (nResult != 0) { printf("bind failed:%d\n", WSAGetLastError()); return 1; } printf("Waiting recv data...\n"); while (1) { // 阻塞式接收数据 nResult = recvfrom(s, szBuf, nBufLen, 0, (SOCKADDR *)&sSendAddr, &nSenderAddrSize); if (nResult == SOCKET_ERROR) { printf("recvfrom failed:%d\n", WSAGetLastError()); } else { //printf("SenderIP :%s\n", inet_ntoa(sSendAddr.sin_addr)); printf("SenderData:%s\n", szBuf); } } // 关闭Socket连接 nResult = closesocket(s); if (nResult == SOCKET_ERROR) { printf("closesocket failed:%d\n", WSAGetLastError()); return 1; } // 清理Socket WSACleanup(); system("pause"); return 0; } /*********************************** C++ UDP socket编程 客户端: //#include "stdafx.h" #include<stdio.h> #include<tchar.h> #include <iostream> #include <WinSock2.h> #include <Windows.h> using namespace std; #pragma comment(lib, "ws2_32.lib") int _tmain(int argc, _TCHAR* argv[]) { WSAData wsd; //初始化信息 SOCKET soSend; //发送SOCKET int nRet = 0; //int i = 0; int dwSendSize = 0; SOCKADDR_IN siLocal; //远程发送机地址和本机接收机地址 //启动Winsock if (WSAStartup(MAKEWORD(2,2),&wsd) != 0) {/*进行WinSocket的初始化, windows 初始化socket网络库,申请2,2的版本,windows socket编程必须先初始化。*/ cout << "WSAStartup Error = " << WSAGetLastError() << endl; return 0; } //创建socket //AF_INET 协议族:决定了要用ipv4地址(32位的)与端口号(16位的)的组合 //SOCK_DGRAM -- UDP类型,不保证数据接收的顺序,非可靠连接; soSend = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if (soSend == SOCKET_ERROR) { cout << "socket Error = " << WSAGetLastError() << endl; return 1; } //设置端口号 int nPort = 5150; siLocal.sin_family = AF_INET; siLocal.sin_port = htons(nPort); siLocal.sin_addr.s_addr = inet_addr("127.0.0.1"); for (int i = 0; i < 30; i ++){ //开始发送数据 //发送数据到指定的IP地址和端口 nRet = sendto(soSend,"123 mutouren",strlen("123 mutouren"),0,(SOCKADDR*)&siLocal,sizeof(SOCKADDR)); if (nRet == SOCKET_ERROR) { cout << "sendto Error " << WSAGetLastError() << endl; break; } } //关闭socket连接 closesocket(soSend); //清理 WSACleanup(); return 0; } 服务端: //#include "stdafx.h" #include<stdio.h> #include<tchar.h> #include <iostream> #include <WinSock2.h> #include <Windows.h> using namespace std; #pragma comment(lib, "ws2_32.lib") int _tmain(int argc, _TCHAR* argv[])//_tmain,要加#include <tchar.h>才能用 { WSAData wsd; //初始化信息 SOCKET soRecv; //接收SOCKET char * pszRecv = NULL; //接收数据的数据缓冲区指针 int nRet = 0; //int i = 0; int dwSendSize = 0; SOCKADDR_IN siRemote,siLocal; //远程发送机地址和本机接收机地址 //启动Winsock if (WSAStartup(MAKEWORD(2,2),&wsd) != 0) { cout << "WSAStartup Error = " << WSAGetLastError() << endl; return 0; } //创建socket soRecv = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if (soRecv == SOCKET_ERROR) { cout << "socket Error = " << WSAGetLastError() << endl; return 1; } //设置端口号 int nPort = 5150; //int nPort = 1234; siLocal.sin_family = AF_INET; siLocal.sin_port = htons(nPort); siLocal.sin_addr.s_addr = inet_addr("127.0.0.1"); //绑定本地地址到socket if (bind(soRecv,(SOCKADDR*)&siLocal,sizeof(siLocal)) == SOCKET_ERROR) { cout << "bind Error = " << WSAGetLastError() << endl; return 1; } //申请内存 pszRecv = new char[4096]; if (pszRecv == NULL) { cout << "pszRecv new char Error " << endl; return 0; } for (int i = 0; i < 30; i ++){ dwSendSize = sizeof(siRemote); //开始接受数据 nRet = recvfrom(soRecv,pszRecv,4096,0,(SOCKADDR*)&siRemote,&dwSendSize); if (nRet == SOCKET_ERROR) { cout << "recvfrom Error " << WSAGetLastError() << endl; break; } else if (nRet == 0) { break; } else{ pszRecv[nRet] = '\0'; cout << inet_ntoa(siRemote.sin_addr) << endl <<pszRecv << endl; } } //关闭socket连接 closesocket(soRecv); delete[] pszRecv; //清理 WSACleanup(); system("pause"); return 0; } /****************************************************** class UDP { public: UDP() { RecvAddrSize = sizeof(RecvAddr); int nResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (nResult != NO_ERROR) { std::cout << WSAGetLastError(); return; } RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //设置服务器地址 RecvAddr.sin_family = AF_INET; RecvAddr.sin_port = htons(Port); RecvAddr.sin_addr.s_addr = inet_addr("192.168.1.111"); int ret = bind(RecvSocket, (sockaddr *)&RecvAddr, sizeof(RecvAddr)); if (ret < 0) { perror("bind"); return; } } void sendDataToPython() {char temp[2000]; sprintf(temp, "insert into Flight_data.AirData({:}, cdata) values({:}, '{:}')", m_str, s_str, date_time); int flag = sendto(RecvSocket, temp, sizeof(temp), 0, (sockaddr*)&RecvAddr, sizeof(RecvAddr)); std::cout << flag; } } void recvDataToLocal() { memset(RecvBuf, '\0', sizeof(RecvBuf)); printf("recv a datagram to the receiver...\n"); int nResult = recvfrom(RecvSocket, RecvBuf, BufLen, 0, (SOCKADDR *)&RecvAddr, &RecvAddrSize); //发送完成,关闭Socket printf("%s\n", RecvBuf); } ~UDP() { closesocket(RecvSocket); WSACleanup(); } private: WSADATA wsaData;//初始化 SOCKET RecvSocket; sockaddr_in RecvAddr, sendAddr;//服务器地址 char RecvBuf[10240] = { 0 };//发送数据的缓冲区 int Port = 4000;//服务器监听地址 int BufLen = 10240;//缓冲区大小 int RecvAddrSize = 0; //初始化SocketAddr的大小 };
/**************************************
1、一个socket实现udp收发
socket用于udp通信时,是不区分Server与Client的。因为是无连接的,发送完了也就完了。同样接收到数据也就完成了一次通信。因此,Server端与Client端的措辞在Udp通信中的含义其实就退化了。
将socket用于tcp编程时,都比较喜欢send和recv函数。而用于udp通信编程时,个人感觉用sendto和recvfrom更方便。
因为socket用于udp通信时,如果用一种比较糙的方式实现,是无须connect操作、bind操作的。直接想要发送的时候用sendto指明接收端的参数,想要接收数据时直接用recvfrom指明发送端的参数。
2、粗糙方式
在这种粗糙的方式下,即不加任何bind、connect操作时,仅有一个缺点,也即接收时是无法指定发送方的端口。换句话说,监听的远端机器向本机任何一个端口发送数据,recvfrom都会认为收到数据了。
3、完全方式
而要Debug这个问题,添加一个bind操作即可。而且只需要bind一次。这样,需要的功能就都妥妥的了。指定远端ip的指定端口发送的数据才会触发本机recvfrom的接收。而发送功能,一直是好的。
4、代码
上一点代码。写的比较急,赶出来的。扎眼见谅啊。
1)、创建socket
//创建socket
m_sock = socket(AF_INET, SOCK_DGRAM, 0);
//设定远端地址、端口进行绑定
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.104");
addr.sin_family = AF_INET;
addr.sin_port = htonl(8889);
//绑定远端
int nRet = bind(m_sock, (sockaddr*)&addr, sizeof(addr));
TRACE("BINDRET = %d \r\n", nRet);
TRACE("errcode = %d \r\n", WSAGetLastError());
2)、发送代码
//设定目的端参数
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.104");
addr.sin_family = AF_INET;
addr.sin_port = htonl(8888);
char* buf="hehehahaheihei";
//发送数据
int sendRet=sendto(m_sock, buf, sizeof(buf), 0, (sockaddr*)&addr, sizeof(addr));
TRACE("Send errcode = %d \r\n", WSAGetLastError());
TRACE("sendRet= %d \r\n", sendRet);
3)、接收代码
//存储发送端的参数结构体,可取出ip地址端口号等
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
//接收缓冲区、参数等
char rcvBuf[20];
int len = 20;
int stSize=sizeof(addr);
int rcvRet = 0;
len = sizeof(addr);
rcvRet=recvfrom(m_sock, rcvBuf, len, 0, (sockaddr*)&addr, &stSize);
//打印出错误信息码
TRACE("rcv errcode = %d \r\n", WSAGetLastError());
TRACE("rcvRet= %d\r\n", rcvRet);
5、注意事项
1)、socket默认是阻塞的
阻塞不影响发送,因为直接发送出去立即就返回了,不管对方是否接收到。
而接收时就会阻塞了,会一直等到有数据到来。
设置为非阻塞代码
ULONG nVal = 1;
ioctlsocket(m_sock, FIONBIO, &nVal);
2)、启动函数
在创建socket之前需要调用启动函数
WSAStartup(
WORD wVersionRequested, //版本号
LPWSADATA lpWSAData //返回的参数信息。详见msdn
);
具体调用样例如下:文章来源:https://www.toymoban.com/news/detail-791415.html
WSADATA data;
WSAStartup(0X11,&data);
对称的,有一个清理函数。在退出时可以调用。)
文章来源地址https://www.toymoban.com/news/detail-791415.html
到了这里,关于C++中UDP通讯详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!