程序实现了基本的多客户端“群聊”功能,还有一些问题需要完善。
通过socket实现服务端与客户端间通信,服务端采用多线程实现与多个客户端同时通信,接受客户端的消息并转发至所有客户端。客户端采用多线程同时接受与发送消息。
服务端:
#include<iostream>
#include"stdlib.h"
#include<winsock2.h>
#include<string>
#include<stdio.h>
//#pragma comment(lib,"ws2_32.lib")**项目-属性-链接库-输入-附加依赖项加载ws2_32.lib(取消勾选“从父级或项目默认设置继承”)**
using namespace std;
int count = 0;
SOCKET clientSocket[1024] = { 0 };
DWORD WINAPI sendThread(LPVOID lpParam)
{
char str[1024] = { 0 };
char recBuf[1024] = {0};
char sendBuf[1024] = {0};
int i = (int)lpParam;
while (1)
{
int receByte = recv(clientSocket[i-1], recBuf, sizeof(recBuf), 0);
if (receByte == -1)
{
cout << "接收来自客户端 "<<i<<" 的消息失败!" << endl;
break;
}
else
{
cout << "接收到来自客户端 " << i << " 的消息:" << recBuf << endl;
for (int j = 0; j < ::count; j++)
{
int sendByte = send(clientSocket[j], recBuf, sizeof(recBuf), 0);//返回发送数据的总和
if (sendByte < 0)
{
cout << "发送失败" << endl;
}
else
{
cout << "成功发送到客户端 " << j+1 << " 的消息:";
cout << recBuf << endl;
continue;
}
}
}
}
closesocket(*clientSocket);
return 0;
}
int main(void)
{
WSADATA wsaData;
WORD require;
require = MAKEWORD(2, 2);
if (WSAStartup(require, &wsaData)!=0)//成功加载返回0;
{
cout << "加载winsock失败!" << endl;
return -1;
}
if (LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2)
{
cout << "请求版本失败!" << endl;
return -1;
}
cout << "请求版本成功!" << endl;
SOCKET severSocket;
severSocket=socket(AF_INET, SOCK_STREAM, 0);
if (severSocket == -1)
{
cout << "创建socket失败!" << endl;
return -1;
}
cout << "创建socket成功!" << endl;
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = INADDR_ANY;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(25000);
int b;
b=bind(severSocket, (sockaddr*)&addr, sizeof(addr));
if (b == -1)
{
cout << "绑定socket失败!" << endl;
return -1;
}
cout << "绑定socket成功!" << endl;
b=listen(severSocket, 10);
if (b == -1)
{
cout << "listen失败!" << endl;
return -1;
}
cout << "listen成功......" << endl;
//主线程用于接收请求
int i = 0;
while (1)
{
//clientSocket = (SOCKET*)malloc(sizeof(SOCKET));
clientSocket[i] = accept(severSocket, NULL, NULL);
i++;
::count++;
cout << "客户端" << i << "已连接到服务器" << endl;
CreateThread(NULL, 0, &sendThread, (LPVOID*)i, 0, NULL);
}
closesocket(severSocket);
WSACleanup();
system("pause");
return 0;
}
客户端:
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
using namespace std;
DWORD WINAPI receThread(LPVOID lpParam)
{
char receBuff[1024] = { 0 };
SOCKET *serveSocket = (SOCKET*)lpParam;
while (1)
{
int recvByte = recv(*serveSocket, receBuff, sizeof(receBuff), 0);
if (recvByte >0)
{
cout << "接收到来自服务器的消息:" << receBuff << endl;
continue;
}
else
{
cout << "收信结束" << endl;
break;
}
}
closesocket(*serveSocket);
return 0;
}
int main(void)
{
WSADATA wsaData;
WORD require = MAKEWORD(2, 2);
if (WSAStartup(require, &wsaData) != 0)
{
cout << "加载winsock失败" << endl;
return -1;
}
cout << "加载winsock成功" << endl;
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
cout << "请求版本失败" << endl;
return -1;
}
cout << "加载版本成功" << endl;
SOCKET clientSocket;
clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == -1)
{
cout << "创建socket失败" << endl;
return -1;
}
cout << "创建socket成功" << endl;
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//10.230.136.55
addr.sin_port = htons(25000);
int b;
b = connect(clientSocket, (sockaddr*)&addr, sizeof(addr));//
if (b == -1)
{
cout << WSAGetLastError() << endl;
cout << "连接失败" << endl;
return -1;
}
cout << "已成功连接到服务器,可以发送消息" << endl;
char str[1024];
char sendBuff[1024] = { 0 };
char receBuff[1024] = { 0 };
CreateThread(NULL, 0, &receThread, &clientSocket, 0, NULL);
while (1)
{
cout << "请输入消息:" << endl;
gets_s(str);
strcpy(sendBuff, str);
int sendByte;
sendByte = send(clientSocket, sendBuff, sizeof(sendBuff), 0);
if (sendByte < 0)
{
cout << "发送失败" << endl;
break;
}
else
{
cout << "发送成功" << endl;
continue;
}
}
closesocket(clientSocket);
WSACleanup();
system("pause");
return 0;
}
客户端服务端均需在项目-属性中关闭SDL检查
遇到的部分问题:
- connect连接不上,错误WSAGetLastError() 10049:表示IP或者端口不能用,逐步调试确定IP是否有误及端口号是否被设为0;
- socket返回的值是一个文件描述符,socket类型是定义为int型的,错误是返回-1,INVALID_SOCKET 就是被定义为 -1
- WSAStartup()中第一个参数不严格,如果传入的版本不存在会自动调用最低版本。
- 协议族:PF_INET(表示使用Internet的TCP/IP协议族)
服务:SOCK_STREAM(表示使用流式服务,也就是TCP服务) - LOWORD()得到一个32bit数的低16bit
HIWORD()得到一个32bit数的高16bit
LOBYTE()得到一个16bit数的低字节
HIBYTE()得到一个16bit数的高字节 - 其余函数及函数参数问题就百科吧
理论:
运输层主要有两种协议,传输控制协议TCP(transmission control protocol)、用户数据报协议UDP(user datagram protocol)
网络层使用IP协议(Internet ptotocol)网际协议,网络层另一个任务选择合适的路由,TCP连接的端点不是主机,不是主机的IP地址,不是应用进程,也不是运输层地协议端口,TCP连接的端点是socket(套接字);端口号拼接到IP地址即构成了套接字(192.3.4.5:80)每一条TCP连接唯一地被通信两端的两个端点(即两套接字)所确定:
TCP连接::={socket1,socket2}={(IP1:port1 ),(IP2:port2)}
socket=(IP地址:端口号)
1.连接建立阶段
服务端情况:
- bind
套接字被建立后,端口号和IP地址都是空的,应用进程要调用bind来指明套接字的本地地址(本地端口号和本地IP地址),在服务端就是把本地地址绑定到套接字,在客户端可以不调用bind,由操作系统内核自动分配一格动态端口号; - lishten
服务器调用bind后,必须调用收听listen把套接字设置为被动方式,以便随时接受客户的服务请求(UDP服务器由于只提供无连接服务,不使用listen系统调用。) - accept
服务器紧接着调用accept,以便把远地客户进程发来的连接请求提取出来,系统调用accept就是要指明从哪一个套接字发起的连接,服务器必须能够同时处理多个连接,就是并发方式工作的服务器,主服务器进程一调用accept,就为每一个新的连接请求创建一个新的套接字,并把这个套接字的标识符返回给发起连接的客户。主服务器进程还要创建一个从属服务器进程来处理新的链接,从属服务器用套接字与客户链接,主服务器进程用原来的套接字从新调用accept接收下一个连接请求
客户端情况:
客户已经调用socket创建了套接字,客户调用connect,以便和远地服务器建立连接,(相当于客户发出的连接请求,在connect中,客户必须指明远地端点)(端口号及IP地址)。
2,数据传送阶段
客户和服务器都在TCP连接上使用send系统调用传送数据,使用recv系统调用接口数据。通常客户使用send发送请求,而服务器使用send发送回答,服务器使用recv接收客户用send调用发送的请求。客户在发完请求后用revc接收回答。
调用send需要三个变量:
- 数据要发送的套接字的描述符
- 要发送的数据的地址
- 数据的长度
调用recv需要三个变量:
- 套接字的描述符
- 缓存的地址
- 缓存空间的长度
3,连接释放阶段
调用close释放连接,撤销套接字。