【干货】Windows平台基于udp的socket网络编程开发

9 篇文章 0 订阅
3 篇文章 0 订阅

使用UDP进行windows平台下的socket网络编程,本人在vs 2019编译器上开发,语言C++,希望能为大家提供一些参考。

首先建立两个项目,分别称之为客户端和服务端,唯一的区别是服务端需要绑定IP和端口,客户端指明服务端的IP和端口,这样就可以通信啦。

首先是写一个简单的客户端发送字符串到服务端的程序,因为比较简单,直接上代码,其中的注释很详细,有不明白的小伙伴可以评论区或者私信~

字符串收发

UDP_Client.cpp

#include <stdio.h>
#include <tchar.h>
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
#include <WS2tcpip.h>
#include <string>
using namespace std;

#pragma comment(lib, "ws2_32.lib")
int _tmain1(int argc, _TCHAR* argv[])
{
    cout << "hello world" << endl;
    WSAData wsd;           //初始化信息
    SOCKET soSend;         //发送到的目的SOCKET
    int nRet = 0;
    int dwSendSize = 0;
    const int SIZEOFBUF = 65500;
    char recvBuf[SIZEOFBUF];
    SOCKADDR_IN serverAddr{};    //服务器socket地址

    //启动Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {/*进行WinSocket的初始化,
        windows 初始化socket网络库,申请2,2的版本,windows socket编程必须先初始化。*/
        cout << "WSAStartup Error = " << WSAGetLastError() << endl;
        return 0;
    }
    else {
        cout << "WSAStartup Success" << endl;
    }

    //创建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;
    }
    else {
        cout << "socket Success" << endl;
    }

    //设置端口号
    int nPort = 5150; // 服务器的端口号
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(nPort);
    //serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    inet_pton(AF_INET, "127.0.0.1", (void*)&serverAddr.sin_addr.s_addr);

    /*
    for (int i = 0; i < 30; i++) {
        //开始发送数据
        //发送数据到指定的IP地址和端口
        string* desc = new string("123 mutouren");
        const char* cc = (desc->append(to_string(i).c_str())).c_str();
        nRet = sendto(soSend, cc, strlen(cc), 0, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR));
        if (nRet == SOCKET_ERROR || nRet < 0) {
            cout << "sendto Error " << WSAGetLastError() << endl;
            break;
        }
        else {
            cout << "sendto Success!!" << nRet << endl;
        }
    }
    */

    while (1) {
    	// 不断输入一些字符串,回车发送
        printf("input: ");
        scanf_s("%s", recvBuf, sizeof(recvBuf));
        //发送数据到指定的IP地址和端口
        nRet = sendto(soSend, recvBuf, strlen(recvBuf)+1, 0, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR));
        if (nRet == SOCKET_ERROR || nRet < 0) {
            cout << "sendto Error " << WSAGetLastError() << endl;
            break;
        }
        else {
            cout << "sendto Success!!" << nRet << endl;
        }
    }

    //关闭socket连接
    closesocket(soSend);
    //清理
    WSACleanup();

    return 0;
}

服务端的功能也很简单,就是使用while不断等待数据到来复制到本地分配的地址空间中,然后解析客户端的IP地址、端口号、和字符串信息。

UDP_Server.cpp

#include<stdio.h>
#include<tchar.h>
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
#include <WS2tcpip.h>
using namespace std;

