您的位置:首页 > 编程语言

Windows Socket 编程 服务器端 可多用户连接

2017-11-30 01:08 267 查看


                                          效果图

一、介绍

      VC6下编译通过,监听797端口(自己随便设置的一个),每当有客户端连接时就创建一个新线程接收这个客户端发送的消息,客户端离开时进程结束,接收到客户端的消息后将消息转发给除了发送者外的所有其他客户端(实现一个简单的群聊的功能)。

二、源码和注释

// Server.cpp

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <iterator>
#include <algorithm>
#include <Winsock2.h>
#include <Windows.h>

#pragma comment(lib,"ws2_32.lib")

using namespace std;
HANDLE bufferMutex;     // 令其能互斥成功正常通信的信号量句柄
SOCKET sockConn;        // 客户端的套接字
vector <SOCKET> clientSocketGroup;

int g_nThreadNum=0;//当前的接收线程数 也就是当前连接的客户数

//自定义的类型 用来存放客户端的SOCKET唯一编号和客户端ip地址
struct RecvType{
SOCKET recvSocket;
struct  in_addr recvIp;
};

int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData;    // 这结构是用于接收Wjndows Socket的结构信息的
wVersionRequested = MAKEWORD( 2, 2 );   // 请求2.2版本的WinSock库
int err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1;          // 返回值为零的时候是表示成功申请WSAStartup
}
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 检测是否2.2版本的socket库
WSACleanup( );
return -1;
}

// 创建socket操作,建立流式套接字,返回套接字号sockSrv
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

// 套接字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(797);  //这个是端口号,我设置的是797,可以改成自己的(如果使用的是云服务器需要在安全策略中增加该端口)

if(SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){ // 第二参数要强制类型转换
return -1;
}

// 将套接字设置为监听模式(连接请求), listen()通知TCP服务器准备好接收连接 100表示最大连接数为100
listen(sockSrv, 100);

cout << "服务器已就绪.\n";
// accept(),接收连接,等待客户端连接

bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);

DWORD WINAPI SendMessageThread(LPVOID IpParameter);
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);

//只创建一次发送线程
HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);

RecvType *recvType;

while(true){    // 不断等待客户端请求的到来  因为是服务器所以进程会一直运行 所以这里用while(true)
recvType=(RecvType*)(malloc(sizeof(RecvType)));//自定义的类型
SOCKADDR_IN clientsocket;
int len=sizeof(SOCKADDR);
sockConn = accept(sockSrv, (SOCKADDR*)&clientsocket,&len);//接收到连接的请求才会继续之后的代码

//将客户端的唯一标识和IP地址记录下来,作为参数传给接收线程,接收线程显示“100.100.0.1 says:hello”
recvType->recvSocket=sockConn;
recvType->recvIp =clientsocket.sin_addr;

if (SOCKET_ERROR != sockConn){
clientSocketGroup.push_back(sockConn);
}  //如果连接没有错误,将新连接的SOCKET加入到客户端组的向量中

//创建一个接收线程,将自定义的类型的地址作为参数传给线程
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, (LPVOID)recvType, 0, NULL);

WaitForSingleObject(bufferMutex, INFINITE);     //等待互斥体未被占用后执行之后的代码,然后占用互斥体,防止多个线程同时对屏幕缓冲进行读写
if(NULL == receiveThread) {
printf("\nCreatThread AnswerThread() failed.\n");
}
else{
g_nThreadNum++;
printf("\nCreate Receive Client Thread OK.Current Thread Count:%d\n",g_nThreadNum);

}
ReleaseSemaphore(bufferMutex, 1, NULL);     // 解除资源占用
}

WaitForSingleObject(sendThread, INFINITE);  // 等待线程结束
CloseHandle(sendThread);
CloseHandle(bufferMutex);
WSACleanup();   // 终止对套接字库的使用
printf("\n");
system("pause");
return 0;
}

DWORD WINAPI SendMessageThread(LPVOID IpParameter)  //发送线程 只会有一个发送线程存在
{
while(1){
string talk;
getline(cin, talk);  //在服务器进程直接输入内容可以发给所有的客户端(不过不推荐这样做)

WaitForSingleObject(bufferMutex, INFINITE);     // 屏幕资源未被占用则执行之后代码并占用屏幕资源

//显示的时候增加一个回车
if (!talk.empty() && talk!="\n"){
talk=talk+'\n';

cout <<"Send:"<< talk;
for(int i = 0; i < clientSocketGroup.size(); ++i){  //用循环遍历所有在线的客户端,给他们发送消息

send(clientSocketGroup[i], talk.c_str(), talk.size(), 0);   // 发送信息
}
}
ReleaseSemaphore(bufferMutex, 1, NULL);     // 解除屏幕资源的占用
}
return 0;
}

DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)  //接收线程 每当有客户端连接时都会新建一个接收线程
{
//SOCKET ClientSocket=(SOCKET)(LPVOID)IpParameter;

RecvType *recvType=(RecvType *)(LPVOID)IpParameter;
SOCKET ClientSocket=recvType->recvSocket;//获取连接的SOCKET编号

while(1){     //与客户端的TCP连接正常时一直接收客户端发来的消息
char recvBuf[300];
int t=recv(ClientSocket, recvBuf, 200, 0); //接收客户端发来的消息 t为接收的长度
WaitForSingleObject(bufferMutex, INFINITE);     //屏幕资源未被占用则执行之后代码并占用屏幕资源

if ( (t==0) || (t==-1) ){  //接收不到客户端的连接状态时断开TCP连接,并且结束线程,释放资源
vector<SOCKET>::iterator result = find(clientSocketGroup.begin(), clientSocketGroup.end(), ClientSocket);
clientSocketGroup.erase(result);
closesocket(ClientSocket);
ReleaseSemaphore(bufferMutex, 1, NULL);     // 释放屏幕资源
printf("\nAttention:%s has leave...\n",inet_ntoa(recvType->recvIp));
g_nThreadNum--;//接收线程数减1,当前连接数减1
break;
}
recvBuf[t]='\0';

printf("%s Says: %s status:%d\n", inet_ntoa(recvType->recvIp), recvBuf,t);     // 输出接收信息如“100.100.0.1 say:hello~server”

//接收到任何一个客户端发来的消息以后 转发给除了发送者的其他所有客户端
///////////////////////////////////////////
for(int i = 0; i < clientSocketGroup.size(); ++i){
//      send(clientSocketGroup[i], talk.c_str(), talk.size(), 0);   // 发送信息
if(clientSocketGroup[i]!=ClientSocket)
send(clientSocketGroup[i], recvBuf, t, 0);   // 发送信息
}

/////////////////////////////////////////////

ReleaseSemaphore(bufferMutex, 1, NULL);     //释放屏幕资源
}
return 0;
}


三、测试

      1.使用VC6编译以上源码,并且上传到服务器,运行。(服务器的安全策略需要开797端口)

      2.打开两个能建立TCP连接的客户端(我这里使用的一个是netcat(nc),另外一个是手机的APP),使这两个客户端与服务器处于能连通的网络内。(可以是局域网,两个客户端连在和服务器同一个交换机或路由器,如果服务器是在公网下两个客户端能上网就行了)

      3.客户端连接服务器的IP地址,端口填写797。

      4.一方发送信息,另一方能接收到。



 安卓的具有TCP连接功能的APP客户端发送了一个你好tcp(APP源码后续会放出)



已经与服务器建立好TCP连接的NetCat(NC)收到了APP发来的消息
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  windows c++ tcp socket winsock
相关文章推荐