Windows Socket 编程_单个服务器对多个客户端简单通讯
单个服务器对多个客户端程序:
一。简要说明
二。查看效果
三。编写思路
四。程序源代码
五。存在问题
一。简要说明:
程序名为:TcpSocketOneServerToMulClient
程序功能:实现单个服务器对多个客户端通讯功能的小程序。
PS: 这是继上次简单的 Tcp Windows Socket 编程后的再一程序,程序实现依然不是很严谨,还待完善~
二。查看效果:
三。编写思路:
由上一次的程序思路来看,如果想实现单个服务器对多个客户端程序的通讯的话,这次程序编写尝试从多线程的角度来考虑问题:
在服务器的实现中:可以main函数作为主线程,不断地接客户端的连接请求。
再新建子线程——每连接一个客户端,就专为这个客户端新建一个用于实现接收信息并显示到屏幕上功能的子线程。
然后,新建子线程,专用于本机发送消息。
在客户端的实现中:主线程负责连接服务器,新建子线程,用于从服务器接收信息,再建子线程,用于从客户端向服务器中发送信息。
总的来说,也可以理解为,单个服务器的进程利用这个服务器中的线程与多个客户端进程进行通讯。
四。程序源代码:
// OneServerMain.cpp #include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <iterator>
#include <algorithm>
#include <Winsock2.h>
#include <Windows.h> using namespace std;
HANDLE bufferMutex; // 令其能互斥成功正常通信的信号量句柄
SOCKET sockConn; // 客户端的套接字
vector <SOCKET> clientSocketGroup; int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的
wVersionRequested = MAKEWORD( , ); // 请求2.2版本的WinSock库
int err = WSAStartup( wVersionRequested, &wsaData );
if ( err != ) {
return -; // 返回值为零的时候是表示成功申请WSAStartup
}
if ( LOBYTE( wsaData.wVersion ) != || HIBYTE( wsaData.wVersion ) != ) { // 检测是否2.2版本的socket库
WSACleanup( );
return -;
} // 创建socket操作,建立流式套接字,返回套接字号sockSrv
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, ); // 套接字sockSrv与本地地址相连
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 将INADDR_ANY转换为网络字节序,调用 htonl(long型)或htons(整型)
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(); if(SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){ // 第二参数要强制类型转换
return -;
} // 将套接字设置为监听模式(连接请求), listen()通知TCP服务器准备好接收连接
listen(sockSrv, ); cout << "服务器已成功就绪,若服务器想发送信息给客户端,可直接输入内容后按回车.\n";
// accept(),接收连接,等待客户端连接 bufferMutex = CreateSemaphore(NULL, , , NULL); DWORD WINAPI SendMessageThread(LPVOID IpParameter);
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter); HANDLE sendThread = CreateThread(NULL, , SendMessageThread, NULL, , NULL); while(true){ // 不断等待客户端请求的到来
sockConn = accept(sockSrv, NULL, NULL);
if (SOCKET_ERROR != sockConn){
clientSocketGroup.push_back(sockConn);
}
HANDLE receiveThread = CreateThread(NULL, , ReceiveMessageThread, (LPVOID)sockConn, , NULL);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
if(NULL == receiveThread) {
printf("\nCreatThread AnswerThread() failed.\n");
}
else{
printf("\nCreate Receive Client Thread OK.\n");
}
ReleaseSemaphore(bufferMutex, , NULL); // V(资源占用完毕)
} WaitForSingleObject(sendThread, INFINITE); // 等待线程结束
CloseHandle(sendThread);
CloseHandle(bufferMutex);
WSACleanup(); // 终止对套接字库的使用
printf("\n");
system("pause");
return ;
} DWORD WINAPI SendMessageThread(LPVOID IpParameter)
{
while(){
string talk;
getline(cin, talk);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
/* if("quit" == talk){
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
return 0;
}
else*/
{
talk.append("\n");
}
printf("I Say:(\"quit\"to exit):");
cout << talk;
for(int i = ; i < clientSocketGroup.size(); ++i){
// send(clientSocketGroup[i], talk.c_str(), talk.size(), 0); // 发送信息
send(clientSocketGroup[i], talk.c_str(), , ); // 发送信息
}
ReleaseSemaphore(bufferMutex, , NULL); // V(资源占用完毕)
}
return ;
} DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
{
SOCKET ClientSocket=(SOCKET)(LPVOID)IpParameter;
while(){
char recvBuf[];
recv(ClientSocket, recvBuf, , );
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用) if (recvBuf[] == 'q' && recvBuf[] == 'u' && recvBuf[] == 'i' && recvBuf[] == 't' && recvBuf[] == '\0'){
vector<SOCKET>::iterator result = find(clientSocketGroup.begin(), clientSocketGroup.end(), ClientSocket);
clientSocketGroup.erase(result);
closesocket(ClientSocket);
ReleaseSemaphore(bufferMutex, , NULL); // V(资源占用完毕)
printf("\nAttention: A Client has leave...\n", , );
break;
} printf("%s Says: %s\n", "One Client", recvBuf); // 接收信息 ReleaseSemaphore(bufferMutex, , NULL); // V(资源占用完毕)
}
return ;
}
// MulClientMain.cpp #include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <winsock2.h>
#include <Windows.h> using namespace std; SOCKET sockClient; // 连接成功后的套接字
HANDLE bufferMutex; // 令其能互斥成功正常通信的信号量句柄 int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的
wVersionRequested = MAKEWORD( , ); // 请求2.2版本的WinSock库
int err = WSAStartup( wVersionRequested, &wsaData );
if ( err != ) { // 返回值为零的时候是表示成功申请WSAStartup
return -;
}
if ( LOBYTE( wsaData.wVersion ) != || HIBYTE( wsaData.wVersion ) != ) { // 检查版本号是否正确
WSACleanup( );
return -;
} // 创建socket操作,建立流式套接字,返回套接字号sockClient
sockClient = socket(AF_INET, SOCK_STREAM, );
if(sockClient == INVALID_SOCKET) {
printf("Error at socket():%ld\n", WSAGetLastError());
WSACleanup();
return -;
} // 将套接字sockClient与远程主机相连
// int connect( SOCKET s, const struct sockaddr* name, int namelen);
// 第一个参数:需要进行连接操作的套接字
// 第二个参数:设定所需要连接的地址信息
// 第三个参数:地址的长度
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路地址是127.0.0.1;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons();
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
cout << "本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n"; // send(sockClient, "\nAttention: A Client has enter...\n", strlen("Attention: A Client has enter...\n")+1, 0);
send(sockClient, "\nAttention: A Client has enter...\n", , ); bufferMutex = CreateSemaphore(NULL, , , NULL); DWORD WINAPI SendMessageThread(LPVOID IpParameter);
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter); HANDLE sendThread = CreateThread(NULL, , SendMessageThread, NULL, , NULL);
HANDLE receiveThread = CreateThread(NULL, , ReceiveMessageThread, NULL, , NULL); WaitForSingleObject(sendThread, INFINITE); // 等待线程结束
closesocket(sockClient);
CloseHandle(sendThread);
CloseHandle(receiveThread);
CloseHandle(bufferMutex);
WSACleanup(); // 终止对套接字库的使用 printf("End linking...\n");
printf("\n");
system("pause");
return ;
} DWORD WINAPI SendMessageThread(LPVOID IpParameter)
{
while(){
string talk;
getline(cin, talk);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
if("quit" == talk){
talk.push_back('\0');
// send(sockClient, talk.c_str(), talk.size(), 0);
send(sockClient, talk.c_str(), , );
break;
}
else{
talk.append("\n");
}
printf("\nI Say:(\"quit\"to exit):");
cout << talk;
// send(sockClient, talk.c_str(), talk.size(), 0); // 发送信息
send(sockClient, talk.c_str(), , ); // 发送信息
ReleaseSemaphore(bufferMutex, , NULL); // V(资源占用完毕)
}
return ;
} DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
{
while(){
char recvBuf[];
recv(sockClient, recvBuf, , );
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用) printf("%s Says: %s\n", "Server", recvBuf); // 接收信息 ReleaseSemaphore(bufferMutex, , NULL); // V(资源占用完毕)
}
return ;
}
五。存在问题:
1. 将客户端异常退出(不按程序要求输入“quit”退出),而直接用ALT+F4关闭后,服务器会出现死循环显示乱码的情况。
2. 在未启动服务器前提下,直接启动客户端时出现死循环乱码情况。
3. 服务器输入“quit”时不能正常退出。
转http://blog.csdn.net/neicole/article/details/7539444
Windows Socket 编程_单个服务器对多个客户端简单通讯的更多相关文章
- Windows Socket 编程_ 简单的服务器/客户端程序
转载自:http://blog.csdn.net/neicole/article/details/7459021 一.程序运行效果图 二.程序源代码 三.程序设计相关基础知识 1.计算机网络 2 ...
- android下socket编程问题:服务器关闭时,客户端发送请求的异常处理
我用socket分别创建了一个服务器和一个客户端. 当服务器程序运行时,客户端和服务器发送接收数据是OK的. 但是,如果服务器程序关闭以后,客户端仍然发送请求的话,会抛出一个IOException.但 ...
- linux tcp/ip编程和windows tcp/ip编程差别以及windows socket编程详解
最近要涉及对接现有应用visual c++开发的tcp客户端,花时间了解了下windows下tcp开发和linux的差别,从开发的角度而言,最大的差别是头文件(早期为了推广尽可能兼容,后面越来越扩展, ...
- socket编程实现tcp服务器_C/C++
1. 需求分析 实现一个回声服务器的C/S(客户端client/服务器server)程序,功能为客户端连接到服务器后,发送一串字符串,服务器接受信息后,返回对应字符串的大写形式给客户端显示. 例如: ...
- 利用socket编程在ESP32上搭建一个TCP客户端
通过之前http://www.cnblogs.com/noticeable/p/7636582.html中对socket的编程,已经可以知道如何通过socket编程搭建服务器和客户端了,现在,就在ES ...
- windows socket编程select模型使用
int select( int nfds, //忽略 fd_ser* readfds, //指向一个套接字集合,用来检测其可读性 ...
- Windows Socket编程精华《TCP通信服务器》
1.网络中进程之间如何通信? 首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的.其实TCP/IP协议族已经帮我们解决了这个问 ...
- socket编程和并发服务器
socket这个词可以表示很多概念: 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”就称为socket. 在TCP协议中,建立连接的两个进程 ...
- Java修炼——基于TCP协议的Socket编程_双向通信_实现模拟用户登录
首先我们需要客户端和服务器端. 服务器端需要:1.创建ServerSocket对象.2.监听客户端的请求数据.3.获取输入流(对象流)即用户在客户端所发过来的信息. ...
随机推荐
- 菜鸟初学redis(二)
如果你的redis可以在myeclipse上运行小demo了,那么可以继续学习了 redis Java String 实例 string是redis最基本的类型,一个key对应一个value. str ...
- WebForm文件上传
用 FileUpload控件进行上传文件. <asp:FileUpload ID="FileUpload1" runat="server" /> ...
- 了解Linux操作系统发展阶段
一.硬件与软件发展历史 计算机由硬件和软件组成结构 二.Linux的发展史 Linux 操作系统是Unix操作系统的一种克隆系统.它诞生于1991年的10月5日(只是第一次正式向外公布的时间).以后借 ...
- [转载]Oracle用户创建及权限设置
出处:https://www.cnblogs.com/buxingzhelyd/p/7865194.html 权限: create session 允许用户登录数据库权限 create table ...
- pthread_cond_wait虚假唤醒
pthread_cond_wait中的while()不仅仅在等待条件变量前检查条件cond_is_false是否成立,实际上在等待条件变量后也检查条件cond_is_false是否成立.在多线程等待的 ...
- python class继承
https://blog.csdn.net/brucewong0516/article/details/79121179 类继承: class SubClassName(parentClass,[,p ...
- Redis学习-主从复制、哨兵
主从复制 官方文档:https://redis.io/topics/replication Redis中的主从复制,也就是Master-Slave模型,有以下特点 Master可以拥有多个slave ...
- electron 打包流程 electron-packager + NSIS
1.安装 electron-packager 2.electron-packager 应用目录 应用名称 打包平台 左上角的图标和任务栏的图标 输出目录 架构 版本 win打包: ele ...
- AOP之proceedingjoinpoint和joinpoint区别(获取各对象备忘)、动态代理机制及获取原理代理对象、获取Mybatis Mapper接口原始对象
现在AOP的场景越来越多,所以我们有必要理解下和AOP相关的一些概念和机制. import org.aspectj.lang.reflect.SourceLocation; public interf ...
- Oracle 12c 单实例安装
准备工作 实验环境:Redhat 6.6 Oracle 12c 12.2.0.1 1.官网下载 https://www.oracle.com/technetwork/database/enterp ...