#pragma comment(lib, "ws2_32.lib")
int _tmain1(int argc, _TCHAR* argv[])//_tmain,要加#include <tchar.h>才能用
{
    WSAData wsd;           //初始化信息
    SOCKET soRecv;         //接收的SOCKET
    char* pszRecv = NULL;  //接收数据的数据缓冲区指针
    int nRet = 0;          //接收数据大小
    int dwSendSize = 0;
    // MTU以太网数据帧的长度在46-1500字节之间,1500:链路层的最大传输单元
    // 单个UDP传输的最大内容为1472字节(1500-20-8)
    // 网络中标准UDP值为576字节,最好在编程中将数据长度控制在548字节以内
    int SIZEOFBUF = 65536; //接收数组大小
    int nPort = 5150;      //设置本机服务的端口号
    SOCKADDR_IN siRemote{}, siLocal{};    //远程发送机地址和本机接收机地址

    //启动Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
        cout << "WSAStartup Error = " << WSAGetLastError() << endl;
        return 0;
    }
    else {
        cout << "start Success" << endl;
    }

    siLocal.sin_family = AF_INET;
    siLocal.sin_port = htons(nPort);
    //siLocal.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    //和上一行一样
    inet_pton(AF_INET, "127.0.0.1", (void*)&siLocal.sin_addr.s_addr);

    //创建socket
    soRecv = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (soRecv == SOCKET_ERROR) {
        cout << "socket Error = " << WSAGetLastError() << endl;
        closesocket(soRecv);
        WSACleanup();
        return 1;
    }
    else {
        cout << "socket Success" << endl;
    }

    char on = 1;
    // 设置端口复用
    setsockopt(soRecv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    // 绑定本地地址到socket
    if (bind(soRecv, (SOCKADDR*)&siLocal, sizeof(siLocal)) == SOCKET_ERROR) {
        cout << "bind Error = " << WSAGetLastError() << endl;
        closesocket(soRecv);
        WSACleanup();
        return 0;
    }
    else {
        cout << "bind Success" << endl;
    }
    
    //申请内存
    pszRecv = new char[SIZEOFBUF];
    if (pszRecv == NULL) {
        cout << "pszRecv new char Error " << endl;
        return 0;
    }
    else {
        cout << "pszRecv new char Success" << endl;
    }

    // 一直等待数据
    while(true){
        dwSendSize = sizeof(siRemote);
        cout << "...开始等待数据..." << endl;
        memset(pszRecv, 0, SIZEOFBUF);
        //开始接受数据
        nRet = recvfrom(soRecv, pszRecv, SIZEOFBUF, 0, (SOCKADDR*)&siRemote, &dwSendSize);
        if (nRet == SOCKET_ERROR) {
            cout << "recvfrom Error " << WSAGetLastError() << endl;
            continue;
        }
        else if (nRet == 0) {
            cout << "recvfrom Error " << WSAGetLastError() << endl;
            continue;
        }
        else {
            pszRecv[nRet] = '\0';
            char sendBuf[20] = { '\0' };
            inet_ntop(AF_INET, (void*)&siRemote.sin_addr, sendBuf, 16);
            cout << "收到数据大小: " << nRet << " IP地址: " << sendBuf << " 端口号: " << siRemote.sin_port << " 数据: " << pszRecv << endl;
        }
    }
    //关闭socket连接
    closesocket(soRecv);
    delete[] pszRecv;

    //清理
    WSACleanup();
    system("pause");
    return 0;
}

运行示例

在这里插入图片描述

文件上传

接下来使用UDP进行客户端和服务端之间的文件上传。可以支持各种文件类型,包括txt等文本文件,jpg等图像文件,mov等视频文件。

使用流程很简单,我们先后启动服务端和客户端,然后在客户端输入一个文件名(完整绝对路径或者和客户端代码同文件夹下的文件名),回车,就可以把存在于客户端上的该文件上传到服务器当前目录中去。类似一般的文件上传,如果带有GUI的话,操作就变为弹出窗口,用户点击选择文件,然后点击上传啦。

首先看一下服务端,在服务端一共开启两个线程,主线程首先接收客户端发来的hello消息,用来解析记录客户端的信息,接着while循环一直等待用户输入字符串,然后发往客户端去。另外一个线程用来循环等待recv接收客户端发来的消息,通过检测每次收到的数据包的前四个字节,判断id是1还是2,如果是1代表接收的是文件数据,如果是2代表接受的是文件名和文件大小。我们设计的逻辑是,客户端在发送文件数据之前,先发送一个存有id为2、文件名、文件大小的数据包,接着将文件数据按照固定1024个字节依次发送给服务端。

