客户端/服务端模式:
在TCP/IP网络应用中,通信的两个进程相互作用的主要模式是客户/服务器模式,即客户端向服务器发出请求,服务器接收请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:
(1)建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而就让拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。
(2)网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区。
因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务端模式的TCP/IP。
- 服务端:建立socket,声明自身的端口号和地址并绑定到socket,使用listen打开监听,然后不断用accept去查看是否有连接,如果有,捕获socket,并通过recv获取消息的内容,通信完成后调用closeSocket关闭这个对应accept到的socket,如果不再需要等待任何客户端连接,那么用closeSocket关闭掉自身的socket。
- 客户端:建立socket,通过端口号和地址确定目标服务器,使用Connect连接到服务器,send发送消息,等待处理,通信完成后调用closeSocket关闭socket。
具体流程:
编程环境:
Windows10
VS2015
本机测试
实验代码
服务器
#include<iostream>
#include<WinSock2.h> //第2版本的网络库
#pragma comment(lib,"ws2_32.lib") //windows 32位版本的,没有64版本的,但是64位下也有32位版本的
int main()
{
//初始化WSA
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
int nRes = WSAStartup(sockVersion, &wsaData);//打开一个套接字
if (0 != nRes)
{
switch (nRes)
{
case WSASYSNOTREADY:
printf("重启电脑,或者检查网络库"); break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库"); break;
case WSAEINPROGRESS:
printf("请重新启动"); break;
case WSAEPROCLIM:
printf("请关闭不必要的软件,以确保有足够的网络资源");
break;
}
}
if (2 != HIBYTE(wsaData.wVersion) || 2 != LOBYTE(wsaData.wVersion))
{
printf("网络库版本错误");
WSACleanup();//关闭此套接字
return 0;
}
//套接字的创建和关闭
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if (listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if (sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//接收数据
int ret = recv(sClient, revData, 255, 0);
if (ret > 0)
{
revData[ret] = 0x00;
printf(revData);
}
//发送数据
const char * sendData = "你好,TCP客户端!我是服务器\n";
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
客户端
#include<WinSock2.h>
#include<iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
int send_len = 0;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
while (true)
{
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sclient == INVALID_SOCKET)
{
printf("invalid socket!");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{ //连接失败
printf("connect error !");
closesocket(sclient);
return 0;
}
//string data;
//scanf("%s", data);
//const char * sendData;
//sendData = data.c_str(); //string转const char*
char * sendData = "你好,TCP服务端,我是客户端\n";
send_len = send(sclient, sendData, strlen(sendData), 0);
//send()用来将数据由指定的socket传给对方主机
//int send(int s, const void * msg, int len, unsigned int flags)
//s为已建立好连接的socket,msg指向数据内容,len则为数据长度,参数flags一般设0
//成功则返回实际传送出去的字符数,失败返回-1,错误原因存于error
//char data2[32] = { 0 };
//scanf("%s", data2);
//send_len = send(sclient, data2, strlen(data2), 0);
if (send_len < 0) {
cout << "发送失败!" << endl;
break;
}
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if (ret>0)
{
recData[ret] = 0x00;
printf(recData);
break;
}
closesocket(sclient);
}
getchar();
WSACleanup();
return 0;
}
注意事项:
使用vs2015编译代码的时候,会遇到报错inet_ntoa;
解决方案:
项目->属性->C/C+±>常规->SDL检查 改为否
实验结果
启动server端,可以连接多个客户端
完整项目地址
简单CS模型
.