网络编程大作业-聊天室

时间:2025-03-20 16:02:08
#include "" // 是套接字接口。 #include "" // 是包含用于和宏指令的作用声明与螺纹和过程一起使用的C标头文件。 #include "" //被包含的文件通常是由系统提供的,其扩展名为.h,而stdio为standard input output的缩写,意为“标准输入输出” #include "" //stdlib 头文件里包含了C语言的一些函数,该文件包含了的C语言标准库函数的定义 #include "" //将包含入你的程序,使你可以引用其中声明的函数。不是C标准库中的头文件。 #pragma comment(lib,"ws2_32.lib") // ws2_32.lib是套接字实现。 #define SEND_OVER 1 //已经转发消息 #define SEND_YET 0 //还没转发消息 int Status = SEND_YET; //状态设为未发送 sockaddr_in ClientAddr = { 0 }; //客户端地址 HANDLE g_hRecv1 = NULL; //handle为句柄,用表示对象 void *g_hRecv1=NULL; HANDLE g_hRecv2 = NULL; //客户端信息结构体 struct Client_inf { SOCKET sClient; //客户端套接字 char buf[512]; //数据缓冲区 char userName[20]; //客户端用户名 char IP[20]; //客户端IP UINT_PTR flag; //标记客户端,用来区分不同的客户端,typedef unsigned int_w64 UINT_PTR,是为解决32位与64位编译器的兼容性而设置的关键字 }; typedef Client_inf Client; Client ClientSock[2] = { 0 }; //创建一个客户端结构体 SOCKET ServerSocket=INVALID_SOCKET; unsigned __stdcall ThreadSend(void* param); //声明发送数据的线程函数 unsigned __stdcall ThreadRecv(void* param); //声明接收数据的线程函数 unsigned __stdcall ThreadAccept(void* param); //声明接受请求的线程函数 int main(void) { int ret,len; WSADATA data ; struct sockaddr_in ServerAddr; //服务端地址 unsigned short SERVERPORT = 6666; //服务器监听端口 //初始化套接字 ret = WSAStartup(MAKEWORD(2,2),&data);//WSAStartup函数的返回值是0表示成功 if (SOCKET_ERROR == ret) { printf("WSAstartup error!\n"); return -1; } //创建套接字 ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == ServerSocket) { printf("创建socket失败!\n"); return -2; } //设置服务器地址 ServerAddr.sin_family = AF_INET;//连接方式 ServerAddr.sin_port = htons(SERVERPORT);//服务器监听端口 ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//任何客户端都能连接这个服务器 //绑定服务器 ret = bind(ServerSocket, (struct sockaddr*)&ServerAddr, sizeof(sockaddr));//bind是一组用于函数绑定的模板。 if (SOCKET_ERROR == ret) { printf("bind error!\n"); closesocket(ServerSocket); WSACleanup(); return -3; } //设置监听客户端连接数 ret =listen(ServerSocket,2);//listen是创建一个套接口并监听申请的连接. if (SOCKET_ERROR == ret) { printf("listen error!\n"); closesocket(ServerSocket); WSACleanup(); return -4; } printf("开启聊天成功,等待用户连接....\n"); _beginthreadex(NULL, 0, ThreadAccept, NULL, 0, 0); //启动接受连接线程 int k=0; while(k<100) //让主线程休眠,不让它关闭TCP连接. { Sleep(10000000); k++; } //关闭套接字 for (int j = 0;j < 2;j++) { if (ClientSock[j].sClient != INVALID_SOCKET) closesocket(ClientSock[j].sClient); } closesocket(ServerSocket); WSACleanup(); return 0; } //发送数据线程函数的定义 unsigned __stdcall ThreadSend(void* param) //param形参 { int ret = 0; int flag = *(int*)param; //int*是把param从void*强制转化为int*,要不然取值的时候系统会不知所措,外面的*就是取值操作,取param这个地址里面保存的整型值 SOCKET client = INVALID_SOCKET; //创建一个临时套接字来存放要转发的客户端套接字 char temp[512] = { 0 }; //创建一个临时的数据缓冲区,用来存放接收到的数据 memcpy(temp, ClientSock[!flag].buf, sizeof(temp)); //拷贝 sprintf( ClientSock[flag].buf, "%s: %s", ClientSock[!flag].userName, temp);//添加一个用户名头 if (strlen(temp) != 0 && Status == SEND_YET&&temp!="\n"&&(*temp!=' ')) //如果数据不为空且还没转发则转发 { ret = send( ClientSock[flag].sClient, ClientSock[flag].buf, sizeof( ClientSock[flag].buf), 0); }//send()是一个计算机函数,功能是向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。 if (SOCKET_ERROR == ret) { return 1; } Status = SEND_OVER; //转发成功后设置状态为已转发 return 0; } //接受数据线程函数的定义 unsigned __stdcall ThreadRecv(void* param) { SOCKET client = INVALID_SOCKET; //客户端套接字 int flag = 0; if (*(int*)param == ClientSock[0].flag) //判断是哪个客户端发来的消息 { client = ClientSock[0].sClient; flag = 0; } else if (*(int*)param == ClientSock[1].flag) { client = ClientSock[1].sClient; flag = 1; } char temp[512] = { 0 }; //临时数据缓冲区 while (1) { memset(temp, 0, sizeof(temp)); int ret = recv(client, temp, sizeof(temp), 0); //接收数据 if (SOCKET_ERROR == ret) continue; Status = SEND_YET; //设置转发状态为未转发 if(client==ClientSock[0].sClient ) { //设置防止出现自己给自己发消息的BUG flag=1; } else { flag=0; } memcpy( ClientSock[!flag].buf, temp, sizeof( ClientSock[!flag].buf)); _beginthreadex(NULL, 0, ThreadSend, &flag, 0, NULL); //开启一个转发线程,flag标记着要转发给哪个客户端 } return 0; } //接受请求线程函数的定义 unsigned __stdcall ThreadAccept(void* param) { int i = 0; int temp1 = 0, temp2 = 0; { while (i < 2) //表明只允许两个客户端连接 { if (ClientSock[i].flag != 0) { ++i; continue; } //如果有客户端申请连接就接受连接 int len= sizeof(ClientAddr); int ret = ClientSock[i].sClient = accept(ServerSocket, (SOCKADDR*)&ClientAddr, &len);//accept()是在一个套接口接受的一个连接。 if (INVALID_SOCKET == ret) { printf("accept error!\n"); closesocket(ServerSocket); WSACleanup(); return -1; } recv(ClientSock[i].sClient, ClientSock[i].userName, sizeof(ClientSock[i].userName), 0); //接收用户名 printf(" 成功连上聊天用户!IP:%s ,Port: %d,UerName: %s\n",inet_ntoa(ClientAddr.sin_addr), htons(ClientAddr.sin_port), ClientSock[i].userName); memcpy(ClientSock[i].IP, inet_ntoa(ClientAddr.sin_addr), sizeof(ClientSock[i].IP)); //记录客户端IP ClientSock[i].flag = ClientSock[i].sClient; //不同的socke有不同UINT_PTR类型的数字来标识 i++; } i = 0; if (ClientSock[0].flag != 0 && ClientSock[1].flag != 0 ) //当两个用户都连接上服务器后才进行消息转发 { if (ClientSock[0].flag != temp1) //每次断开一个连接后再次连上会新开一个线程,导致cpu使用率上升,所以要关掉旧的,,int temp1 = 0, temp2 = 0; { if (g_hRecv1) //这里关闭了线程句柄 CloseHandle(g_hRecv1); g_hRecv1 = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, &ClientSock[0].flag, 0, NULL); //开启2个接收消息的线程 } if (ClientSock[1].flag != temp2) { if (g_hRecv2) CloseHandle(g_hRecv2); g_hRecv2 = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, &ClientSock[1].flag, 0, NULL); } } temp1 = ClientSock[0].flag; //防止ThreadRecv线程多次开启 temp2 = ClientSock[1].flag; Sleep(2000); } return 0; }