我们知道UDP是基于数据报的协议,不可靠不保证数据的有序性,那么我们怎么把乱序的数据合成完整的文件呢?这里我们自己设置索引进行排序,只要保证发送端按照顺序向后读取字节,服务端即使收到乱序的数据包,也能按照索引恢复。

服务端首先收到文件信息,接着根据文件大小创建内存空间,根据文件名新建文件,等待数据的接收操作。等客户端发送的数据到来之后(id为1),就把数据按照index索引存放到正确的位置。如何判断文件是否接收完毕呢?我们使用变量receivedlen记录已经接收的数据大小,当此变量和文件大小相等的时候,我们把内存数据写入到事先新建的文件中去。

UDP_Server2.cpp

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<tchar.h>
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
#include <WS2tcpip.h>
using namespace std;

struct FileData {
	int id; // 用于服务端接受发送文件的类型
	int index; // 卡车的索引
	char filedata[1024]; // 卡车的数据
};

SOCKET soRecv;         //接收的SOCKET


DWORD WINAPI ThreadFunc(LPVOID p);
void startServer(SOCKET soRecv);
BOOL WINAPI CtrlFun(DWORD dwType);

BOOL WINAPI CtrlFun(DWORD dwType) {
	switch (dwType)
	{
	case CTRL_CLOSE_EVENT:
		printf("关闭socket,退出");
		closesocket(soRecv);
		WSACleanup();
	default:
		break;
	}
	return FALSE;
}


int main() {

	SetConsoleCtrlHandler(CtrlFun, TRUE);

	WSAData wsd;           //初始化信息
	
	// MTU以太网数据帧的长度在46-1500字节之间,1500:链路层的最大传输单元
	// 单个UDP传输的最大内容为1472字节(1500-20-8)
	// 网络中标准UDP值为576字节,最好在编程中将数据长度控制在548字节以内
	int SIZEOFBUF = 65536; //接收数组大小
	int nPort = 5150;      //设置本机服务的端口号
	SOCKADDR_IN siLocal{};    //远程发送机地址和本机接收机地址
	
	//启动Winsock
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
		cout << "WSAStartup Error = " << WSAGetLastError() << endl;
		return 0;
	}
	else {
		cout << "开始winsock成功" << endl;
	}

	//创建socket
	soRecv = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (soRecv == SOCKET_ERROR) {
		cout << "socket创建失败 = " << WSAGetLastError() << endl;
		closesocket(soRecv);
		WSACleanup();
		return 1;
	}
	else {
		cout << "socket创建成功" << endl;
	}

	char on = 1;
	// 设置端口复用
	setsockopt(soRecv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	int nRecvBuf = 4 * 1024 * 1024;//设置为4M
	setsockopt(soRecv, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int));

	//设置为非阻塞模式  
	/*
	int imode = 1;
	int rev = ioctlsocket(soRecv, FIONBIO, (u_long*)&imode);
	if (rev == SOCKET_ERROR)
	{
		printf("ioctlsocket failed!");
		closesocket(soRecv);
		WSACleanup();
		return -1;
	}
	*/

	siLocal.sin_family = AF_INET;
	siLocal.sin_port = htons(nPort);
	//siLocal.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	//和上一行一样
	inet_pton(AF_INET, "127.0.0.1", (void*)&siLocal.sin_addr.s_addr);
	//绑定本地地址到socket
	if (bind(soRecv, (SOCKADDR*)&siLocal, sizeof(siLocal)) == SOCKET_ERROR) {
		cout << "绑定错误 = " << WSAGetLastError() << endl;
		closesocket(soRecv);
		WSACleanup();
		return 0;
	}
	else {
		cout << "绑定成功" << endl;
	}
	
	startServer(soRecv);
	return 0;
}

void startServer(SOCKET soRecv) {
	char recvbuf[1024] = { 0 };
	SOCKADDR_IN siClient{};
	memset(recvbuf, 0, sizeof(recvbuf));
	int cli_addr_size = sizeof(siClient);
	int bytes = 0;
	// 设置非阻塞,这句话就会越过去
	// 开始接受数据,并把客户端信息填入到siClient中,下面的sendTo才能正确发送
	bytes = recvfrom(soRecv, recvbuf, sizeof(recvbuf), 0, (SOCKADDR*)&siClient, &cli_addr_size);
	if (bytes < 0) perror("recvFrom error");

	printf("客户端对服务器说: %s\n", recvbuf);

	// 创建一个线程
	HANDLE hThread;
	DWORD  threadId;

	hThread = CreateThread(NULL, 0, ThreadFunc, (LPVOID)soRecv, 0, &threadId); // 创建线程

	char sendbuf[1024] = { 0 };
	while (1) {
		memset(sendbuf, 0, sizeof(sendbuf));
		scanf_s("%s", sendbuf, sizeof(sendbuf));
		bytes = sendto(soRecv, sendbuf, strlen(sendbuf)+1, 0, (SOCKADDR*)&siClient, sizeof(SOCKADDR));
		if (bytes == SOCKET_ERROR || bytes < 0) {
			cout << "sendto Error " << WSAGetLastError() << endl;
			break;
		}
		else {
			cout << "sendto Success!!大小:" << bytes << endl;
		}
	}
}

DWORD WINAPI ThreadFunc(LPVOID p)
{
	int soRecv = (int)p;
	printf("服务端等待接收数据\n");
	char recvbuf[1400] = { 0 };
	int bytes = 0; //接收数据大小
	int dwSendSize = 0;
	// 文件句柄
	FILE* openfd = nullptr;
	SOCKADDR_IN siRemote{};
	// 文件数据的地址空间
	char* pRecvData = NULL;  //接收数据的数据缓冲区指针
	
	fd_set rfd;                     // 描述符集 这个将用来测试有没有一个可用的连接
	struct timeval timeout;
	timeout.tv_sec = 0;               //等下select用到这个
	timeout.tv_usec = 0;              //timeout设置为0,可以理解为非阻塞
	int selectRcv;

	while (1) {
		// UDP数据接收
		/*
		FD_ZERO(&rfd);           //总是这样先清空一个描述符集
		FD_SET(soRecv, &rfd); //把sock放入要测试的描述符集
		selectRcv = select(soRecv + 1, &rfd, 0, 0, &timeout); //检查该套接字是否可读

		if (selectRcv < 0)
			cout << "服务端监听失败" << GetLastError() << endl;
		if (selectRcv > 0)
		{
		*/
			memset(recvbuf, 0, sizeof(recvbuf));
			dwSendSize = sizeof(siRemote);
			//开始接受数据
			bytes = recvfrom(soRecv, recvbuf, sizeof(recvbuf), 0, (SOCKADDR*)&siRemote, &dwSendSize);
			if (bytes == SOCKET_ERROR || bytes == 0) {
				cout << "recvfrom Error " << WSAGetLastError() << endl;
				continue;
			}
			else if (bytes == 9) {
				cout << "服务端接收到: " << recvbuf << endl;
				continue;
			}
			else {
				//recvbuf[bytes] = '\0';
				//char sendBuf[20] = { '\0' };
				//inet_ntop(AF_INET, (void*)&siRemote.sin_addr, sendBuf, 16);
				//cout << "收到数据大小: " << bytes << " IP地址: " << sendBuf << " 端口号: " << siRemote.sin_port << " 数据: " << recvbuf << endl;
			}

			// 接收到文件名
			char filename[64] = { 0 };
			// 接收到文件大小
			static int filesize = 0;
			// 已经接收的文件大小
			static int receivedlen = 0;


			int id = *(int*)recvbuf;
			//printf("拆分id: %d\n", id);

			switch (id)
			{
			case 1: // 运送数据
			{
				FileData* Fdd = (FileData*)recvbuf;
				//printf("Fdd->id: %d Fdd->index: %d\n", Fdd->id, Fdd->index);

				// 将卡车的物品放到指定位置
				memcpy(pRecvData + (Fdd->index) * 1024, Fdd->filedata, bytes - sizeof(int) * 2);
				receivedlen += bytes - sizeof(int) * 2;
			}
			break;
			case 2: // 运送文件的大小和文件名
			{
				memcpy(&filesize, recvbuf + sizeof(int), sizeof(int));
				strncpy_s(filename, recvbuf + sizeof(int) * 2, 256);
				pRecvData = (char*)malloc(sizeof(char) * filesize);

				// 清空堆空间的数据
				//memset(pRecvData, 0, sizeof(pRecvData));

				printf("客户端发送的文件信息: 文件名称: %s, 文件大小: %d\n", filename, filesize);

				fopen_s(&openfd, filename, "wb");
				if (openfd == NULL) perror("create error");
			}
			break;
			default:
				printf("服务端接收错误");
				break;
			}

			// 当文件接受完了,需要关闭文件
			if (receivedlen == filesize && receivedlen != 0) {
				// 数据接受完毕
				printf("服务端数据接受完毕\n");

				// 讲所有数据写入文件
				fwrite(pRecvData, sizeof(char), receivedlen, openfd);
				// 文件接受完了需要关闭文件
				fclose(openfd);
				receivedlen = 0;
			}
		/*}
		else {
			//返回值为0,表示超时
			//cout << "超时" << endl;
		}
		*/
	}
	free(pRecvData);
	pRecvData = NULL;
}

