socke通信之三:阻塞版本的客户/服务器模型
上一篇中实现出来的客户端只能向服务器端发送一次数据,然后就断开了连接,那么如果需要向服务器端持续发送数据,那么应该怎么做?
一个很直观地想法就是修改客户端的第4步,即发送,接收数据那一步,在基本的客户/服务器模型中我们是直接发送一个字符串给服务器端,现在我们从控制台接收数据将接收到的数据发送给服务器端,然后从服务端端接收数据并打印输出。并持续从控制台读取数据。
而服务器端也只需要更改第6步,即调用send和recv这两个函数和客户端进行通信这一步,在源代码中通过注释可以很方便找到这一步,现在服务器端也需要持续从客户端接收数据,将接收到的数据显示在客户端,然后在这个数据前面加上“message from client:”这个字符串后再将字符串发送给客户端。
可以发现,客户端的代码只在第4步进行了修改,而服务器端的代码只在第6步进行了修改。
服务器端的代码:
- #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();
- }
客户端代码:
- #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步接收数据和发送数据中的死循环了。
当关闭第一个客户端以后,服务器端会可以变成下图所示:显示有客户端退出,并显示有客户端连接。
下一篇将介绍多个客户端同时连接服务器进行通信的方法。
转载请注明本文地址:socke通信之三:阻塞版本的客户/服务器模型
上一篇中实现出来的客户端只能向服务器端发送一次数据,然后就断开了连接,那么如果需要向服务器端持续发送数据,那么应该怎么做?
一个很直观地想法就是修改客户端的第4步,即发送,接收数据那一步,在基本的客户/服务器模型中我们是直接发送一个字符串给服务器端,现在我们从控制台接收数据将接收到的数据发送给服务器端,然后从服务端端接收数据并打印输出。并持续从控制台读取数据。
而服务器端也只需要更改第6步,即调用send和recv这两个函数和客户端进行通信这一步,在源代码中通过注释可以很方便找到这一步,现在服务器端也需要持续从客户端接收数据,将接收到的数据显示在客户端,然后在这个数据前面加上“message from client:”这个字符串后再将字符串发送给客户端。
可以发现,客户端的代码只在第4步进行了修改,而服务器端的代码只在第6步进行了修改。
服务器端的代码:
- #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();
- }
客户端代码:
- #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步接收数据和发送数据中的死循环了。
当关闭第一个客户端以后,服务器端会可以变成下图所示:显示有客户端退出,并显示有客户端连接。
下一篇将介绍多个客户端同时连接服务器进行通信的方法。