网络编程大作业-聊天室
#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;
}