客户端的逻辑也相似,同样是开两个线程,主线程用来发送文件数据,另一个线程用来接收服务端反馈的消息。

客户端需要先给服务器发送一个hello消息,以便让服务端解析出这个客户端。程序开始后命令行等待用户输入文件名,先计算文件大小和拼接文件名和后缀,然后发送第一个数据包,里面是id为2,文件名,和文件大小。接着读取用户选择上传的那个文件,按照字节顺序发送,每次1032个字节,直到文件末尾。

UDP_Client.cpp

#include <stdio.h>
#include <tchar.h>
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
#include <WS2tcpip.h>
#include <string>
using namespace std;
const int SIZEOFBUF = 1024;
int getFileLength(const char* filename);
DWORD WINAPI ThreadFunc(LPVOID p);
int sock_init();

struct FileData {
    int id; // 用于服务端接受发送文件的类型
    int index; // 卡车的索引
    char filedata[SIZEOFBUF]; // 卡车的数据
};

FileData Fdd;

#pragma comment(lib, "ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
    int soSend = sock_init();
    
    //关闭socket连接
    closesocket(soSend);
    //清理
    WSACleanup();

    return 0;
}


int sock_init() {
    cout << "hello world" << endl;
    WSAData wsd;           //初始化信息
    SOCKET soSend;         //发送到的目的SOCKET
    int nRet = 0;
    int dwSendSize = 0;
    
    //char recvBuf[SIZEOFBUF];
    SOCKADDR_IN serverAddr{};    //服务器socket地址

    //启动Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {/*进行WinSocket的初始化,
        windows 初始化socket网络库,申请2,2的版本,windows socket编程必须先初始化。*/
        cout << "WSAStartup Error = " << WSAGetLastError() << endl;
        return 0;
    }
    else {
        cout << "WSAStartup启动成功" << endl;
    }

    //创建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;
    }
    else {
        cout << "socket新建成功" << endl;
    }

    //设置端口号
    int nPort = 5150; // 服务器的端口号
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(nPort);
    //serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    inet_pton(AF_INET, "127.0.0.1", (void*)&serverAddr.sin_addr.s_addr);

    // 针对丢包问题,要么减小流量,要么换tcp协议传输,要么做丢包重传的工作

    int nRecvBuf = 4 * 1024 * 1024;//设置为32K
    setsockopt(soSend, SOL_SOCKET, SO_SNDBUF, (const char*)&nRecvBuf, sizeof(int));

    // 服务器端设置非阻塞接收,下面开启了接收线程,如果不先sendto,下面的接收函数里面的recvFrom就一直非阻塞跳出
    sendto(soSend, "say hello", strlen("say hello"), 0, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR));

    // 创建一个线程,创建完了之后,线程函数开始执行,且与主线程同时执行
    HANDLE hThread;
    DWORD  threadId;

    hThread = CreateThread(NULL, 0, ThreadFunc, (LPVOID)soSend, 0, &threadId); // 创建线程


    // 发送数据
    char filename[256] = { 0 };
    char fileinfo[1024] = { 0 };
    int filesize = 0;
    int fileId = 2;
    while (1) {
        printf("\ninput: ");
        scanf_s("%s", filename, sizeof(filename));
        
        filesize = getFileLength(filename);
        printf("即将发送的文件大小%d\n", filesize);
        if (filesize == -1) break;

        fileId = 2;

        // 内存拷贝,将id拷贝到缓冲区的前4个字节
        memcpy(fileinfo, &fileId, sizeof(int));

        // 内存拷贝,将文件大小拷贝到缓冲区的前4个字节之后
        memcpy(fileinfo + sizeof(int), &filesize, sizeof(int));

        char drive[5];
        char dir[100];
        char tfileName[100];
        char suffix[10];
        _splitpath_s(filename, drive, dir, tfileName, suffix);
        strcat_s(tfileName, suffix);
        printf("发送服务端的文件名拼为:%s\n", tfileName);

        // 将文件的名称放到缓冲区
        memcpy(fileinfo + sizeof(int) * 2, tfileName, strlen(tfileName) + 1);

        // 将文件id,大小和文件名称一起发送给服务器
        int ret = sendto(soSend, fileinfo, sizeof(int) * 2 + strlen(tfileName)+1, 0, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR));
        printf("文件名%s已发送, 大小:%d\n", tfileName, ret);

        // 发送文件中的数据
        FILE* pFile;
        fopen_s(&pFile, filename, "rb");
        if (pFile == NULL) {
            perror("打开文件错误");
            return soSend;
        }

        int bytes = 0;
        int index = 0;
        while ((bytes = fread_s(Fdd.filedata, sizeof(Fdd.filedata), sizeof(char), sizeof(Fdd.filedata), pFile)) != 0)
        {
            //printf("读取字节数bytes:%d\n", bytes);
            // 卡车的类型
            Fdd.id = 1;

            // 卡车索引号
            Fdd.index = index++;

            //发送数据到指定的IP地址和端口
            nRet = sendto(soSend, (char*)&Fdd, bytes+sizeof(int)*2, 0, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR));
            if (nRet == SOCKET_ERROR || nRet < 0) {
                cout << "发送数据错误" << WSAGetLastError() << endl;
                break;
            }
            else {
                //cout << "发送成功,数据大小" << nRet << endl;
            }
            Sleep(5);
        }
        fclose(pFile);
        printf("客户端文件发送完毕\n");
    }
    return soSend;
}

