您的位置:首页 > 其它

socke通信之三:阻塞版本的客户/服务器模型

2015-09-02 14:50 190 查看
上一篇中实现出来的客户端只能向服务器端发送一次数据,然后就断开了连接,那么如果需要向服务器端持续发送数据,那么应该怎么做?

一个很直观地想法就是修改客户端的第4步,即发送,接收数据那一步,在基本的客户/服务器模型中我们是直接发送一个字符串给服务器端,现在我们从控制台接收数据将接收到的数据发送给服务器端,然后从服务端端接收数据并打印输出。并持续从控制台读取数据。

而服务器端也只需要更改第6步,即调用send和recv这两个函数和客户端进行通信这一步,在源代码中通过注释可以很方便找到这一步,现在服务器端也需要持续从客户端接收数据,将接收到的数据显示在客户端,然后在这个数据前面加上“message from client:”这个字符串后再将字符串发送给客户端。

可以发现,客户端的代码只在第4步进行了修改,而服务器端的代码只在第6步进行了修改。

服务器端的代码:

[cpp]
view plaincopy

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <iostream>

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


using namespace std;

#define PORT 6000
//#define IP_ADDRESS "10.11.163.113"
#define IP_ADDRESS "127.0.0.1" //设置连接的服务器的ip地址

void main()
{

WSADATA wsaData;
int err;

//1.加载套接字库
err=WSAStartup(MAKEWORD(1,1),&wsaData);
if (err!=0)
{
cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl;
return ;
}

//2.创建socket
//套接字描述符,SOCKET实际上是unsigned int
SOCKET serverSocket;
serverSocket=socket(AF_INET,SOCK_STREAM,0);
if (serverSocket==INVALID_SOCKET)
{
cout<<"Create Socket Failed::"<<GetLastError()<<endl;
return ;
}


//服务器端的地址和端口号
struct sockaddr_in serverAddr,clientAdd;
serverAddr.sin_addr.s_addr=inet_addr(IP_ADDRESS);
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(PORT);

//3.绑定Socket,将Socket与某个协议的某个地址绑定
err=bind(serverSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if (err!=0)
{
cout<<"Bind Socket Failed::"<<GetLastError()<<endl;
return ;
}


//4.监听,将套接字由默认的主动套接字转换成被动套接字
err=listen(serverSocket,10);
if (err!=0)
{
cout<<"listen Socket Failed::"<<GetLastError()<<endl;
return ;
}

cout<<"服务器端已启动......"<<endl;

int addrLen=sizeof(clientAdd);

while(true)
{
//5.接收请求,当收到请求后,会将客户端的信息存入clientAdd这个结构体中,并返回描述这个TCP连接的Socket
SOCKET sockConn=accept(serverSocket,(struct sockaddr*)&clientAdd,&addrLen);
if (sockConn==INVALID_SOCKET)
{
cout<<"Accpet Failed::"<<GetLastError()<<endl;
return ;
}

cout<<"客户端连接:"<<inet_ntoa(clientAdd.sin_addr)<<":"<<clientAdd.sin_port<<endl;

char receBuff[MAX_PATH];
char sendBuf[MAX_PATH];
//6.调用send和recv这两个函数和客户端进行通信,相比于第一个版本,只有这一步发生了变化
while(true)
{
memset(receBuff,0,sizeof(receBuff));
memset(sendBuf,0,sizeof(sendBuf));

//接收数据
err=recv(sockConn,receBuff,MAX_PATH,0);

//下面是客户端退出的判断条件,如果不加这个条件,在客户端关闭后服务器会一直执行recv语句
if (err==0||err==SOCKET_ERROR)
{
cout<<"客户端退出"<<endl;
break;
}

cout<<"message from client:"<<receBuff<<endl;
strcpy(sendBuf,"server receive a message:");
strcat(sendBuf,receBuff);

//发送数据
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
}

//关闭这个socket
closesocket(sockConn);
}

closesocket(serverSocket);
//清理Windows Socket库
WSACleanup();
}

客户端代码:

[cpp]
view plaincopy

#include <stdio.h>
#include <WinSock2.h>
#include <iostream>
#include <string>

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

#define PORT 6000
//#define IP_ADDRESS "10.11.163.113" //表示服务器端的地址
#define IP_ADDRESS "127.0.0.1" //直接使用本机地址

using namespace std;

void main()
{
WSADATA wsaData;
int err;

//1.首先执行初始化Windows Socket库
err=WSAStartup(MAKEWORD(1,1),&wsaData);
if (err!=0)
{
cout<<"Init Socket Failed::"<<GetLastError()<<endl;
return ;
}

//2.创建Socket
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_in addrServer;
addrServer.sin_addr.s_addr=inet_addr(IP_ADDRESS);
addrServer.sin_family=AF_INET;
addrServer.sin_port=htons(PORT);

//3.连接Socket,第一个参数为客户端socket,第二个参数为服务器端地址
err=connect(sockClient,(struct sockaddr *)&addrServer,sizeof(addrServer));
if (err!=0)
{
cout<<"Connect Error::"<<GetLastError()<<endl;
return ;
}else
{
cout<<"连接成功!"<<endl;
}

char sendBuff[MAX_PATH];
char recvBuf[MAX_PATH];
while (true)
{
//4.发送,接收数据,相比与第一个版本,只有这一步发生了变化
cin.getline(sendBuff,sizeof(sendBuff));
send(sockClient,sendBuff,strlen(sendBuff)+1,0); //第三个参数加上1是为了将字符串结束符'\0'也发送过去
recv(sockClient,recvBuf,MAX_PATH,0);
cout<<recvBuf<<endl;
}

//4.关闭套接字
closesocket(sockClient);
WSACleanup();
}

上面这个模型在只有一个客户端时是能正常运行的,如下:

客户端连接服务器成功后会在客户端显示“连接成功!”的字符串,同时服务器端也显示连接成功的客户端的ip地址和端口号,接着客户端发送hello,nihao,good给服务器端,服务器每次收到一个字符串会在服务器端显示"message from client:"+字符串。



一个客户端连接时服务器端能正常工作,但是有多个客户端连接时服务器端就会出问题了,这是由于默认情况下socket是阻塞式的,在上面服务器端的代码中第6步现在被替换成了一个循环来连续接受来自哪个连接的数据,在这个连接没有断开之前,它是不会再次进行accept调用,连接其它客户端的。

如下图所示:当再次启动一个客户端时,客户端显示“连接成功!”表示客户端的connect成功返回,但是服务器端没有显示“客户端连接:”这行字符串,这行字符串是在accept函数返回时调用的,所以accept函数此时没有返回。原因也很简单,服务器陷入第6步接收数据和发送数据中的死循环了。



当关闭第一个客户端以后,服务器端会可以变成下图所示:显示有客户端退出,并显示有客户端连接。



可执行文件可以在这儿下载,工程文件可以在这儿下载。

下一篇将介绍多个客户端同时连接服务器进行通信的方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: