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.获取输入流(对象流)即用户在客户端所发过来的信息. ...
随机推荐
- sublime快捷方式
- 对java多态的理解
java多态,如何理解父类引用指向子类对象 要理解多态性,首先要知道什么是“向上转型”. 我定义了一个子类Cat,它继承了Animal类,那么后者就是前者是父类.我可以通过 Cat c = new C ...
- vue开发目录
基于 vue-cli 脚手架生成项目模板基础上做了些改动,加入了 vue-router ,vuex 等配套设施,本地 dev server 中加入了接口 mock 功能,还增加一个 build ser ...
- PowerDesigner 15的Table表视图的列显示Code
PowerDesigner 15的图表的Table表视图一般显示成这样: 现在,我要将Code显示到Table表视图上,该怎么做?选择菜单:Tools→Display Preferences,弹出对话 ...
- pandas的基本功能(一)
第16天pandas的基本功能(一) 灵活的二进制操作 体现在2个方面 支持一维和二维之间的广播 支持缺失值数据处理 四则运算支持广播 +add - sub *mul /div divmod()分区和 ...
- c++的一些入门
cout:对象名 <<:是一种插入运算符 endl:换行 :列如:count <<"Hello!"<<endl; //把hello送到 ...
- 什么是Referer?Referer的作用?空Referer是怎么回事?
什么是Referer? Referer是 HTTP请求header 的一部分,当浏览器(或者模拟浏览器行为)向web 服务器发送请求的时候,头信息里有包含 Referer.比如我在www.sojson ...
- 怎么把Thu Nov 22 2018 10:49:36 GMT+0800转换成正常日期
this.data //Thu Nov 22 2018 10:49:36 GMT+0800 this.date_of_birth = date.getFullYear() + '-' + (date. ...
- Gitlab安装、汉化及使用
环境:centos 关闭防火墙和selinux [root@Gitlab ~]# setenforce [root@Gitlab ~]# service iptables stop && ...
- 迭代(遍历)时候不可以使用集合的remove和add方法,但可使用Java迭代器的remove和add方法
不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式. 反例: public class ForeachTest { private ...