int getFileLength(const char* filename) {
    FILE* fd;
    fopen_s(&fd, filename, "r");
    if (fd == NULL) {
        perror("打开文件名失败: ");
        return -1;
    }

    // 获取文件长度
    struct stat statbuf;
    stat(filename, &statbuf);
    int size = statbuf.st_size;
    fclose(fd);
    return size;
}

//获取文件大小
int filelength(FILE* fp)
{
    int num;
    fseek(fp, 0, SEEK_END);
    num = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    return num;
}


DWORD WINAPI ThreadFunc(LPVOID p)
{
    int sockfd = (int)p;
    printf("我是子线程, pid = %d\n", GetCurrentThreadId());   //输出子线程pid
    char recvBuf[SIZEOFBUF] = { 0 };
    while (1) {
        recvfrom(sockfd, recvBuf, SIZEOFBUF, 0, NULL, 0);
        printf("服务器对客户端说%s\n", recvBuf);
    }
    return 0;
}

运行示例

在这里插入图片描述
当开多个客户端的时候,同样可以上传文件,但是服务端返回信息的时候,只有第一次连接到服务端的客户端能收到消息。这是因为只有在第一次接收到客户端的hello信息的时候siClient才内赋值,后面就只会往这个客户端地址发消息。
在这里插入图片描述
至此,此次使用udp进行简单操作的实践完毕,接下来可能还会深入研究加入线程池、保证不丢包、超时重传等功能,如果有感兴趣的小伙伴可以找我共同学习交流。

