Windows平台基于TCP协议Sockets多人聊天室控制台程序

程序实现了基本的多客户端“群聊”功能,还有一些问题需要完善。
通过socket实现服务端与客户端间通信,服务端采用多线程实现与多个客户端同时通信,接受客户端的消息并转发至所有客户端。客户端采用多线程同时接受与发送消息。

服务端:

      #include
        #include"stdlib.h"
        #include
        #include
        #include
        //#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 << "接收来自客户端 "<

客户端:

#include
#include
#include
#include
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检查

遇到的部分问题:

  1. connect连接不上,错误WSAGetLastError() 10049:表示IP或者端口不能用,逐步调试确定IP是否有误及端口号是否被设为0;
  2. socket返回的值是一个文件描述符,socket类型是定义为int型的,错误是返回-1,INVALID_SOCKET 就是被定义为 -1
  3. WSAStartup()中第一个参数不严格,如果传入的版本不存在会自动调用最低版本。
  4. 协议族:PF_INET(表示使用Internet的TCP/IP协议族)
    服务:SOCK_STREAM(表示使用流式服务,也就是TCP服务)
  5. LOWORD()得到一个32bit数的低16bit
    HIWORD()得到一个32bit数的高16bit
    LOBYTE()得到一个16bit数的低字节
    HIBYTE()得到一个16bit数的高字节
  6. 其余函数及函数参数问题就百科吧

理论:
运输层主要有两种协议,传输控制协议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.连接建立阶段
服务端情况:

  1. bind
    套接字被建立后,端口号和IP地址都是空的,应用进程要调用bind来指明套接字的本地地址(本地端口号和本地IP地址),在服务端就是把本地地址绑定到套接字,在客户端可以不调用bind,由操作系统内核自动分配一格动态端口号;
  2. lishten
    服务器调用bind后,必须调用收听listen把套接字设置为被动方式,以便随时接受客户的服务请求(UDP服务器由于只提供无连接服务,不使用listen系统调用。)
  3. accept
    服务器紧接着调用accept,以便把远地客户进程发来的连接请求提取出来,系统调用accept就是要指明从哪一个套接字发起的连接,服务器必须能够同时处理多个连接,就是并发方式工作的服务器,主服务器进程一调用accept,就为每一个新的连接请求创建一个新的套接字,并把这个套接字的标识符返回给发起连接的客户。主服务器进程还要创建一个从属服务器进程来处理新的链接,从属服务器用套接字与客户链接,主服务器进程用原来的套接字从新调用accept接收下一个连接请求

客户端情况:
客户已经调用socket创建了套接字,客户调用connect,以便和远地服务器建立连接,(相当于客户发出的连接请求,在connect中,客户必须指明远地端点)(端口号及IP地址)。

2,数据传送阶段
客户和服务器都在TCP连接上使用send系统调用传送数据,使用recv系统调用接口数据。通常客户使用send发送请求,而服务器使用send发送回答,服务器使用recv接收客户用send调用发送的请求。客户在发完请求后用revc接收回答。

调用send需要三个变量:

  1. 数据要发送的套接字的描述符
  2. 要发送的数据的地址
  3. 数据的长度

调用recv需要三个变量:

  1. 套接字的描述符
  2. 缓存的地址
  3. 缓存空间的长度

3,连接释放阶段
调用close释放连接,撤销套接字。

你可能感兴趣的:(Windows平台基于TCP协议Sockets多人聊天室控制台程序)