windows网络编程 | 基于控制台的循环并发式服务器客户端程序

    科技2024-11-20  8

    文章目录

    服务器端完整代码客户端完整代码服务器/客户端运行效果:

    本程序代码基于 《windows网络编程》 5.4 编程举例 修改所得。

    程序功能: 向指定服务器发起连接请求,与服务器之间实现收发数据,然后关闭连接。客户端实现用户输入信息方式与服务器对话。

    1)使用基本的服务器编程模式,创建流式套接字,根据指定的端口号绑定服务,建立监听队列,并接受来自客户端的连接请求,收发数据;使用基本的客户端编程模式,能够创建流式套接字,通过用户指定的服务器地址 。 2)通过改进服务器端,实现服务器循环为多个客户端提供服务;服务器端能够实现显示对应客户端的ip地址; 3)采用Windows环境下多线程开发方法改进服务器端,对每个客户连接请求独立创建通信线程;实现并发服务

    注:这里规定客户端向服务器发送数据的格式为[长度]+[数据]。 比如,我们在客户端输入 “abcd”,实际发送是“9abcd”。 另外,在服务器端定义的缓冲区长度为10(#define DEFAULT_BUFLEN 10),而我们使用该缓冲区接收数据时使用循环接收的方式,以便能够全部接收来自客户端的信息。

    服务器端完整代码

    #undef UNICODE #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <stdlib.h> #include <stdio.h> #include <thread> using std::thread; // 声明线程库 #pragma warning(disable:4996) // 连接到WinSock 2对应的lib文件:Ws2_32.lib #pragma comment (lib, "Ws2_32.lib") // 定义默认的缓冲区长度和端口号 #define DEFAULT_BUFLEN 10 #define DEFAULT_PORT "27015" void task_connect(SOCKET, struct sockaddr_in); // 线程处理函数,处理已连接的套接字通讯 void Print_SerIP(); // 输出本机IP int __cdecl main(void) { WSADATA wsaData; int iResult; SOCKET ListenSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; struct addrinfo* result = NULL; struct addrinfo hints; // 初始化WinSock iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); // 声明IPv4地址族,流式套接字,TCP协议 hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; // 解析服务器地址和端口号 iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed with error: %d\n", iResult); WSACleanup(); return 1; } // 为面向连接的服务器创建套接字 ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; } // 为套接字绑定地址和端口号 iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { printf("bind failed with error: %d\n", WSAGetLastError()); freeaddrinfo(result); closesocket(ListenSocket); WSACleanup(); return 1; } freeaddrinfo(result); // 监听连接请求 iResult = listen(ListenSocket, SOMAXCONN); if (iResult == SOCKET_ERROR) { printf("listen failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } Print_SerIP(); printf("-----------------------------------\n\ 服务器已启动,(0.0.0.0)正在监听...\n\ ============================================\n"); // 接受客户端的连接请求,返回连接套接字ClientSocket // 循环接收 int id = 0; // 表示第几个客户端连接 while (true) { // 可在次数带出客户端ip ClientSocket = accept(ListenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } printf("\n>>>连接成功...[ok]\n"); struct sockaddr_in sa; // 客户端ip int len = sizeof(sa); if (!getpeername(ClientSocket, (struct sockaddr*) & sa, &len)) { printf("Client Ip:%s ", inet_ntoa(sa.sin_addr)); printf("Client Port:%d \n\n", ntohs(sa.sin_port)); } thread task(task_connect, ClientSocket, sa); task.detach(); } // 在必须要监听套接字的情况下释放该套接字 closesocket(ListenSocket); WSACleanup(); return 0; } void task_connect(SOCKET ClientSocket, struct sockaddr_in ip) { int len = sizeof(int); // 发送来的数据的长度 int iResult; // 一次recv接收到数据的长度 char recvbuf[DEFAULT_BUFLEN]; int recvbuflen = DEFAULT_BUFLEN; // 持续接收数据,直到对方关闭连接 while (1) { memset(recvbuf, 0, recvbuflen); iResult = recv(ClientSocket, recvbuf, recvbuflen - 1, 0); memcpy(&len, recvbuf, sizeof(int)); // 提取头部信息——长度 strcpy(recvbuf, recvbuf + sizeof(int)); // 将头部信息移出recvbuf if (iResult > 0) { // 情况1:成功接收到数据 [ip:port]:(len)data printf("[%s:%d]:(%d)\t\b>> %s", inet_ntoa(ip.sin_addr), ntohs(ip.sin_port), len, recvbuf); // 打印数据 while (len > iResult) // 客户端发送的数据过长,一次没接收完 { len -= iResult; memset(recvbuf, 0, recvbuflen); iResult = recv(ClientSocket, recvbuf, recvbuflen - 1, 0); printf_s("%s", recvbuf); } printf("\n"); // 利用recvbuf充当sendbuf回复客户端 strcpy(recvbuf, "ok!"); // 统一回复ok // 将缓冲区的内容回送给客户端 int iSendResult = send(ClientSocket, recvbuf, (int)strlen(recvbuf), 0); if (iSendResult == SOCKET_ERROR) { printf("send failed with error: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); } printf("reply : (%d) ok! \n-----------------------\n", iSendResult); } else if (iResult == 0) { // 情况2:连接关闭 printf("Client [%s]: Connection closing...\n", inet_ntoa(ip.sin_addr)); } else { // 情况3:接收发生错误 printf("Client [%s]: recv failed with error: %d\n", inet_ntoa(ip.sin_addr), WSAGetLastError()); closesocket(ClientSocket); printf("Client [%s]: Connection closing...\n", inet_ntoa(ip.sin_addr)); return; } } // 关闭连接 iResult = shutdown(ClientSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("Client [%s]: shutdown failed with error: %d\n", inet_ntoa(ip.sin_addr), WSAGetLastError()); closesocket(ClientSocket); printf("Client [%s]: Connection closing...\n", inet_ntoa(ip.sin_addr)); return; } // 关闭套接字,释放资源 closesocket(ClientSocket); } // 输出本机IP void Print_SerIP() { char host[255]; if (gethostname(host, sizeof(host)) == SOCKET_ERROR) { printf("无法获取主机名\n"); } else { printf("本机计算机名为:\t\b%s\n", host); } struct hostent* p = gethostbyname(host); if (p == 0) { printf("无法获取计算机主机名及IP"); } else { printf("本地环回测试IP为:\t127.0.0.1\n"); //本机IP:利用循环,输出本机所有IP for (int i = 0; p->h_addr_list[i] != 0; i++) { struct in_addr in; memcpy(&in, p->h_addr_list[i], sizeof(struct in_addr)); printf("第%d块网卡的IP为:\t\b%s\n", i + 1, inet_ntoa(in)); } } }

    客户端完整代码

    #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <stdlib.h> #include <stdio.h> // 连接到WinSock 2对应的lib文件:Ws2_32.lib, Mswsock.lib, Advapi32.lib #pragma comment (lib, "Ws2_32.lib") #pragma comment (lib, "Mswsock.lib") #pragma comment (lib, "AdvApi32.lib") // 定义默认的缓冲区长度和端口号 #define DEFAULT_BUFLEN 1024 #define DEFAULT_PORT "27015" #pragma warning(disable:4996) int __cdecl main(int argc, char** argv) { WSADATA wsaData; SOCKET ConnectSocket = INVALID_SOCKET; struct addrinfo* result = NULL, * ptr = NULL, hints; char sendbuf[DEFAULT_BUFLEN]; char recvbuf[DEFAULT_BUFLEN]; int iResult; int recvbuflen = DEFAULT_BUFLEN; char servIP[50] = "127.0.0.1"; // 保存输入的服务器IP // 初始化套接字 iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; while (true) { printf("请输入服务器域名/IP地址:\n>>>"); fflush(stdout); rewind(stdin); // 清空缓冲区 scanf_s("%s", servIP, 49); // 输入服务器IP/域名 // 解析服务器地址和端口号 iResult = getaddrinfo(servIP, DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed with error: %d\n", iResult); fflush(stdout); continue; //WSACleanup(); //return 1; } for (ptr = result; ptr != NULL; ptr = ptr->ai_next) { // 创建套接字 ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } // 向服务器请求连接 iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; continue; } break; } break; } freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; } printf(">>>连接到服务器(%s)...\n>>>连接成功...[ok]\n", servIP); struct sockaddr_in sa; // 客户端ip int len = sizeof(sa); if (!getsockname(ConnectSocket, (struct sockaddr*) & sa, &len)) { printf("Client Ip:%s ", inet_ntoa(sa.sin_addr)); printf("Client Port:%d \n\n", ntohs(sa.sin_port)); } printf("max send: %d bites\n", DEFAULT_BUFLEN); printf("====================================================\n"); char buff[40] = ""; sprintf(buff, "title client:[%s: %d]", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port)); system(buff); // 设置客户端标题 // 发送数据 bool flag = true; while (flag) { printf("@[%s] send: >>> ", servIP); fflush(stdout); rewind(stdin); // 清空输入缓冲区 /* 这里空出头部字段 */ if (0 == scanf_s("%[^\n]", sendbuf + sizeof(int), DEFAULT_BUFLEN - sizeof(int) - 1)) // 键盘输入数据 { printf("\ninput error: 输入错误(tips:最多发送 %d 字节数据) \n", DEFAULT_BUFLEN - sizeof(int) - 1); continue; } int len = strlen(sendbuf + sizeof(int)) + sizeof(int); // 发送格式 长度(4bit)+数据 memcpy(sendbuf, &len, sizeof(int)); // 写入头部信息 if (0 == strncmp(sendbuf, "end", 3)) // 输入end 结束发送 { // 数据发送结束,调用shutdown()函数声明不再发送数据,此时客户端仍可以接收数据 iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } flag = false; } else { iResult = send(ConnectSocket, sendbuf, len, 0); if (iResult == SOCKET_ERROR) { printf("send failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("|<--- (Sent size: %ld)\n|-------------------- \n", len); } // 接收服务器回复 // 由于服务器统一回复3字节,这里不用循环接收 iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); if (iResult > 0) printf("|---> (recv size: %d ) \n\n", iResult); else if (iResult == 0) printf("Connection closed\n"); else printf("recv failed with error: %d\n", WSAGetLastError()); } // 关闭套接字 closesocket(ConnectSocket); // 释放资源 WSACleanup(); return 0; }

    服务器/客户端运行效果:

    Processed: 0.055, SQL: 8