好的,下面我会详细讲解一下C语言编程的步骤,希望对您有所帮助。 1. 定义问题 定义问题是编写程序的第一步,也是最重要的一步。在这一步中,需要明确程序的目标和要解决的问题。具体来说,需要回答以下问题: - 你的程序要实现什么功能? - 你的程序要处理什么类型的数据? - 你的程序需要输入什么数据? - 你的程序需要输出什么数据? 定义问题的过程中,需要尽可能详细地描述程序的目标和要解决的问题。这样有助于后续步骤的顺利进行。 2. 设计算法 设计算法是编写程序的核心步骤。在这一步中,需要根据问题的需求,设计程序的算法,选择合适的数据结构和算法策略。具体来说,需要完成以下工作: - 确定程序的输入和输出。 - 根据数据的类型和大小,选择合适的数据结构。 - 设计程序的逻辑流程,包括条件判断、循环结构等。 - 确定算法的复杂度,评估程序的性能。 在这一步中,需要注意算法的正确性和可读性。算法应该能够正确地解决问题,并且易于理解和维护。 3. 编写程序 在完成算法设计后,就可以开始编写程序了。具体来说,需要完成以下工作: - 编写程序的源代码,按照算法设计的逻辑结构组织代码。 - 使用C语言的语法规则,定义变量、函数等。 - 使用控制语句和函数库,实现程序的功能。 在编写程序的过程中,需要注意代码的风格和可读性。代码应该具有良好的组织结构和注释,易于理解和维护。 4. 编译程序 编写程序后,需要使用编译器将程序源代码转换成可执行程序。具体来说,需要完成以下工作: - 使用编译器将程序源代码编译成二进制文件。 - 编译过程中,需要检查代码是否符合C语言的语法规则,是否存在错误。 编译后,会生成可执行文件。可以通过运行可执行文件,测试程序的功能是否正常。 5. 调试程序 运行程序并检查程序是否按照预期工作。如果出现错误,需要进行调试。具体来说,需要完成以下工作: - 分析程序的错误,确定出错的原因。 - 修改代码,消除错误。 - 重新编译程序,测试修改后的程序是否正常工作。 在调试程序的过程中,需要使用调试工具和技术,如断点调试、输出调试等。 6. 优化程序 根据程序的性能需求,对程序进行优化。具体来说,需要完成以下工作: - 分析程序的性能瓶颈,确定需要优化的部分。 - 使用合适的算法和数据结构,减少程序的运行时间和空间复杂度。 - 对代码进行改进,优化程序的可读性和可维护性。 在优化程序的过程中,需要注意程序的正确性和可读性。优化应该是有针对性的,避免过度优化导致新的问题。 7. 维护程序 对程序进行维护,修复已知的错误或更新程序以适应新的需求。具体来说,需要完成以下工作: - 定期检查程序,修复已知的错误。 - 根据新的需求,对程序进行改进和扩展。 在维护程序的过程中,需要注意程序的兼容性和可维护性。维护应该是持续性的,避免放任程序出现新的问题。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Antrn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值